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过滤器
对/login
的 POST
请求做拦截,校验表单中用户名,密码。
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
容器中注入过滤器时,会先或得servletContext
和springSecurityFilterChain
,servletContext
提供了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
方法,然后在进入WebSecurity
的performBuild
:发现是直接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());
遍历的过程中会调用HttpSecurity
的build
方法,我们来看看这个build
方法:
进入发现是AbstractSecurityBuilder
的build
方法,HttpSecurity
是它的子类
来到AbstractConfiguredSecurityBuilder
的doBuild
:
进入HttpSecurity
的performBuild
:
发现它是直接new
了一个默认的过滤器链。
小结一下,也就是说HttpSecurity
,new
了一个默认的过滤器链给securityFilterChains
集合,然后FilterChainProxy
再根据这个集合去实例化。
3、看看这个默认的过滤器链里面都有些啥
过滤器链其实就是一个过滤器的集合。
现在主要看看这个filters
集合怎么构造出来,然后再传进默认的过滤器链。
回到AbstractConfiguredSecurityBuilder
的doBuild
:在创建默认的过滤器链之前,其实是有一个初始化的过程。
实际上调用的是WebSecurityConfigurerAdapter
的init
方法:
初始化了一个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
,干嘛呢?
再次回到AbstractConfiguredSecurityBuilder
的doBuild
:
还有一个重要的configure
方法。
可以看到configure
()方法:进入该方法就会发现,其实就是调的各个xxxConfigurer
的configure
方法
随便看一个xxxConfigurer
的configure
方法:发现会把对应的过滤器加入到过滤器集合当中
到此为止,终于明白了过滤器链的构建过程!
4.2.3、总结过滤器链的构建过程
1、通过Servlet3.0
提供的SCI
机制,将DelegatingFilterProxy
加入到servlet
容器
2、当DelegatingFilterProxy
调用它的doFilter
方法时,会去从Spring
容器中获取一个FilterChainProxy
,并且执行FilterChainProxy
的doFilter
方法
3、FilterChainProxy
是在WebSecurityConfiguration
中被注入到Spring
容器中的,它是被WebSecurity
调用build
方法构建出来的
4、具体的构建过程是在performBuild
方法:
首先,是创建了List<SecurityFilterChain> securityFilterChain
过滤器链集合,然后遍历securityFilterChainBuilders
,执行SecurityBuilder
的build
,创建出过滤器链,然后把这个过滤器链,添加到过滤器链集合,有了过滤器链集合,就能创建FilterChainProxy
了
5、在遍历securityFilterChainBuilders
时,securityFilterChainBuilders
是包含HttpSecurity
的,因此,会执行到HttpSecurity
的build
方法【它的build
方法就是AbstractSecurityBuilder
的build
方法】,具体构建过滤器的过程是在dobuild
方法
- 首先是执行初始化方法,遍历
SecurityConfigurer
集合,然后执行WebSecurityConfigurerAdapter
的init
初始化方法,直接new HttpSecurity
,添加一些过滤器到filter
集合,以及添加一些xxxconfigurer
到Map
中,然后执行WebSecurity
的configure
方法 - 接下来就是执行
configure
()方法,就是从Map
中获取xxxconfigurer
,并执行它们的configure
,这些configure
方法,会把对应的过滤器添加到filters集合 - 最后就是直接调
HttpSecurity
的performBuild
方法,直接利用过滤器集合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;
接口实现类:
BCryptPasswordEncoder
是Spring 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 权限方案
SpringSecurity
在web
项目中如何做认证和授权?
回顾一下什么是认证,什么是授权。
认证就是通过用户名和密码登录的过程,这个过程就叫认证。
授权就是系统判断用户是否有权限去做某些事情。
之前写入门例子的时候,使用的是默认的用户名和密码来实现认证的过程。那么怎么设置用户名和密码呢?
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
中的某一个元素。
过滤出集合中username
含admin1
的数据
@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入门的主要内容,如果未能解决你的问题,请参考以下文章