作为一个配置HttpSecurity
的SecurityConfigurer
,CsrfConfigurer
的配置任务如下 :
Filter
CsrfFilter
对应
#requireCsrfProtectionMatcher
方法指定的RequestMatcher
会应用CSRF
保护,如果#requireCsrfProtectionMatcher
没有被使用者调用,使用缺省值:对"GET", “HEAD”, “TRACE”, "OPTIONS"这四种头部之外的其他请求应用CSRF
保护。
另外,CsrfConfigurer
使用到了如下共享对象 :
ExceptionHandlingConfigurer.accessDeniedHandler(AccessDeniedHandler)
用于决定如何处理
CSRF
尝试(attempts
)的缺省AccessDeniedHandler
InvalidSessionStrategy
SessionManagementConfigurer
的属性
// HttpSecurity 代码片段
public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
ApplicationContext context = getContext();
return getOrApply(new CsrfConfigurer<>(context));
}
源代码版本 Spring Security Config 5.1.4.RELEASE
package org.springframework.security.config.annotation.web.configurers;
// 省略 imports
public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
// csrf token 存储库,缺省使用基于 http session 的 存储库
private CsrfTokenRepository csrfTokenRepository = new LazyCsrfTokenRepository(
new HttpSessionCsrfTokenRepository());
// 应用 csrf 保护的 请求匹配器 , 缺省为 : GET, HEAD, TRACE, OPTIONS 之外所有的请求
private RequestMatcher requireCsrfProtectionMatcher = CsrfFilter.DEFAULT_CSRF_MATCHER;
// 不应用 csrf 保护的 请求匹配器
private List<RequestMatcher> ignoredCsrfProtectionMatchers = new ArrayList<>();
private final ApplicationContext context;
/**
* Creates a new instance
* @see HttpSecurity#csrf()
*/
public CsrfConfigurer(ApplicationContext context) {
this.context = context;
}
/**
* Specify the CsrfTokenRepository to use. The default is an
* HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
*
* @param csrfTokenRepository the CsrfTokenRepository to use
* @return the CsrfConfigurer for further customizations
*/
public CsrfConfigurer<H> csrfTokenRepository(
CsrfTokenRepository csrfTokenRepository) {
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
this.csrfTokenRepository = csrfTokenRepository;
return this;
}
/**
* Specify the RequestMatcher to use for determining when CSRF should be
* applied. The default is to ignore GET, HEAD, TRACE, OPTIONS and process all other
* requests.
*
* @param requireCsrfProtectionMatcher the RequestMatcher to use
* @return the CsrfConfigurer for further customizations
*/
public CsrfConfigurer<H> requireCsrfProtectionMatcher(
RequestMatcher requireCsrfProtectionMatcher) {
Assert.notNull(requireCsrfProtectionMatcher,
"requireCsrfProtectionMatcher cannot be null");
this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
return this;
}
/**
*
* Allows specifying HttpServletRequest that should not use CSRF Protection
* even if they match the #requireCsrfProtectionMatcher(RequestMatcher).
*
*
*
* For example, the following configuration will ensure CSRF protection ignores:
*
*
* 1. Any GET, HEAD, TRACE, OPTIONS (this is the default)
* 2. We also explicitly state to ignore any request that starts with "/sockjs/"
*
*
*
* http
* .csrf()
* .ignoringAntMatchers("/sockjs/**")
* .and()
* ...
*
*
* @since 4.0
*/
public CsrfConfigurer<H> ignoringAntMatchers(String... antPatterns) {
return new IgnoreCsrfProtectionRegistry(this.context).antMatchers(antPatterns)
.and();
}
/**
*
* Allows specifying HttpServletRequests that should not use CSRF Protection
* even if they match the #requireCsrfProtectionMatcher(RequestMatcher).
*
*
*
* For example, the following configuration will ensure CSRF protection ignores:
*
*
* 1. Any GET, HEAD, TRACE, OPTIONS (this is the default)
* 2. We also explicitly state to ignore any request that
* has a "X-Requested-With: XMLHttpRequest" header
*
*
*
* http
* .csrf()
* .ignoringRequestMatchers(request -> "XMLHttpRequest".equals(
* request.getHeader("X-Requested-With")))
* .and()
* ...
*
*
* @since 5.1
*/
public CsrfConfigurer<H> ignoringRequestMatchers(RequestMatcher... requestMatchers) {
return new IgnoreCsrfProtectionRegistry(this.context).requestMatchers(requestMatchers)
.and();
}
@SuppressWarnings("unchecked")
@Override
public void configure(H http) throws Exception {
// 创建 CsrfFilter 并设置 csrf 存储库
CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
// 构建 csrf 保护要应用的 RequestMatcher 并设置 :
// 1. 哪些 URL 明确要求 csrf 保护;
// 2. 哪些 URL 明确要求不要 csrf 保护;
RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
if (requireCsrfProtectionMatcher != null) {
filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
}
// 构建 csrf token 缺失时要应用的 AccessDeniedHandler 并设置
AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
if (accessDeniedHandler != null) {
filter.setAccessDeniedHandler(accessDeniedHandler);
}
// 如果 LogoutConfigurer 也被应用,向其添加一个 CsrfLogoutHandler 退出登录处理器
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
if (logoutConfigurer != null) {
logoutConfigurer
.addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));
}
// 如果 SessionManagementConfigurer 也被应用, 向其添加属性 CsrfAuthenticationStrategy,
// 认证成功时 csrf token 的处理策略,比如更新一个新的 csrf token 等
SessionManagementConfigurer<H> sessionConfigurer = http
.getConfigurer(SessionManagementConfigurer.class);
if (sessionConfigurer != null) {
sessionConfigurer.addSessionAuthenticationStrategy(
new CsrfAuthenticationStrategy(this.csrfTokenRepository));
}
filter = postProcess(filter);
http.addFilter(filter);
}
/**
* Gets the final RequestMatcher to use by combining the
* #requireCsrfProtectionMatcher(RequestMatcher) and any #ignore().
* 获取最终要应用的 RequestMatcher, 会综合考虑属性 :
* 1. requireCsrfProtectionMatcher 明确要使用csrf保护的请求匹配器
* 2. ignoredCsrfProtectionMatchers 明确不要使用csrf保护的请求匹配器
*
* @return the RequestMatcher to use
*/
private RequestMatcher getRequireCsrfProtectionMatcher() {
if (this.ignoredCsrfProtectionMatchers.isEmpty()) {
return this.requireCsrfProtectionMatcher;
}
return new AndRequestMatcher(this.requireCsrfProtectionMatcher,
new NegatedRequestMatcher(
new OrRequestMatcher(this.ignoredCsrfProtectionMatchers)));
}
/**
* Gets the default AccessDeniedHandler from the
* ExceptionHandlingConfigurer#getAccessDeniedHandler() or create a
* AccessDeniedHandlerImpl if not available.
*
* @param http the HttpSecurityBuilder
* @return the AccessDeniedHandler
*/
@SuppressWarnings("unchecked")
private AccessDeniedHandler getDefaultAccessDeniedHandler(H http) {
ExceptionHandlingConfigurer<H> exceptionConfig = http
.getConfigurer(ExceptionHandlingConfigurer.class);
AccessDeniedHandler handler = null;
if (exceptionConfig != null) {
handler = exceptionConfig.getAccessDeniedHandler();
}
if (handler == null) {
handler = new AccessDeniedHandlerImpl();
}
return handler;
}
/**
* Gets the default InvalidSessionStrategy from the
* SessionManagementConfigurer#getInvalidSessionStrategy() or null if not
* available.
*
* @param http the HttpSecurityBuilder
* @return the InvalidSessionStrategy
*/
@SuppressWarnings("unchecked")
private InvalidSessionStrategy getInvalidSessionStrategy(H http) {
SessionManagementConfigurer<H> sessionManagement = http
.getConfigurer(SessionManagementConfigurer.class);
if (sessionManagement == null) {
return null;
}
return sessionManagement.getInvalidSessionStrategy();
}
/**
* Creates the AccessDeniedHandler from the result of
* #getDefaultAccessDeniedHandler(HttpSecurityBuilder) and
* #getInvalidSessionStrategy(HttpSecurityBuilder). If
* #getInvalidSessionStrategy(HttpSecurityBuilder) is non-null, then a
* DelegatingAccessDeniedHandler is used in combination with
* InvalidSessionAccessDeniedHandler and the
* #getDefaultAccessDeniedHandler(HttpSecurityBuilder). Otherwise, only
* #getDefaultAccessDeniedHandler(HttpSecurityBuilder) is used.
*
* @param http the HttpSecurityBuilder
* @return the AccessDeniedHandler
*/
private AccessDeniedHandler createAccessDeniedHandler(H http) {
// 从 SessionManagementConfigurer 获取其 invalidSessionStrategy 属性
InvalidSessionStrategy invalidSessionStrategy = getInvalidSessionStrategy(http);
// 获取缺省要使用的 AccessDeniedHandler
AccessDeniedHandler defaultAccessDeniedHandler = getDefaultAccessDeniedHandler(
http);
if (invalidSessionStrategy == null) {
// 如果 invalidSessionStrategy 为 null 则直接使用缺省值 defaultAccessDeniedHandler
return defaultAccessDeniedHandler;
}
// 否则基于 defaultAccessDeniedHandler 和 invalidSessionStrategy 构造
// 一个 DelegatingAccessDeniedHandler 并应用
InvalidSessionAccessDeniedHandler invalidSessionDeniedHandler =
new InvalidSessionAccessDeniedHandler(invalidSessionStrategy);
LinkedHashMap<Class<? extends AccessDeniedException>, AccessDeniedHandler> handlers =
new LinkedHashMap<Class<? extends AccessDeniedException>, AccessDeniedHandler>();
handlers.put(MissingCsrfTokenException.class, invalidSessionDeniedHandler);
return new DelegatingAccessDeniedHandler(handlers, defaultAccessDeniedHandler);
}
/**
* Allows registering RequestMatcher instances that should be ignored (even if
* the HttpServletRequest matches the
* CsrfConfigurer#requireCsrfProtectionMatcher(RequestMatcher).
*
* @author Rob Winch
* @since 4.0
*/
private class IgnoreCsrfProtectionRegistry
extends AbstractRequestMatcherRegistry<IgnoreCsrfProtectionRegistry> {
/**
* @param context
*/
private IgnoreCsrfProtectionRegistry(ApplicationContext context) {
setApplicationContext(context);
}
@Override
public MvcMatchersIgnoreCsrfProtectionRegistry mvcMatchers(HttpMethod method,
String... mvcPatterns) {
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
CsrfConfigurer.this.ignoredCsrfProtectionMatchers.addAll(mvcMatchers);
return new MvcMatchersIgnoreCsrfProtectionRegistry(getApplicationContext(),
mvcMatchers);
}
@Override
public MvcMatchersIgnoreCsrfProtectionRegistry mvcMatchers(String... mvcPatterns) {
return mvcMatchers(null, mvcPatterns);
}
public CsrfConfigurer<H> and() {
return CsrfConfigurer.this;
}
@Override
protected IgnoreCsrfProtectionRegistry chainRequestMatchers(
List<RequestMatcher> requestMatchers) {
CsrfConfigurer.this.ignoredCsrfProtectionMatchers.addAll(requestMatchers);
return this;
}
}
/**
* An IgnoreCsrfProtectionRegistry that allows optionally configuring the
* MvcRequestMatcher#setMethod(HttpMethod)
*
* @author Rob Winch
*/
private final class MvcMatchersIgnoreCsrfProtectionRegistry
extends IgnoreCsrfProtectionRegistry {
private final List<MvcRequestMatcher> mvcMatchers;
private MvcMatchersIgnoreCsrfProtectionRegistry(ApplicationContext context,
List<MvcRequestMatcher> mvcMatchers) {
super(context);
this.mvcMatchers = mvcMatchers;
}
public IgnoreCsrfProtectionRegistry servletPath(String servletPath) {
for (MvcRequestMatcher matcher : this.mvcMatchers) {
matcher.setServletPath(servletPath);
}
return this;
}
}
}