先简单介绍一下CORS的背景
同源策略
跨域问题的产生是因为浏览器的同源策略。同源策略将协议+域名+端口
构成的三元作为一个整体,只有三者均相同的情况下才属于一个源。跨域问题也就是不同源之间访问导致的问题。
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
跨域资源共享 CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
公司有一个查询接口,该接口本身用于end to end一直没有产生跨域的问题,后来了解到还有一个JS SDK,该SDK也会通过AJAX调用这个接口就产生了跨域问题,需要后端进行支持。
简单请求与非简单请求
(1)工作中比较常见 【简单请求】:
方法为:
- GET
- HEAD
- POST
(2)请求header里面:
无自定义头
Content-Type为以下几种:
text/plain
multipart/form-data
application/x-www-form-urlencoded
除此以外的请求都为非简单请求,最常见的场景中由ajax发送json报文。这两种请求浏览器会有不同的行为,具体可以参考CORS详解。本文主要介绍如何在SpringBoot中如何处理CORS。
SpringBoot配置
@Bean
public CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("POST"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/test",configuration);
return source;
}
由于我们项目中使用了SpringSecurity,还需要一个额外的配置上面才能生效
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.....
.and().cors();
}
}
上述完成之后,会在filterChain中加入一个新的CorsFilter
,该filter中有一个map存放url和CorsConfiguration,当有新的request时,会通过url获取到对应的CorsConfiguration,然后交给DefaultCorsProcessor
来进行处理,主要是检查headers,origin,HttpMethod是否匹配,不匹配的话就返回http403的错误,下面是源代码。
public class CorsFilter extends OncePerRequestFilter {
private final CorsConfigurationSource configSource;
private CorsProcessor processor = new DefaultCorsProcessor();
/**
* Constructor accepting a {@link CorsConfigurationSource} used by the filter
* to find the {@link CorsConfiguration} to use for each incoming request.
* @see UrlBasedCorsConfigurationSource
*/
public CorsFilter(CorsConfigurationSource configSource) {
Assert.notNull(configSource, "CorsConfigurationSource must not be null");
this.configSource = configSource;
}
/**
* Configure a custom {@link CorsProcessor} to use to apply the matched
* {@link CorsConfiguration} for a request.
* <p>By default {@link DefaultCorsProcessor} is used.
*/
public void setCorsProcessor(CorsProcessor processor) {
Assert.notNull(processor, "CorsProcessor must not be null");
this.processor = processor;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
if (!isValid || CorsUtils.isPreFlightRequest(request)) {
return;
}
filterChain.doFilter(request, response);
}
}
到这里其实CORS就已经结束了,但是DefaultCorsProcessor
不太符合我们的需要,我们在interceptor增加了各种检查,而options类型的请求不会进入到interceptor中,我们需要定制化的Processor来完善这一功能。
仔细看CorsFilter
,可以发现其实有一个方法setCorsProcessor(CorsProcessor processor)
可以进行替换,我们先定义一个CustomCorsProcessor
public class CustomCorsProcessor extends DefaultCorsProcessor {
private static Logger logger = LoggerFactory.getLogger(DefaultCorsProcessor.class);
@Override
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config, boolean preFlightRequest) throws IOException{
logger.info("worked");
return super.handleInternal(request, response, config, preFlightRequest);
}
}
最初的想法是通过@Bean
注解,看Spring本身会不会有机制进行方法注入,不过该尝试没有成功,最后干脆直接new一个CorsFilter
@Bean
public CorsFilter corsFilter(){
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("POST"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/test",configuration);
CorsFilter corsFilter = new CorsFilter(source);
corsFilter.setCorsProcessor(new CustomCorsProcessor());
return corsFilter;
}
更新,手动创建CorsFilter 的话,可以省略.and().cors()
的设置