Spring Security入门

Posted weixin_42412601

tags:

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

目录

1、SpringSecurity 框架简介

Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方
案。

关于安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 Spring Security 重要核心功能。

(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录

(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。

2、同款产品对比

SpringSecurity 特点:

  • 和 Spring 无缝整合
  • 全面的权限控制。
  • 专门为 Web 开发而设计。
  • 旧版本不能脱离 Web 环境使用。
  • 新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境。
  • 重量级。

Shiro特点:

  • 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
  • 通用性。
  • 好处:不局限于 Web 环境,可以脱离 Web 环境使用。
  • 缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制。

Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。

相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。因此,一般来说,常见的安全管理技术栈的组合是这样的:
• SSM + Shiro
• Spring Boot/Spring Cloud + Spring Security

3、入门例子

依赖:

springboot版本
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

写一个接口:

@RestController
@RequestMapping(value = "/test")
public class TestController 
    @GetMapping("/hello")
    public String hello()
        return "hello security";
    

启动工程:

访问接口:发现要登录才能访问接口,用户名是user,密码在上图,输入用户名密码。即可访问接口

4、SpringSecurity 基本原理

SpringSecurity 本质是一个过滤器链。
从启动是可以获取到过滤器链:

org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
org.springframework.security.web.context.SecurityContextPersistenceFilter 
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter 
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter 
org.springframework.security.web.session.SessionManagementFilter 
org.springframework.security.web.access.ExceptionTranslationFilter 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor

代码底层流程:重点看三个过滤器。

4.1、三个重点过滤器

4.1.1、FilterSecurityInterceptor过滤器

FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部。

  • super.beforeInvocation(fi) 表示查看之前的 filter 是否通过。
  • fi.getChain().doFilter(fi.getRequest(), fi.getResponse());表示真正的调用后台的服务。

4.1.2、ExceptionTranslationFilter过滤器

是个异常过滤器,用来处理在认证授权过程中抛出的异常

4.1.3、UsernamePasswordAuthenticationFilter过滤器

/loginPOST 请求做拦截,校验表单中用户名,密码。

UsernamePasswordAuthenticationFilter:在构造函数中默认对/login的请求并且是POST的进行拦截

AbstractAuthenticationProcessingFilter

  • 1处就是子类传进来的封装了post方式的/login
  • 2处就是判断当前请求能否匹配的上之前封装的/login
  • 3处,匹配不上就直接执行下一个过滤器,匹配上了,说明当前请求是post方式的/login,执行子类重写的attemptAuthentication方法,进行用户名和密码的认证

4.2、过滤器的加载过程

这些过滤器通过AbstractSecurityWebApplicationInitializer注入到servlet容器中:

  • DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
  • this.registerFilter(servletContext, true, filterName, springSecurityFilterChain);

在往servlet容器中注入过滤器时,会先或得servletContextspringSecurityFilterChainservletContext提供了addFilter方法将过滤器注入到容器中,现在就来看看DelegatingFilterProxy

4.2.1、DelegatingFilterProxy

它自己就是一个过滤器


DelegatingFilterProxy注册到servlet容器中后,在调用它的doFilter才会去构建其他过滤器

通过beanName->springSecurityFilterChain,获取一个FilterChainProxy过滤器,然后初始化这个过滤器

接下来就是执行FilterChainProxy过滤器的doFilter方法了

4.2.2、FilterChainProxy

它自己就是一个过滤器。
首先看一下它是怎么注入到Spring IOC容器中的,只有注入到Spring的容器中,DelegatingFilterProxy才能通过getBean的方式获取到它。

1、看到配置类WebSecurityConfiguration

进入build方法,再进入doBuild方法,然后在进入WebSecurityperformBuild:发现是直接new了一个FilterChainProxy实例,然后在配置类中将这个实例注入到Spring容器中的

2、别的过滤器是怎么注入的?

看上图new FilterChainProxy(securityFilterChains);入参是一个List<SecurityFilterChain>过滤器链集合,在构造这个过滤器链集合的时候,遍历了 List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<>();集合,这个集合里包含什么东西呢?HttpSecurity

for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) 
	securityFilterChains.add(securityFilterChainBuilder.build());

遍历的过程中会调用HttpSecuritybuild方法,我们来看看这个build方法:
进入发现是AbstractSecurityBuilderbuild方法,HttpSecurity是它的子类

来到AbstractConfiguredSecurityBuilderdoBuild:

进入HttpSecurityperformBuild

发现它是直接new了一个默认的过滤器链。
小结一下,也就是说HttpSecuritynew了一个默认的过滤器链给securityFilterChains集合,然后FilterChainProxy再根据这个集合去实例化。

3、看看这个默认的过滤器链里面都有些啥

过滤器链其实就是一个过滤器的集合。

现在主要看看这个filters集合怎么构造出来,然后再传进默认的过滤器链。

回到AbstractConfiguredSecurityBuilderdoBuild:在创建默认的过滤器链之前,其实是有一个初始化的过程。


实际上调用的是WebSecurityConfigurerAdapterinit方法:
初始化了一个HttpSecurity

创建了一个HttpSecurity,然后把默认的过滤器添加进去了

4、具体是怎么把过滤器添加上去的呢?
看看这段代码:

http
	.csrf().and()
	.addFilter(new WebAsyncManagerIntegrationFilter())
	.exceptionHandling().and()
	.headers().and()
	.sessionManagement().and()
	.securityContext().and()
	.requestCache().and()
	.anonymous().and()
	.servletApi().and()
	.apply(new DefaultLoginPageConfigurer<>()).and()
	.logout();

有些过滤器是直接addFilter添加进去的,这很好理解,就是往filters集合里添加过滤器。

	public HttpSecurity addFilter(Filter filter) 
		Class<? extends Filter> filterClass = filter.getClass();
		if (!comparator.isRegistered(filterClass)) 
			throw new IllegalArgumentException(
					"The Filter class "
							+ filterClass.getName()
							+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
		
		this.filters.add(filter);
		return this;
	

有的底层是调用getOrApply(new CsrfConfigurer<>(context))方法,然后new 一个xxxConfigurer,最后把这些xxxConfigurer,放到一个LinkHashMap中。
比如:csrf()
具体过程是:getOrApply->apply->add

LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();

把这些xxxConfigurer,放到LinkHashMap,干嘛呢?
再次回到AbstractConfiguredSecurityBuilderdoBuild

还有一个重要的configure方法。
可以看到configure()方法:进入该方法就会发现,其实就是调的各个xxxConfigurerconfigure方法

随便看一个xxxConfigurerconfigure方法:发现会把对应的过滤器加入到过滤器集合当中

到此为止,终于明白了过滤器链的构建过程!

4.2.3、总结过滤器链的构建过程

1、通过Servlet3.0提供的SCI机制,将DelegatingFilterProxy加入到servlet容器
2、当DelegatingFilterProxy调用它的doFilter方法时,会去从Spring容器中获取一个FilterChainProxy,并且执行FilterChainProxydoFilter方法
3、FilterChainProxy是在WebSecurityConfiguration中被注入到Spring容器中的,它是被WebSecurity调用build方法构建出来的
4、具体的构建过程是在performBuild方法:
首先,是创建了List<SecurityFilterChain> securityFilterChain 过滤器链集合,然后遍历securityFilterChainBuilders,执行SecurityBuilderbuild,创建出过滤器链,然后把这个过滤器链,添加到过滤器链集合,有了过滤器链集合,就能创建FilterChainProxy
5、在遍历securityFilterChainBuilders时,securityFilterChainBuilders是包含HttpSecurity的,因此,会执行到HttpSecuritybuild方法【它的build方法就是AbstractSecurityBuilderbuild方法】,具体构建过滤器的过程是在dobuild方法

  • 首先是执行初始化方法,遍历SecurityConfigurer集合,然后执行WebSecurityConfigurerAdapterinit初始化方法,直接new HttpSecurity,添加一些过滤器到filter集合,以及添加一些xxxconfigurerMap中,然后执行WebSecurityconfigure方法
  • 接下来就是执行configure()方法,就是从Map中获取xxxconfigurer,并执行它们的configure,这些configure方法,会把对应的过滤器添加到filters集合
  • 最后就是直接调HttpSecurityperformBuild方法,直接利用过滤器集合new DefaultSecurityFilterChain(requestMatcher, filters)创建一个默认的过滤器链

4.3、两个重要的接口

4.3.1、UserDetailsService

当什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。

如果需要自定义认证逻辑时,只需要实现UserDetailsService接口即可。接口定义如下:

public interface UserDetailsService 
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

返回值 UserDetails,这个类是系统默认的用户“主体”,是安全框架提供的User对象

看看实现类User

  • 方法参数 username:表示用户名。此值是客户端表单传递过来的数据。默认情况下必须叫 username,否则无
    法接收。

介绍完了UserDetailsService以及UserDetails。现在来看看自定义认证逻辑,应该怎么做?

  • UsernamePasswordAuthenticationFilter:用于接收Post请求传过来的用户名和密码,然后进行认证。
    • 我们只需要自定义一个过滤器继承它,认证的过程无非成功或者失败,只需要重写successfulAuthentication方法和unsuccessfulAuthentication方法
    • attemptAuthentication方法,只是获取用户名和密码,但是查用户进行认证的过程不在这里面
  • UserDetailsService:创建实现类,重写loadUserByUsername方法。查询数据库用户名密码的过程在UserDetailsService接口的loadUserByUsername方法里写

4.3.2、PasswordEncoder

数据加密接口,用于返回的User对象里面密码加密

public interface PasswordEncoder 
	// 表示把参数按照特定的解析规则进行解析
	String encode(CharSequence rawPassword);
	// 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹
	配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个
	参数表示存储的密码。
	boolean matches(CharSequence rawPassword, String encodedPassword);
	// 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回
	false。默认返回 false。
	default boolean upgradeEncoding(String encodedPassword) 
	return false; 

接口实现类:

BCryptPasswordEncoderSpring Security官方推荐的密码解析器,平时多使用这个解析器。
BCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单向加密。可以通过 strength 控制加密强度,默认 10

查用方法演示:

@Test
public void test01()
// 创建密码解析器
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// 对密码进行加密
String atguigu = bCryptPasswordEncoder.encode("atguigu");
// 打印加密之后的数据
System.out.println("加密之后数据:\\t"+atguigu);
//判断原字符加密后和加密之前是否匹配
boolean result = bCryptPasswordEncoder.matches("atguigu", atguigu);
// 打印比较结果
System.out.println("比较结果:\\t"+result);

5、SpringSecurity Web 权限方案

SpringSecurityweb项目中如何做认证和授权?
回顾一下什么是认证,什么是授权。
认证就是通过用户名和密码登录的过程,这个过程就叫认证。
授权就是系统判断用户是否有权限去做某些事情。

之前写入门例子的时候,使用的是默认的用户名和密码来实现认证的过程。那么怎么设置用户名和密码呢?

5.1、设置用户名和密码

方式一、通过配置文件

在入门例子中,添加如下配置,即可,改变用户名和密码

spring.security.user.name=lzh
spring.security.user.password=123456

方式二、通过配置类

把之前配置文件中配置的用户名和密码去了,加上如下的配置类,即可

@Slf4j
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        String userName="zhangsan";
        String pasword="123456";
        String role="admin";
        // 创建密码解析器
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        // 对密码进行加密
        pasword= bCryptPasswordEncoder.encode(pasword);
        log.info("用户名:"+userName);
        log.info("密码:"+pasword);
        auth.inMemoryAuthentication().withUser(userName).password(pasword).roles(role);
    
    @Bean
    PasswordEncoder bCryptPasswordEncoder()
        return new BCryptPasswordEncoder();
    

方式三、自定义编写实现类

一般,开发中使用这种。
实现UserDetailsService

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User("zhangsan",new BCryptPasswordEncoder().encode("123"),auths);
    

配置类:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    
    @Bean
    PasswordEncoder bCryptPasswordEncoder()
        return new BCryptPasswordEncoder();
    

5.2、实现数据库认证来完成用户登录

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService 
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        //调用userMapper方法查询数据库
        QueryWrapper<UsersDO> objectQueryWrapper = new QueryWrapper<>();
        objectQueryWrapper.eq("username",username);
        UsersDO usersDO = userMapper.selectOne(objectQueryWrapper);

        if (usersDO==null)
            throw new UsernameNotFoundException("用户不存在");
        

        List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User(usersDO.getUsername(),new BCryptPasswordEncoder().encode(usersDO.getPassword()),auths);
    

不用去校验密码,spring security会自动帮我们校验,只需要建一张user表,存用户名和密码就行。

5.3、自定义用户登录界面

@Slf4j
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    
    @Bean
    PasswordEncoder bCryptPasswordEncoder()
        return new BCryptPasswordEncoder();
    

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception 
        httpSecurity.formLogin()//自定义自己编写的登录页面
                .loginPage("/login.html")//登录页面设置
                .loginProcessingUrl("/user/login")//登录访问路径
                .defaultSuccessUrl("/test/index").permitAll()//登录成功之后,跳转路径
                .and().authorizeRequests()
                .antMatchers("/","/test/hello","/user/login").permitAll()//设置哪些路径可以直接访问,不需要认证
                .anyRequest().authenticated()
                .and().csrf().disable();//关闭csf防护
    

5.4、基于角色或权限进行访问控制

简答说,访问某些接口,需要某些权限。

5.4.1、hasAuthority 方法

该方法只能针对某一个角色权限。
配置类:
UserDetailsService

如果把当前用户的权限随便设置为xxx,那么访问/test/index,就会出现:

5.4.2、hasAnyAuthority 方法

该方法可以针对多个角色权限。

5.4.3、hasRole方法

如果用户具备给定角色就允许访问,否则出现 403。
如果当前主体具有指定的角色,则返回 true。

给用户添加角色:

需要前缀ROLE_,因为hasRole底层默认给加了

5.4.4、hasAnyRole方法

表示用户具备任何一个条件都可以访问。
给用户添加角色:

修改配置文件:

5.4.5、自定义403页面

在用户没有角色权限,访问某个接口时,会返回403页面,这个页面如何自定义呢?

5.5、常用注解使用

使用注解的目的,简化开发。

5.5.1、@Secured

用户具有某个角色,才可以访问该注解标注的方法。

1、启动类:

##开启Secured注解
@EnableGlobalMethodSecurity(securedEnabled = true)

2、在controller的方法上面使用注解,设置角色

   @GetMapping("/update")
    //用户具有角色ROLE_sale或者ROLE_manager,才能访问
    @Secured("ROLE_sale","ROLE_manager")
    public String update()
        return "hello update";
    

3、UserDetailsService中设置用户的角色

5.5.2、@PreAuthorize

在进入方法前先进行验证,看你有没有权限访问

1、启动类:

##开启prePostEnabled 注解
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)

2、在controller方法中加上注解

    @GetMapping("/update")
    @PreAuthorize("hasAnyAuthority('admin')")
    public String update()
        return "hello update";
    

5.5.3、@PostAuthorize

在方法执行后再进行权限验证,适合验证带有返回值的权限。
先开启注解功能:

@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequestMapping("/testPostAuthorize")
@ResponseBody
@PostAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize()
 System.out.println("test--PostAuthorize");
return "PostAuthorize"; 

5.5.4、@PostFilter

对方法返回数据进行过滤。

表达式中的 filterObject 引用的是方法返回值 List 中的某一个元素。
过滤出集合中usernameadmin1的数据

    @RequestMapping("getAll")
    @PreAuthorize("hasRole('ROLE_admin')")
    @PostFilter("filterObject.username == 'admin1'")
    @ResponseBody
    public List<UsersDO> getAllUser()
        ArrayList<UsersDO> list = new ArrayList<>();
        list.add(new UsersDO(1,"admin1","6666"));
        list.add(new UsersDO(2,"admin2","888"));
        return list;
    

5.5.4、@PreFilter

对方法传入的参数进行过滤。

@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_管理员')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo> 
list)
 list.forEach(t-> 
 System.out.println(t.getId()+"\\t"+t.getUsername());
 );
