SpringSecurity - 启动流程分析(九)- CorsFilter 过滤器


活动地址:CSDN21天学习挑战赛

前言

在上一篇 SpringSecurity - 启动流程分析(八)- CsrfFilter 过滤器 文章中我们了解了 SpringSecurity - 启动流程分析(四)- 默认过滤器 中提到的 CsrfFilter 过滤器 来抵御 Csrf 攻击。跨域问题也是我们 Web 开发中经常遇到的问题,接下来在这篇文章中我们就来看一下 SpringSecurity 是如何使用 CorsFilter 来解决跨域问题的

概述

对于什么是 跨域问题同源策略,这里就不再赘述了,直接从源码开始分析

分析

HttpSecurity 加载 cors() 配置

/**
 * Adds a {@link CorsFilter} to be used. If a bean by the name of corsFilter is
 * provided, that {@link CorsFilter} is used. Else if corsConfigurationSource is
 * defined, then that {@link CorsConfiguration} is used. Otherwise, if Spring MVC is
 * on the classpath a {@link HandlerMappingIntrospector} is used.
 * @return the {@link CorsConfigurer} for customizations
 * @throws Exception
 */
public CorsConfigurer<HttpSecurity> cors() throws Exception {
	return getOrApply(new CorsConfigurer<>());
}

从源码注释中可以看到,要使用跨域配置有三种方式:

  1. 配置 CorsFilterIoC 容器
  2. 配置 CorsConfiguration
  3. 在类路径上存在 SpringMVC 提供的 HandlerMappingIntrospector

接下来看一下 CorsConfigurer 的具体源码:

public class CorsConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<CorsConfigurer<H>, H> {

	private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";

	private static final String CORS_CONFIGURATION_SOURCE_BEAN_NAME = "corsConfigurationSource";

	// 从容器中获取名称为 corsFilter 的 Bean
	private static final String CORS_FILTER_BEAN_NAME = "corsFilter";

	private CorsConfigurationSource configurationSource;

	/**
	 * Creates a new instance
	 *
	 * @see HttpSecurity#cors()
	 */
	public CorsConfigurer() {
	}

	// 可以传入 CorsConfigurationSource
	public CorsConfigurer<H> configurationSource(CorsConfigurationSource configurationSource) {
		this.configurationSource = configurationSource;
		return this;
	}

	@Override
	public void configure(H http) {
		ApplicationContext context = http.getSharedObject(ApplicationContext.class);
		CorsFilter corsFilter = getCorsFilter(context);
		Assert.state(corsFilter != null, () -> "Please configure either a " + CORS_FILTER_BEAN_NAME + " bean or a "
				+ CORS_CONFIGURATION_SOURCE_BEAN_NAME + "bean.");
		http.addFilter(corsFilter);
	}

	// 返回 CorsFilter,是从容器中获取还是创建一个
	private CorsFilter getCorsFilter(ApplicationContext context) {
		// 构造器中是否有传入 configurationSource 
		if (this.configurationSource != null) {
			return new CorsFilter(this.configurationSource);
		}
		// IoC 容器中是否有名为 corsFilter 的 Bean
		boolean containsCorsFilter = context.containsBeanDefinition(CORS_FILTER_BEAN_NAME);
		if (containsCorsFilter) {
			return context.getBean(CORS_FILTER_BEAN_NAME, CorsFilter.class);
		}
		boolean containsCorsSource = context.containsBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME);
		if (containsCorsSource) {
			CorsConfigurationSource configurationSource = context.getBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME,
					CorsConfigurationSource.class);
			return new CorsFilter(configurationSource);
		}
		boolean mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, context.getClassLoader());
		if (mvcPresent) {
			return MvcCorsFilter.getMvcCorsFilter(context);
		}
		return null;
	}

	static class MvcCorsFilter {

		private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";

		/**
		 * This needs to be isolated into a separate class as Spring MVC is an optional
		 * dependency and will potentially cause ClassLoading issues
		 * @param context
		 * @return
		 */
		private static CorsFilter getMvcCorsFilter(ApplicationContext context) {
			if (!context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
				throw new NoSuchBeanDefinitionException(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, "A Bean named "
						+ HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME + " of type "
						+ HandlerMappingIntrospector.class.getName()
						+ " is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.");
			}
			HandlerMappingIntrospector mappingIntrospector = context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
					HandlerMappingIntrospector.class);
			return new CorsFilter(mappingIntrospector);
		}

	}

}

CorsConfigurer 的主要作用是为了构建 CorsFilter 并加入到 SecurityFilterChain 中,CorsFilter 是执行具体的过滤规则:

public class CorsFilter extends OncePerRequestFilter {
    private final CorsConfigurationSource configSource;
    private CorsProcessor processor = new DefaultCorsProcessor();

	// 在定义 CorsFilter 的同时,需要传入 CorsConfigurationSource
    public CorsFilter(CorsConfigurationSource configSource) {
        Assert.notNull(configSource, "CorsConfigurationSource must not be null");
        this.configSource = configSource;
    }

    public void setCorsProcessor(CorsProcessor processor) {
        Assert.notNull(processor, "CorsProcessor must not be null");
        this.processor = processor;
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
        // 这里是判断请求是否符合同源策略,可以查看上面默认的 DefaultCorsProcessor
        boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
        if (isValid && !CorsUtils.isPreFlightRequest(request)) {
            filterChain.doFilter(request, response);
        }
    }
}

举例

这里给出一个示例,往容器中添加一个名为 corsFilterBean,在执行请求的时候就会判断请求头中的属性是否符合配置的同源设置

@Configuration
public class CorsConfig {

    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();

		// 接收所有来源的请求
        // corsConfiguration.addAllowedOriginPattern("*");
        // 接收指定来源的请求
        corsConfiguration.addAllowedOriginPattern("http://xxx.com");
        corsConfiguration.addAllowedOriginPattern("http://aa.xxx.com");

        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowCredentials(true);
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值