Spring Security入门基础

Posted 李奈 - Leemon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Security入门基础相关的知识,希望对你有一定的参考价值。

Spring Security入门基础

一,Spring Security的使用

1.1 基本术语

SpringSecurity是一个强大的可高度定制的认证和授权框架,对于Spring应用来说它是一套Web安全标准。Spring Security注重于为java应用提供认证和授权功能。

OAuth2是用于授权的行业标准协议,为简化客户端开发提供类特定的授权流。

Resource owner(资源拥有者):拥有该资源的最终用户,有访问资源的账号密码。

Resource server(资源服务器):拥有受保护资源的服务器,如果请求包含正确的访问令牌,可以访问资源。

Client(客户端):访问资源的客户端,会使用访问令牌去获取资源服务器的资源,可以是浏览器,移动设备或者服务器。

Authorization server(授权服务器):用于授权用户的服务器,如果客户端授权通过,发放访问资源服务器的令牌。

四种授权模式:

  • Authorization Code(授权码模式):客户端先将用户导向授权服务器,登陆后获取授权码,然后进行授权,最后根据授权码获取访问令牌。

  • Implicit(简化模式):和授权码模式相比,取消了获取授权码的过程,直接获取访问令牌。

  • Resource Owner Password Credential(密码模式):客户端直接向用户获取用户名和密码,之后向授权服务器获取访问令牌。

  • Client Credential(客户端模式):客户端直接通过客户端授权,从授权服务器获取访问令牌。

1.2 基本使用

1.2.1 引入依赖

新创建模块tools-security,引入依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!--    安全认证    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

1.2.2 配置文件

server:
  port: 9040

spring:
  application:
    name: tools-security

1.2.3 测试接口

    @RequestMapping("test")
    public String test() {
        return "test";
    }

运行项目,在以下截取的部分日志中,找到 Using generated security password,后面的字符串为原始密码(每次运行结果不同),账号为 user

