前言
- • 跨域:两个域名的(协议、域名/ip、端口)有任意一个不同即视为跨域
- • 跨域资源共享(Cors):即浏览器允许访问其他跨域的资源
- • 而CorsFilter就是SpringSecurity用来处理Cors的过滤器
1. CorsConfigurer
- • CorsConfigurer是CorsFilter对应的配置类,其中就只有一个重要方法
- • configure(...)
1.1 configure(...)
- • configure(...)源码很简单,主要是调用了getCorsFilter(...)方法
@Override
public void configure(H http) {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
// 创建CorsFilter
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);
}
- • getCorsFilter(...):
- • 容器中若是有CorsFilter直接返回
- • 容器中若是有CorsConfigurationSource,那就为其包装为CorsFilter返回
private CorsFilter getCorsFilter(ApplicationContext context) {
if (this.configurationSource != null) {
return new CorsFilter(this.configurationSource);
}
//从容器中获得CorsFilter过滤器
boolean containsCorsFilter = context.containsBeanDefinition(CORS_FILTER_BEAN_NAME);
if (containsCorsFilter) {
return context.getBean(CORS_FILTER_BEAN_NAME, CorsFilter.class);
}
//获取Cors匹配容器
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;
}
1.2 CorsConfiguration
- • CorsConfigurationSource是根据请求获取CorsConfiguration,也就Cors规则
- • 简单的看下里面的规则
/**
* Cors配置默认的允许的请求方式
*/
private static final List<String> DEFAULT_PERMIT_METHODS = Collections.unmodifiableList(
Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));
/**
* 默认允许全部
*/
private static final List<String> DEFAULT_PERMIT_ALL = Collections.singletonList(ALL);
/**
* 允许的来源
*/
@Nullable
private List<String> allowedOrigins;
/**
* 允许的请求方式
*/
@Nullable
private List<String> allowedMethods;
/**
* 允许的请求方式
*/
@Nullable
private List<HttpMethod> resolvedMethods = DEFAULT_METHODS;
/**
* 跨域请求允许携带的请求头
*/
@Nullable
private List<String> allowedHeaders;
/**
* 不懂
*/
@Nullable
private List<String> exposedHeaders;
/**
* 应该是客户端是否允许发送Cookie
*/
@Nullable
private Boolean allowCredentials;
/**
* 预检查请求的有效期
*/
@Nullable
private Long maxAge;
2. CorsFilter
- • 作为SpringSecurity处理Cors的过滤器,其源码很少
- • 就是从CorsConfigurationSource中获取CorsConfiguration,然后把CorsProcessor丢进CorsProcessor判断是否跨域请求以及对跨域请求的处理
public class CorsFilter extends OncePerRequestFilter {
private final CorsConfigurationSource configSource;
private CorsProcessor processor = new DefaultCorsProcessor();
...
@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);
}
...
}
2.1 DefaultCorsProcessor
- • CorsProcessor是Spring-Web包下的,其实现也只有一个DefaultCorsProcessor
- • 我们看下其入口方法,主要就是非跨域请求直接放行,不是的话就调用handleInternal(...)方法
@Override
@SuppressWarnings("resource")
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
HttpServletResponse response) throws IOException {
//添加一些响应头
Collection<String> varyHeaders = response.getHeaders(HttpHeaders.VARY);
if (!varyHeaders.contains(HttpHeaders.ORIGIN)) {
response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
}
if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD)) {
response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
}
if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS)) {
response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
}
// 不是跨域请求直接放行
if (!CorsUtils.isCorsRequest(request)) {
return true;
}
if (response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) != null) {
logger.trace("Skip: response already contains "Access-Control-Allow-Origin"");
return true;
}
// 是否是跨域请求
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
// 如果是预检查请求,但是又没有Cors数据源,就直接返回false
if (config == null) {
if (preFlightRequest) {
rejectRequest(new ServletServerHttpResponse(response));
return false;
}
else {
return true;
}
}
// 如果是预检查请求检查是否支持,其他类型的直接返回True
return handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest);
}
- • 我们再看下handleInternal(...)方法:如果是预检查(跨域)请求检查是否支持,其他类型的直接返回True
- • 本质上就是在满足跨域规则的情况下,添加对应的响应头
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config, boolean preFlightRequest) throws IOException {
String requestOrigin = request.getHeaders().getOrigin();
// 检查来源
// 获得请求来源
String allowOrigin = checkOrigin(config, requestOrigin);
if (allowOrigin == null) {
logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
rejectRequest(response);
return false;
}
HttpHeaders responseHeaders = response.getHeaders();
// 检查请求方式
// 拿到此次预检查请求代表的真正请求的请求方式
HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
// 检查请求方式是否允许,为空就代表不支持
if (allowMethods == null) {
logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
rejectRequest(response);
return false;
}
// 检查请求头
// 拿到预检查请求对应的的真正请求会携带的请求头
List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
// 检查请求头是否允许,为空就代表不支持
List<String> allowHeaders = checkHeaders(config, requestHeaders);
if (preFlightRequest && allowHeaders == null) {
logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
rejectRequest(response);
return false;
}
// 告诉客户端允许来自这的请求
responseHeaders.setAccessControlAllowOrigin(allowOrigin);
// 是预检查请求, 告诉客户端允许这种请求方式
if (preFlightRequest) {
responseHeaders.setAccessControlAllowMethods(allowMethods);
}
if (preFlightRequest && !allowHeaders.isEmpty()) {
responseHeaders.setAccessControlAllowHeaders(allowHeaders);
}
if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
}
// 估计是允许Cookie
if (Boolean.TRUE.equals(config.getAllowCredentials())) {
responseHeaders.setAccessControlAllowCredentials(true);
}
// 是预检查请求, 设置预检查请求有效期
if (preFlightRequest && config.getMaxAge() != null) {
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
}
response.flush();
return true;
}