SpringSercurity
1. 概述
1. 简介
Spring Security 是一个专注于为 Java 应用程序提供身份验证和授权的框架。与所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以轻松扩展以满足自定义要求。
特点:
- 对身份验证和授权的全面且可扩展的支持
- 防止会话固定、点击劫持、跨站点请求伪造等攻击
- Servlet API 集成
- 与 Spring Web MVC 的可选集成
2. 安全方面两大核心
用户认证(Authentication)
验证某个用户是否为系统中的合法主体
授权(Authorization)
用户是否有权限去做某些事情
3. 同款产品对比
SpringSercurity
特点:
- 和Spring无缝整合。
- 全面的权限控制。 专门为Web开发而设计。
- 旧版本不能脱离Web环境使用。
- 新版本对整个框架进行了分层抽取,分成了核心模块和Web模块。单独引入核心模块就可以脱离Web环境。
- 重量级。
Shiro
Apache旗下的轻量级权限控制框架。
特点:
- 轻量级。Shiro主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
- 通用性。
- 好处:不局限于Web环境,可以脱离Web环境使用。
- 缺陷:在Web环境下一些特定的需求需要手动编写代码定制。
推荐组合:
SSM + Shiro
Springboot/SpringCloud + SpringSercurity
4. 模块
待补充……
2. 入门案例
1. 映入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. 编写controller
package com.zhan.demo01.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello Spring Security";
}
}
3. 启动测试
# 输入用户名 user
# 输入随机密码 08b46078-0cd1-42b6-b26b-780a3c8a89d5
# 点击登录
这里说明了一个问题;那就是访问controller的请求被springsecurity拦截了!接下来我们来看看源码,springsecuriity如何拦截了我们的请求:
3. Springsecurity基本原理
本质:Springsecurity本质上是一个过滤器链,有很多过滤器链
常见过滤器器
- org.springframework.security.web.context.SecurityContextPersistenceFilter
首当其冲的一个过滤器,作用之重要,自不必多言。
SecurityContextPersistenceFilter主要是使用SecurityContextRepository在session中保存或更新一个
SecurityContext,并将SecurityContext给以后的过滤器使用,来为后续filter建立所需的上下文。
SecurityContext中存储了当前用户的认证以及权限信息 - org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
此过滤器用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager - org.springframework.security.web.header.HeaderWriterFilter
向请求的Header中添加相应的信息,可在http标签内部使用security:headers来控制 - org.springframework.security.web.csrf.CsrfFilter
csrf又称跨域请求伪造,SpringSecurity会对所有post请求验证是否包含系统生成的csrf的token信息,
如果不包含,则报错。起到防止csrf攻击的效果。 - org.springframework.security.web.authentication.logout.LogoutFilter
匹配 URL为/logout的请求,实现用户退出,清除认证信息。 - org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
认证操作全靠这个过滤器,默认匹配URL为/login且必须为POST请求。 - org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。 - org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
由此过滤器可以生产一个默认的退出登录页面 - org.springframework.security.web.authentication.www.BasicAuthenticationFilter
此过滤器会自动解析HTTP请求中头部名字为Authentication,且以Basic开头的头信息。 - org.springframework.security.web.savedrequest.RequestCacheAwareFilter
通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存HttpServletRequest - org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
针对ServletRequest进行了一次包装,使得request具有更加丰富的API - org.springframework.security.web.authentication.AnonymousAuthenticationFilter
当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。
spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。 - org.springframework.security.web.session.SessionManagementFilter
SecurityContextRepository限制同一用户开启多个会话的数量 - org.springframework.security.web.access.ExceptionTranslationFilter
异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常 - org.springframework.security.web.access.intercept.FilterSecurityInterceptor
获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权限。
4. 用户认证
1. 用户密码配置
1.1 配置文件配置
# springsecurity配置
spring:
security:
user:
name: salvation
password: 12345
1.2 配置类配置
package com.zhan.demo01.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// super.configure(auth);
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("12345");//密码加密
auth.inMemoryAuthentication().withUser("salvation").password(password).roles("admin");
}
/**
* 注入PasswordEncoder接口实现类,因为springSecurity初始化是需要PasswordEncoder接口实现类来解析密码
* @return {@link PasswordEncoder}
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
缺点:用户名密码都写死了,现实中需要从数据库中获取用户密码权限
1.3 自定义编写实现类(MyUserDetailsService)
原理:编写自定义的实现类UserDetailsService,通过这个类来解析从数据库获取的用户权限相关信息(用户名,用户密码,用户权限),最后在自定义的SpringSecurityConfig配置类中被加载进去
- 编写配置类
package com.zhan.demo01.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SpringSecurityConfigDemo02 extends WebSecurityConfigurerAdapter {
@Autowired
// @Qualifier("userDetailsService")
private UserDetailsService userDetailsService; //注入自定义的UserDetailsService
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// super.configure(auth);
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);//自定义userDetailsService解析权限信息
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
- 自定义MyUserDetailsService(实现UserDetailsService接口)
源码解析
UserDetailsService接口类
- 接口中只有一个方法
UserDetails loadUserByUsername(String var1)
,通过varl(指的是用户名字符串)返回一个UserDetails 类
UserDetails类
- 返回UserDetails的实现类(org\springframework\security\core\userdetails\User.class)对象供SpringSecurityConfig解析加载
自定义UserDetailsService
package com.zhan.demo01.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zhan.demo01.bean.User;
import com.zhan.demo01.dao.UserMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.util.List;
@Service(value = "userDetailService")
public class MyUserDetailService implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(MyUserDetailService.class);
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询包装器
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
User user = userMapper.selectOne(wrapper);
logger.info("username:{},user:{}", username, user.toString());
if(ObjectUtils.isEmpty(user)){
throw new UsernameNotFoundException("用户名或密码错误");
}
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getAuth()); //AuthorityUtils工具类构造权限集合
//由于同名的关系
return new org.springframework.security.core.userdetails.User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), auths);
}
}
自定义SpringSecurityConfig配置类
package com.zhan.demo01.config;
import com.zhan.demo01.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SpringSecurityConfigDemo02 extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// super.configure(auth);
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();;
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
2. 自定义用户登录页面
配置类
package com.zhan.demo01.config;
import com.zhan.demo01.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SpringSecurityConfigDemo02 extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// super.configure(auth);
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();;
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
http.formLogin() //表单登录
.loginPage("/login") // 设置登录页面
.loginProcessingUrl("/login")//设置自定义登录逻辑
//.defaultSuccessUrl("/success.html",true).permitAll() // 设置登录成功后跳转的页面
.successForwardUrl("/success") //设置登陆成功跳转页面
.failureForwardUrl("/error") //设置登陆失败跳转页面
.and().authorizeRequests() //对匹配器中的请求进行授权,也就是这些路径不需要权限就能访问
.antMatchers("/noAuth", "/login","/error").permitAll()
.anyRequest().authenticated()//任何请求都需要认证
.and().csrf().disable(); //关闭csrf支持 ,不关闭表单提交会被拦截
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean("userDetailsService")
public UserDetailsService userDetailsService(){
return new MyUserDetailService();
}
}
HelloController
为了跳转方便,这里引入了thymeleaf模板引擎
package com.zhan.demo01.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
public class HelloController {
@RequestMapping(value = "/login", method = {RequestMethod.GET, RequestMethod.POST})
public String toLogin(){
return "/login";
}
@RequestMapping(value = "/index", method = {RequestMethod.GET, RequestMethod.POST})
public String toIndex(){
return "/index";
}
@GetMapping("/noAuth")
@ResponseBody
public String noAuth(){
return "不需授权页面";
}
@RequestMapping(value = "/error", method = {RequestMethod.GET, RequestMethod.POST})
public String toError(){
return "/error";
}
@RequestMapping(value = "/success", method = {RequestMethod.GET, RequestMethod.POST})
public String toSuccess(){
return "/success";
}
}
静态页面
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<form method="post" action="/login">
用户名: <input name="username" type="text" placeholder="请输入用户名">
密码: <input name="password" type="text" placeholder="请输入密码">
<input type="submit" value="提交">
</form>
</body>
</html>
success.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录成功页面</title>
</head>
<body>
<h1>登录成功了!</h1>
</body>
</html>
测试
- 访问不需要认证的页面
- 访问需要授权的页面,会跳转到login.html页面
- 输入密码登录后跳转到指定的success.html页面
-
登录失败后跳转失败页面
5. 用户授权
1. 基于权限访问控制
1. hasAuthority()
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
http.formLogin() //表单登录
.loginPage("/login") // 设置登录页面
// .loginProcessingUrl("/login")//设置自定义登录逻辑
//.defaultSuccessUrl("/success.html",true).permitAll() // 设置登录成功后跳转的页面
.successForwardUrl("/success")
.failureForwardUrl("/error")
.and().authorizeRequests() //对匹配器中的请求进行授权,也就是这些路径不需要权限就能访问
.antMatchers("/noAuth", "/login","/error").permitAll()
.antMatchers("/index").hasAuthority("admin")
.anyRequest().authenticated()//任何请求都需要认证
.and().csrf().disable(); //关闭csrf支持 ,不关闭表单提交会被拦截
}
- 设置访问的url所需要的权限,如果访问用户所拥有的权限与之不对应,那么会报403访问错误,如果有error.html页面的话,会自动跳转到error页面
2. hasAnyAuthority(“user”,“admin”)
.antMatchers("/index").hasAnyAuthority("user","admin")
- 可以配置多个权限,访问用户如果拥有其中一个,即可访问
2. 基于角色权限控制
因此如果想要使用基于角色权限控制的话,在**进行角色认证后授权的权限自字符串中要加上前缀“ROLE_”**
1. hasRole()
.antMatchers("/index").hasRole("user") // 配置单个角色
2. hasAnyRole()
.antMatchers("/index").hasAnyRole("user", "admin") //配置多个角色
6. 注解使用
1. @PreAuthorize
@PreAuthorize适合进入方法之前验证授权。 @PreAuthorize可以兼顾,角色/登录用户权限,参数传递给方法等等。
支持El表达式: @PreAuthorize(“hasRole(‘ADMIN’) AND hasRole(‘DBA’)”)
2. @PostAuthorize
@PostAuthorize 虽然不经常使用,检查授权方法之后才被执行,所以它适合用在对返回的值作验证授权。
支持EL表达式: @PostAuthorize(“hasRole(‘ADMIN’) AND hasRole(‘DBA’)”)
3. @Secured
@Secured注释是用来定义业务方法的安全性配置属性列表。您可以使用@Secured在方法上指定安全性要求[角色/权限等],只有对应角色/权限的用户才可以调用这些方法。如果有人试图调用一个方法,但是不拥有所需的角色/权限,那会将会拒绝访问将引发异常。
不支持EL表达式
7. 自动登录(记住我)
1. 流程
2. 源码解析
待补充……
3. 案例测试
1. 数据库配置
- 建表
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null
)ENGINE = InnoDB DEFAULT CHARSET = utf8;
- 也可以通过springSecurity自动创建
- 配置数据源
datasource: # 数据源配置
driver-class-name: com.mysql.cj.jdbc.Driver # 数据驱动
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai # 数据库url地址
username: root # 用户名
password: root # 密码
2. 配置类(在上面的基础上加入记住我配置)
//注入数据源
@Autowired
private DataSource dataSource;
//配置持久令牌存储库
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//自动创建表
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
//记住我设置
http.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60) //设置cookie过期时间
.userDetailsService(userDetailsService);
3. 前端表单加上remember-me
的checkbox
<form method="post" action="/login">
用户名: <input name="username" type="text" placeholder="请输入用户名"><br>
密码: <input name="password" type="text" placeholder="请输入密码"><br>
记住我: <input type="checkbox" name="remember-me">
<input type="submit" value="提交">
</form>
4. 登录认证后,访问页面
可以看到此后在cookie过期的时间内,访问页面的请求头都会带上一个remember-me的cookie,springsecurity会根据传过来的cookie去查询对应的数据库表,如果查得到token且没有过期,那么将会自动登录
8. 注销登录
配置
//在配置类的configure方法中加入
http.logout()//用户注销设置
.logoutUrl("/logout") //设置注销触发的url
.logoutSuccessUrl("/login");//注销成功后跳转的页面
9. CSRF功能
1. CSRF是什么?
CSRF(Cross Site Request Forgery, 跨站域请求伪造)是一种网络的攻击方式,它在 2007 年曾被列为互联网 20 大安全隐患之一,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户
的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范
,所以被认为比XSS更具危险性
。
2. 如何防御
源码
public final class CsrfFilter extends OncePerRequestFilter {
public static final RequestMatcher DEFAULT_CSRF_MATCHER = new
CsrfFilter.DefaultRequiresCsrfMatcher();
private final Log logger = LogFactory.getLog(this.getClass());
private final CsrfTokenRepository tokenRepository;
private RequestMatcher requireCsrfProtectionMatcher;
private AccessDeniedHandler accessDeniedHandler;
public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
this.requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
this.accessDeniedHandler = new AccessDeniedHandlerImpl();
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
this.tokenRepository = csrfTokenRepository;
}
//通过这里可以看出SpringSecurity的csrf机制把请求方式分成两种类来处理
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
boolean missingToken = csrfToken == null;
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
//"GET", "HEAD", "TRACE", "OPTIONS"四类请求可以直接通过
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
} else {
//除去上面四类,包括POST都要被验证携带token才能通过
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Invalid CSRF token found for " +
UrlUtils.buildFullRequestUrl(request));
}
if (missingToken) {
this.accessDeniedHandler.handle(request, response, new
MissingCsrfTokenException(actualToken));
} else {
this.accessDeniedHandler.handle(request, response, new
InvalidCsrfTokenException(csrfToken, actualToken));
}
} else {
filterChain.doFilter(request, response);
}
}
}
public void setRequireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) {
Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be
null");
this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
}
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null");
this.accessDeniedHandler = accessDeniedHandler;
}
private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
private final HashSet<String> allowedMethods;
private DefaultRequiresCsrfMatcher() {
this.allowedMethods = new HashSet(Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));
}
public boolean matches(HttpServletRequest request) {
return !this.allowedMethods.contains(request.getMethod());
}
}
}
3. 禁用CSRF
http
//关闭打开的csrf保护
.csrf().disable();
4. Csrf Token
用户登录时,系统发放一个CsrfToken值,用户携带该CsrfToken值与用户名、密码等参数完成登录。系统记录该会话的 CsrfToken 值,之后在用户的任何请求中,都必须带上该CsrfToken值,并由系统进行校验。这种方法需要与前端配合,包括存储CsrfToken值,以及在任何请求中(包括表单和Ajax)携带CsrfToken值。
- 优点:安全性相较于HTTP Referer提高很多,如果都是XMLHttpRequest,则可以统一添加CsrfToken值;
- 缺点:存在大量的表单和a标签,就会变得非常烦琐.
5. 使用CSRF
HttpSessionCsrfTokenRepository
在默认情况下,Spring Security加载的是一个HttpSessionCsrfTokenRepository
HttpSessionCsrfTokenRepository 将 CsrfToken 值存储在 HttpSession 中,并指定前端把CsrfToken 值放在名为“csrf”的请求参数或名为“X-CSRF-TOKEN”的请求头字段里(可以调用相应的设置方法来重新设定)。校验时,通过对比HttpSession内存储的CsrfToken值与前端携带的CsrfToken值是否一致,便能断定本次请求是否为CSRF攻击
<input type='hidden' th:name='${_csrf.parameterName}' th:value='${_csrf.token}'>
注:这里单纯这样的html页面是无法获取请求域的值的,需要配合模板引擎(thymeleaf)或其他前端框架