Spring Security Config : HttpSecurity安全配置器 CsrfConfigurer

概述

介绍

作为一个配置HttpSecuritySecurityConfigurer,CsrfConfigurer的配置任务如下 :

  • 配置如下安全过滤器Filter
    • CsrfFilter

      对应#requireCsrfProtectionMatcher方法指定的RequestMatcher会应用CSRF保护,如果#requireCsrfProtectionMatcher没有被使用者调用,使用缺省值:对"GET", “HEAD”, “TRACE”, "OPTIONS"这四种头部之外的其他请求应用CSRF保护。

  • 创建的共享对象
    • (无)

另外,CsrfConfigurer使用到了如下共享对象 :

  • ExceptionHandlingConfigurer.accessDeniedHandler(AccessDeniedHandler)

    用于决定如何处理CSRF 尝试(attempts)的缺省AccessDeniedHandler

  • InvalidSessionStrategy

    SessionManagementConfigurer的属性

继承关系

Spring Security Config : HttpSecurity安全配置器 CsrfConfigurer_第1张图片

使用

	// 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;
		}
	}
}

参考文章

你可能感兴趣的:(Spring,Security,分析)