Spring Boot整合Spring Security(三)csrf
阅前提示一、csrf 与 xss二、CsrfFilter浅析三、获取csrfToken四、不使用模板引擎获取csrfToken
阅前提示
此文章基于Spring Security 6.0
一、csrf 与 xss
关于csrf与xss的介绍就去网上找一下吧,这篇写的不错。在这里就不多做介绍了 关于csrf与xss与验证码的介绍 在之前的自定义登录界面篇章中,关闭了Spring Security的csrf配置,在这个篇章中就介绍下csrf的使用。 关于csrf的内容本来是不想写的。因为现在的应用开发大多都实现了前后端分离模式,并禁用了cookie与session,使用token进行身份认证。而关于csrf的攻击,基本都是基于cookie与session的。但是,对于一些维护传统项目,使用了模板引擎甚至jsp的程序员没错,就是我来说,确实有必要记录一下这方面的配置。 Spring Security 默认开启csrf,不需要额外配置
二、CsrfFilter浅析
public final class CsrfFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
DeferredCsrfToken deferredCsrfToken = this.tokenRepository.loadDeferredToken(request, response);
this.requestHandler.handle(request, response, deferredCsrfToken::get);
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);
return;
}
CsrfToken csrfToken = deferredCsrfToken.get();
String actualToken = this.requestHandler.resolveCsrfTokenValue(request, csrfToken);
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
boolean missingToken = deferredCsrfToken.isGenerated();
this.logger.debug(
LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken)
: new MissingCsrfTokenException(actualToken);
this.accessDeniedHandler.handle(request, response, exception);
return;
}
filterChain.doFilter(request, response);
}
private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
private final HashSet
@Override
public boolean matches(HttpServletRequest request) {
return !this.allowedMethods.contains(request.getMethod());
}
@Override
public String toString() {
return "CsrfNotRequired " + this.allowedMethods;
}
}
}
来分析一下代码,当请求过来时,创建一个DeferredCsrfToken对象。来看一下创建过程
final class RepositoryDeferredCsrfToken implements DeferredCsrfToken {
private final CsrfTokenRepository csrfTokenRepository;
private final HttpServletRequest request;
private final HttpServletResponse response;
private CsrfToken csrfToken;
private boolean missingToken;
RepositoryDeferredCsrfToken(CsrfTokenRepository csrfTokenRepository, HttpServletRequest request,
HttpServletResponse response) {
this.csrfTokenRepository = csrfTokenRepository;
this.request = request;
this.response = response;
}
@Override
public CsrfToken get() {
init();
return this.csrfToken;
}
@Override
public boolean isGenerated() {
init();
return this.missingToken;
}
private void init() {
if (this.csrfToken != null) {
return;
}
this.csrfToken = this.csrfTokenRepository.loadToken(this.request);
this.missingToken = (this.csrfToken == null);
if (this.missingToken) {
this.csrfToken = this.csrfTokenRepository.generateToken(this.request);
this.csrfTokenRepository.saveToken(this.csrfToken, this.request, this.response);
}
}
}
其实这里就是选择调用哪个csrfTokenRepository(cookie or session)。然后调用get()方法获取到csrfToken对象。绕来绕去,还得看init()方法。这里的csrfTokenRepository.loadToken(this.request)其实就是根据session或者cookie获取csrfToken,若没有获取到csrfToken对象(this.csrfToken == null),那么创建一个csrfToken对象。 接着返回CsrfFilter中的代码,调用了一个handler
public void handle(HttpServletRequest request, HttpServletResponse response,
Supplier
Assert.notNull(request, "request cannot be null");
Assert.notNull(response, "response cannot be null");
Assert.notNull(deferredCsrfToken, "deferredCsrfToken cannot be null");
request.setAttribute(HttpServletResponse.class.getName(), response);
CsrfToken csrfToken = new SupplierCsrfToken(deferredCsrfToken);
request.setAttribute(CsrfToken.class.getName(), csrfToken);
String csrfAttrName = (this.csrfRequestAttributeName != null) ? this.csrfRequestAttributeName
: csrfToken.getParameterName();
request.setAttribute(csrfAttrName, csrfToken);
}
将csrfToken放入到request中 继续往下走,如果请求的方法是"GET",“HEAD”,“TRACE”,“OPTIONS”,那么直接放行详情看DefaultRequiresCsrfMatcher.matches() 如果不是上面那几个方法的请求,那么将比对请求中的_csrf参数或者X-CSRF-TOKEN请求头与session中的csrfToken,若相同则通过,若不同,则拦截请求
三、获取csrfToken
因为csrfToken是写入request中的,所以我们需要用到模板引擎来获取csrfToken
使用模板引擎thymeleaf 你说你想用jsp?老子反手给你一拳
在需要发送post等请求的页面中,加入以下代码
那么再来看一下现在我们自定义的登录界面的样子
四、不使用模板引擎获取csrfToken
@RestController
public class CsrfController {
@RequestMapping("/csrf")
public CsrfToken csrf(CsrfToken token) {
return token;
}
}
上面的代码,就是直接从官网文档里抄来的。关于这个接口,官方文档就写了It is important to keep the CsrfToken a secret from other domains. This means that, if you use Cross Origin Sharing (CORS), you should NOT expose the CsrfToken to any external domains. 至于怎么做,这里就不展开讲了。总之,本篇章就讲怎么使用Spring Security的csrf使用。 然后是前端的一些操作,这里用JQuery的Ajax演示,想来能用到这个的也不会去用axios
$(function(){
$.ajax({
url:"/csrf",
type:"GET",
dataType:"json",
success:callback
})
})
function callback(response) {
let csrfInput = document.getElementById('csrf');
csrfInput.value = response.token;
}
发表评论