2021-10-23 15:52:58.472  WARN [tools-security,,] 100588 --- [  restartedMain] o.s.c.s.a.z.ZipkinAutoConfiguration      : Check result of the [RestTemplateSender{http://localhost:9411/api/v2/spans}] contains an error [CheckResult{ok=false, error=org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request: [Empty JSON_V2 message]}]
2021-10-23 15:52:59.049  INFO [tools-security,,] 100588 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-10-23 15:52:59.303  INFO [tools-security,,] 100588 --- [  restartedMain] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: 0c731ade-806a-4a8e-83e1-e11044fe0f48


访问 http://localhost:9040/test , 会先跳到默认的登录页面,输入账号(user)密码(0c731ade-806a-4a8e-83e1-e11044fe0f48)即可进行访问到 /test 接口。

1.3 过滤器链的15个过滤器

1. org.springframework.security.web.context.SecurityContextPersistenceFilter

首当其冲的一个过滤器

SecurityContextPersistenceFilter主要是使用SecurityContextRepository在session中保存或更新一个securityContext,并将SecurityContext给以后的过滤器使用,来为后续Filter建立所需的上下文。

2. org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter

此过滤器用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager

3. org.springframework.security.web.header.HeaderWriterFilter

向请求的Header中添加相应的信息,可在http标签内部使用security:headers来控制

4. org.springframework.security.web.csrf.CsrfFilter

csrf又称跨域请求伪造,SpringSecurity会对所有post请求验证是否包含系统生成的CSRF的token信息,如果不包含,则报错。起到防止CSRF攻击的效果。

5. org.springframework.security.web.authentication.logout.LogoutFilter

匹配URL为/logout的请求,实现用户退出,清除认证信息。

6. org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

认证操作全靠这个过滤器,默认匹配URL为/login且必须为post请求。

7. org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter

如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面

8. org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter

由此过滤器可以生产一个默认的退出登录页面

9. org.springframework.security.web.authentication.www.BasicAuthenticationFilter

此过滤器会自动解析http请求中头部名字为Authentication,且以Basic 开头的头信息。

10. org.springframework.security.web.savedrequest.RequestCacheAwareFilter

通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存HttpServletRequest

11. org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter

针对ServletRequest进行了一次包装,使得request具有更加丰富的API

12. org.springframework.security.web.authentication.AnpnymousAuthenticationFilter

当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。

spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。

13. org.springframework.security.web.session.SessionManagementFilter

SecurityContextRepository限制同一用户开启多个回话的数量

14. org.springframework.security.web.access.ExceptionTranslationFilter

异常转移过滤器位于整个SpringSecurityFilterChain的后方,用来转换整个链路中出现的异常。

15. org.springframework.security.web.access.intercept.FilterSecurityinterceptor

获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权限

1.4 自定义用户认证 WebSecurityConfigurerAdapter

前面的做法是使用默认账号user和应用启动时自动生成的密码进行访问认证。但是,每个系统都应该有自己的用户体系,需要自定义用户的登录认证和接口的认证控制,这里就需要对WebSecurityConfiguration进行配置,主要关注三个方法:configure(HttpSecurity http),configure(AuthenticationManagerBuilder auth)和configure(WebSecurity)。

  • HttpSecurity 允许基于选择匹配在资源级配置基于网络的安全性,并声明任何其他网址需要成功验证。也就是对角色的权限——所能访问的路径做出限制。
  • AuthenticationManagerBuilder 用于通过允许AuthenticationProvider容易地添加来建立认证机制。配置的是认证信息 主要配置用户身份和角色信息也就是说用来记录账号,密码,角色信息。
  • WebSecurity 用于影响全局安全性(配置资源,设置调试模式,通过实现自定义防火墙定义拒绝请求)的配置设置。一般用于配置全局的某些通用事物,例如静态资源等。

创建WebSecurityConfiguration.java,继承WebSecurityConfigurerAdapter

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

}

1.4.1 configure(HttpSecurity http)

HttpSecurity 有重要的两个方法authorizeRequests()和requestMatchers()

authorizeRequests()

授权管理控制的方法

这个方法返回一个ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry对象。

authorizeRequests定义那些url需要被保护,那些不需要进行保护,通常出来在配置的第一行。

Security所有的权限控制都基于这个类进行控制。如:

http.authorizeRequests().anyRequest().authenticated()要求所有接口都需要进行权限认证,等同于http.authorizeRequests().antMatchers("/**").authenticated();

而http.authorizeRequests().antMatchers("/**").permitAll()这个配置则要求所有接口都不需要进行权限认证,等同于http.authorizeRequests().anyRequest().permitAll()

另外这两个代码中antMatchers方法则是配置匹配规则。即哪些接口需要进行权限认证或不需要进行权限认证。

anyRequest():所有接口

authenticated():需要进行权限认证

permitAll():不需要进行权限认证

antMatchers(String):匹配接口

requestMatchers()

取得RequestMatcherConfigurer对象并配置允许过滤的路由;如requestMatchers().anyRequest()等同于http.authorizeRequests().anyRequest().access(“permitAll”);

举例:

package com.lmc.security.conf;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @author lmc
 * @Description: TODO
 * @Create 2021-10-23 19:55
 * @version: 1.0
 */
@Configuration

public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/403", "/404", "/500").permitAll()    // 访问403/404/500时不需要认证
                .anyRequest().authenticated()   // 其他请求需要认证
                .and()
                .formLogin()    // 通过form进行登录
                .and()
                .csrf().disable();  // 关闭跨站请求伪造保护
    }
}

在以上配置中,我定义了请求为/403,/404和/500的三个接口不需要接受认证,直接放行;然后其他请求都需要认证;然后登录时要使用form方式,同时关闭跨站请求伪造保护。

在配置之前,我已经创建了403/404/500三个页面

@Controller
public class PageController {

    @RequestMapping("403")
    public String accessError() {
        return "403";
    }

    @RequestMapping("404")
    public String notFound() {
        return "404";
    }

    @RequestMapping("500")
    public String serverError() {
        return "500";
    }
    

}

此时,运行项目,访问 /404 接口能正常访问,然后访问 /test 接口时,会跳到Spring Security默认登录页面,输入账号密码后才放行。

1.4.2 configure(AuthenticationManagerBuilder auth)

使用自定义用户密码登录(内存方式)

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()   // 在内存存储用户信息
                .withUser("lmc").password("{noop}123") // 用户名为lmc,密码为123,{noop}表示密码不加密
                .roles("USER");  // 给用户lmc赋予USER角色
    }

spring security默认使用密码是需要加密的,即.password(String)里的密码已经是加密过的字符串,如果在前面加上{noop},就表示非加密的密码

.withUser("lmc").password("{noop}123")
 也可以写成
.passwordEncoder(new BCryptPasswordEncoder()).withUser("lmc").password(new BCryptPasswordEncoder().encode("123"))

注意,在security5.0之前默认加密方式是BC,所以只需要写成

.withUser("lmc").password(new BCryptPasswordEncoder().encode("123"))

