HttpSecurity的默认配置如下:
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
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();
从上面可知,第一个安全配置是针对CSRF(跨站点请求伪造:Cross-Site Request Forgery)的。一般来讲,为了防御CSRF攻击主要有三种策略:验证 HTTP Referer 字段;在请求地址中添加 token 并验证;在 HTTP 头中自定义属性并验证。
HttpSecurity的csrf() 方法定义如下:
public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
ApplicationContext context = getContext();
return getOrApply(new CsrfConfigurer<>(context));
}
可见其配置类为CsrfConfigurer,其部分定义如下:
public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
//Csrf保护使用Token的存储库,默认使用Session保存
private CsrfTokenRepository csrfTokenRepository = new LazyCsrfTokenRepository(
new HttpSessionCsrfTokenRepository());
//需要Csrf保护的请求匹配规则
private RequestMatcher requireCsrfProtectionMatcher = CsrfFilter.DEFAULT_CSRF_MATCHER;
//不需要Csrf保护的请求匹配规则
private List<RequestMatcher> ignoredCsrfProtectionMatchers = new ArrayList<>();
//会话验证策略
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
private final ApplicationContext context;
public CsrfConfigurer(ApplicationContext context) {
this.context = context;
}
/*
* @param http HttpSecurity
*/
@Override
public void configure(H http) {
//用于Csrf保护的拦截器(内部的执行过程便是Csrf保护的过程)
CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
//默认为DefaultRequiresCsrfMatcher
RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
if (requireCsrfProtectionMatcher != null) {
filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
}
//访问拒绝处理器。如果缺失csrf的token时会被执行
AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
if (accessDeniedHandler != null) {
filter.setAccessDeniedHandler(accessDeniedHandler);
}
//注销时候的配置;获取注销的配置类,并添加回调,目的是注销时清除csrf的token
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
if (logoutConfigurer != null) {
logoutConfigurer
.addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));
}
//访问会话管理配置,添加基于Csrf的会话认证策略
//如果有csrf的token则先清除再重建且保存
SessionManagementConfigurer<H> sessionConfigurer = http
.getConfigurer(SessionManagementConfigurer.class);
if (sessionConfigurer != null) {
sessionConfigurer.addSessionAuthenticationStrategy(
getSessionAuthenticationStrategy());
}
//后置处理器
//CompositeObjectPostProcessor、AutowireBeanFactoryObjectPostProcessor
filter = postProcess(filter);
http.addFilter(filter);
}
private AccessDeniedHandler createAccessDeniedHandler(H http) {
//取得SessionManagementConfigurer里面配置的InvalidSessionStrategy
//默认处理器,首先获取ExceptionHandlingConfigurer中配置的AccessDeniedHandler,
//如果没有则使用默认的AccessDeniedHandlerImpl;默认响应403
InvalidSessionStrategy invalidSessionStrategy = getInvalidSessionStrategy(http);
AccessDeniedHandler defaultAccessDeniedHandler = getDefaultAccessDeniedHandler(
http);
if (invalidSessionStrategy == null) {
return defaultAccessDeniedHandler;
}
//使用invalidSessionStrategy进行处理,例如对请求重定向等
InvalidSessionAccessDeniedHandler invalidSessionDeniedHandler = new InvalidSessionAccessDeniedHandler(
invalidSessionStrategy);
LinkedHashMap<Class<? extends AccessDeniedException>, AccessDeniedHandler> handlers = new LinkedHashMap<>();
//指定MissingCsrfTokenException类型的异常可被invalidSessionDeniedHandler处理
handlers.put(MissingCsrfTokenException.class, invalidSessionDeniedHandler);
//该处理的逻辑是,如果处理的异常为MissingCsrfTokenException则使用invalidSessionDeniedHandler处理,
//否则使用默认的defaultAccessDeniedHandler处理
return new DelegatingAccessDeniedHandler(handlers, defaultAccessDeniedHandler);
}
}
CsrfFilter进行请求拦截的处理过程
public final class CsrfFilter extends OncePerRequestFilter {
/**
* The default {@link RequestMatcher} that indicates if CSRF protection is required or
* not. The default is to ignore GET, HEAD, TRACE, OPTIONS and process all other
* requests.
*/
public static final RequestMatcher DEFAULT_CSRF_MATCHER = new DefaultRequiresCsrfMatcher();
private final CsrfTokenRepository tokenRepository;
private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
//得到保存的token
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missingToken = csrfToken == null;
if (missingToken) {
//创建新的csrf的token
csrfToken = this.tokenRepository.generateToken(request);
//将csrf的token进行保存
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
//判断是否是需要Csrf包含的请求,如果不是则执行后续的过滤器链
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
//从Http的请求头或请求参数中得到csrf的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) {
//此处调用了处理器,并传递了MissingCsrfTokenException异常
this.accessDeniedHandler.handle(request, response,
new MissingCsrfTokenException(actualToken));
} else {
//此处调用了处理器,并传递了InvalidCsrfTokenException异常
this.accessDeniedHandler.handle(request, response,
new InvalidCsrfTokenException(csrfToken, actualToken));
}
return;
}
filterChain.doFilter(request, response);
}
}
以上便是Spring Security中进行Csrf保护的大致处理过程,其思路就是生成一个csrf的token并保存到session中,然后从每次的请求中得到携带的csrf的token并与session中保存的做比对,如果不一致则验证不通过。
这个csrf的token其实就是一个uuid字符串,同时,默认只处理除了GET, HEAD, TRACE, OPTIONS方法外的其他请求。
网友评论