关于csrf在第八集中用postman访问 post请求时 已经提到需要 禁用 csrf 才可以访问
http.csrf().disable();
那么到底什么是 csrf 呢?
CSRF(跨站请求伪造)是一种Web安全漏洞,攻击者诱使用户在登录受信任网站的情况下,执行非预期的操作。这种攻击利用了Web应用对用户浏览器的信任。
csrf攻击原理
- 用户登录:用户登录到受信任的网站A
- 保持会话:网站A在用户浏览器中设置认证cookie
- 恶意诱导:用户访问恶意网站B,网站B包含针对网站A的请求
- 自动提交:浏览器自动附带网站A的cookie发送请求
- 非预期操作:网站A认为这是用户的合法请求并执行
csrf防御机制
服务端生成一个随机 Token(如 abcd1234),存储在 Session 或 Cookie 中
客户端在提交请求时需携带该 Token(通过表单字段或请求头)
服务端校验 Token 是否匹配
关于security中的csrf是默认开启的,是利用一个过滤器 CsrfFilter 实现的。在这个过滤器中使用CsrfTokenRepository的实现类 来 生成,存储 csrfToken。security提供了CsrfTokenRepository的三种实现类
- HttpSessionCsrfTokenRepository(默认) CSRF Token 会被存储在 HTTP Session 中
- CookieCsrfTokenRepository CSRF Token 会存入 Cookie(默认名称为 XSRF-TOKEN)
特点: 适合前后端分离架构 需配合 withHttpOnlyFalse() 允许前端 JavaScript 读取
http.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
- LazyCsrfTokenRepository 包装另一个 TokenRepository,仅在需要时生成/存储 Token
下面结合security的 CsrfFilter 过滤器的源码 ,进行分析其大概实现原理
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//将 HttpServletResponse 对象存入请求属性,供后续流程使用
request.setAttribute(HttpServletResponse.class.getName(), response);
//从 TokenRepository(默认 HttpSessionCsrfTokenRepository)中读取已存储的 Token
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
boolean missingToken = csrfToken == null;
//若 Token 不存在
if (missingToken) {
//生成新 Token
csrfToken = this.tokenRepository.generateToken(request);
//保存 Token 到存储库(如 Session 或 Cookie)
this.tokenRepository.saveToken(csrfToken, request, response);
}
/*
将 Token 存入请求属性,供视图层(如 JSP、Thymeleaf)渲染时使用
默认规则
需要防护的请求:POST、PUT、PATCH、DELETE 等修改数据的请求。
无需防护的请求:GET、HEAD、TRACE、OPTIONS(安全方法)。
若无需防护:直接放行请求(doFilter)。
*/
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
//通过 requireCsrfProtectionMatcher 判断当前请求是否需要 CSRF 校验
if (!this.requireCsrfProtectionMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not protect against CSRF since request did not match " + this.requireCsrfProtectionMatcher);
}
filterChain.doFilter(request, response);
} else {
//获取客户端 Token 优先从请求头(默认 X-CSRF-TOKEN)读取
String actualToken = request.getHeader(csrfToken.getHeaderName());
//若头不存在,从请求参数(默认 _csrf)读取(适用于表单提交)
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
/*
比对 Token
使用 equalsConstantTime(防时序攻击的安全比对)比较服务端 Token 和客户端 Token
若不匹配,抛出异常
InvalidCsrfTokenException(Token 不合法)
MissingCsrfTokenException(Token 缺失)
通过 accessDeniedHandler 返回 403 错误。
*/
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
this.logger.debug(LogMessage.of(() -> {
return "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request);
}));
AccessDeniedException exception = !missingToken ? new InvalidCsrfTokenException(csrfToken, actualToken) : new MissingCsrfTokenException(actualToken);
this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
} else {
filterChain.doFilter(request, response);
}
}
}
完整流程总结
-
首次请求:
生成 Token → 存入 Session → 返回给客户端(通过表单或头)。
-
后续修改数据的请求:
客户端需携带 Token(头或参数)→ 服务端校验 → 通过则放行,否则拒绝。
-
安全方法(如 GET):
跳过校验直接放行。
45万+

被折叠的 条评论
为什么被折叠?