就可以,但是5.0后加密方式要自己定义,所以需要使用 passwordEncoder() 方法来定义加密方式,否则会报出以下异常:

There is no PasswordEncoder mapped for the id "null"

1.4.3 configure(WebSecurity web)

配置静态资源,忽略认证

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/assets/**")
                .antMatchers("/403", "/404", "/500")
                .antMatchers("/login.html");
    }

其实,configure(WebSecurity web) 的内容基本都可以在 configure(HttpSecurity http) 中配置,例如以上的配置在configure(HttpSecurity http)中配置如下:

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/403", "/404", "/500").permitAll()    // 访问403/404/500时不需要认证
                .antMatchers("/assets/**").permitAll()
            	.antMatchers("/login.html").permitAll();
    }

不过,我们一般将类似登录页面,以及错误页面,静态资源等通用事物,到 configure(WebSecurity web) 中配置,configure(HttpSecurity http) 偏向基于网络的安全性,也就是对角色权限的控制,泾渭分明,塑造更好的可观性。

1.4.4 自定义表单登录

我们经常不会使用security的默认登录页面,需要自己创建开发自定义登录页,可以通过HttpSecurity的方法进行操作。

先自定义登录页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login Page</title>

    <!-- 引入样式 -->
    <link rel="stylesheet" href="/assets/element-ui/index.css">
</head>
<style>
    .el-header {
        background-color: #1B73E8;
        color: white;
        text-align: left;
        line-height: 60px;
    }
    .el-footer {
        background-color: #F5F5F5;
        color: black;
        text-align: center;
        line-height: 60px;
        font-size: 12px;
    }
    .el-main {
        background-color: white;
        color: #333;
        text-align: center;
        line-height: 160px;
    }

    body > .el-container {
        margin-bottom: 40px;
    }

    .el-row {
        margin-bottom: 20px;
    }
    .el-col {
        border-radius: 4px;
    }
    .bg-purple-dark {
        background: #99a9bf;
    }
    .bg-purple {
        background: #d3dce6;
    }
    .bg-purple-light {
        /*background: #e5e9f2;*/
    }
    .grid-content {
        border-radius: 4px;
        min-height: 36px;
        height: 800px;
    }
    .row-bg {
        padding: 10px 0;
        background-color: #f9fafc;
    }

    .el-card {
        padding: 80px;
        height: 638px;
    }
</style>
<body>
    <div id="app">
        <el-container>
            <el-header>lmc-tools</el-header>
            <el-main>
                <el-row :gutter="20">
                    <el-col :span="7"><div class="grid-content bg-purple"></div></el-col>
                    <el-col :span="10">
                        <div class="grid-content bg-purple-light">
                            <el-card class="box-card">
                                <el-form ref="form" action="/login" method="post" :model="form" :rules="rules" label-width="100px" name="loginform" class="login-form">
                                    <el-form-item label="用户名" prop="username">
                                        <el-input v-model="form.username" name="username" clearable></el-input>
                                    </el-form-item>
                                    <el-form-item label="密码" prop="password">
                                        <el-input v-model="form.password" name="password" show-password type="password" clearable></el-input>
                                    </el-form-item>
                                    <el-form-item>
                                        <el-button type="primary" @click="submitForm('form')">登录</el-button>
                                    </el-form-item>
                                </el-form>
                            </el-card>
                        </div>
                    </el-col>
                    <el-col :span="7"><div class="grid-content bg-purple"></div></el-col>
                </el-row>
            </el-main>
            <el-footer>Copyright © 2021 lmc</el-footer>
        </el-container>
    </div>
</body>
<!-- 引入vue -->
<script src="/assets/vue/vue.js"></script>
<!-- 引入Element-UI组件库 -->
<script src="/assets/element-ui/index.js"></script>
<script>
    new Vue({
        el: "#app",
        data: {
            form: {
                username: '',
                password: ''
            },
            rules: {
                username: [
                    {required: true, message: '用户名不能为空', trigger: 'blur'}
                ],
                password: [
                    {required: true, message: '密码不能为空', trigger: 'blur'}
                ]
            }
        },
        methods: {
            // 登录按钮触发事件
            submitForm: function(formName) {
                // 验证输入框是否为空
                this.$refs[formName].以上是关于Spring Security入门基础的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security入门Demo

Spring Security入门(2-2)Spring Security 的运行原理 2

Spring Security OAuth2入门级使用(示例)

Spring Security OAuth2入门级使用(示例)

Spring Security OAuth2入门级使用(示例)

Spring Security入门(3-7)Spring Security处理页面的ajax请求