return list;

6、用户注销

配置:

7、基于数据库的自动登录

一次登录后,一段时间内免登录。

实现技术:

  • cookie技术
  • 安全框架机制实现自动登录

7.1、原理

原理:

通过设置cookie的有效期,就能控制免登录的时长了。


UsernamePasswordAuthenticationFilter过滤器:重写attemptAuthentication进行认证,认证后会在父类中的dofilter方法中,调用认证成功后的处理方法successfulAuthentication

successfulAuthentication

loginSuccess->onLoginSuccess:生成token并把token放到cookie

将token写入浏览器之后,还会写到数据库,这里的createNewToken和上面的不是同一个:

7.2、具体实现

建表,JdbcTokenRepositoryImpl也有建表语句

CREATE TABLE `persistent_logins` (
 `username` varchar(64) NOT NULL,
 `series` varchar(64) NOT NULL,
 `token` varchar(64) NOT NULL,
 `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE 
CURRENT_TIMESTAMP,
 PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

配置类:
配置操作数据库的对象

    @Autowired
    private DataSource dataSource;
    @Bean
    public PersistentTokenRepository persistentTokenRepository()
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //也可以设置,启动的时候,自动创建表。
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    

配置自动登录:

测试:

此处:复选框的name 属性值必须位 remember-me.不能改为其他值

登录,不选自动登录,然后能正常访问接口,把浏览器关了,再次访问接口,发现又需要登录了

再次登录,把自动登录选上,

发现了1分钟的cookies,关了浏览器,重新开,在访问接口,发现在一分钟内,不需要再次登录

8、CSRF功能

CSRF:跨站请求伪造,一种恶意的攻击。
简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作。
比如:我在浏览器中打开网站A,并进行了登录认证,然后在同一个浏览器中打开网站B,因为网站B与网站A在同一个浏览器中,因此网站B能得到当前浏览器中的所有cookie信息,因此,网站B能获取网站A登录认证的cookie,通过获取到cookei,网站B可能能够调用网站A的一些接口,这是不安全的

从 Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用程序,Spring Security CSRF 会针对 PATCH,POST,PUT 和 DELETE 方法进行防护。注意,不包括get请求

在登录页面添加一个隐藏域:

<input type="hidden" th:if="$_csrf!=null" th:value="$_csrf.token" name="_csrf"

以上是关于Spring Security入门的主要内容,如果未能解决你的问题,请参考以下文章

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

Spring Security入门基础

Spring Security入门

Spring Security入门Demo

Spring Security---ONE

Spring Security4实战与原理分析视频课程( 扩展+自定义)