活动地址:优快云21天学习挑战赛
前言
在上一篇 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<>());
}
从源码注释中可以看到,要使用跨域配置有三种方式:
- 配置
CorsFilter
到IoC 容器
中 - 配置
CorsConfiguration
- 在类路径上存在
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);
}
}
}
举例
这里给出一个示例,往容器中添加一个名为 corsFilter
的 Bean
,在执行请求的时候就会判断请求头中的属性是否符合配置的同源设置
@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);
}
}