前面分析了HandlerExecutionChain
是怎么被拿到的,最后一步就是跨域处理,然后就会把这个对象返回出去供我们使用。
原理
跨域处理原理:就是利用拦截器机制来处理。
跨域处理的核心逻辑:
// 1、判断是否有跨域配置或者是否是预检请求
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
// 2、拿到全局的跨域配置
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
// 3、拿到CrossOrigin级别的
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
// 4、把全局和局部的合并起来
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
// 5、处理 CORS 相关的操作
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
判断是否有跨域配置
hasCorsConfigurationSource()
、isPreFlightRequest()
// ===AbstractHandlerMethodMapping.class===
// @CrossOrigin 局部跨域配置
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
// 全局或者局部配置了 @CrossOrigin 注解
protected boolean hasCorsConfigurationSource(Object handler) {
return super.hasCorsConfigurationSource(handler) ||
(handler instanceof HandlerMethod && this.mappingRegistry.getCorsConfiguration((HandlerMethod) handler) != null);
}
// 获取 @CrossOrigin 注解修饰的方法
public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {
HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();
return this.corsLookup.get(original != null ? original : handlerMethod);
}
// ===AbstractHandlerMapping.class===
protected boolean hasCorsConfigurationSource(Object handler) {
if (handler instanceof HandlerExecutionChain) {
handler = ((HandlerExecutionChain) handler).getHandler();
}
return (handler instanceof CorsConfigurationSource || this.corsConfigurationSource != null);
}
// 是否是 OPTIONS 预检请求
public static boolean isPreFlightRequest(HttpServletRequest request) {
return (HttpMethod.OPTIONS.matches(request.getMethod()) &&
request.getHeader(HttpHeaders.ORIGIN) != null &&
request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null);
}
-
调用
hasCorsConfigurationSource()
判断是否有全局配置跨域 -
去
MappingRegistry
中找是否有局部配置了跨域@CrossOrigin
- 如果修饰类,这个类里所有接口方法都会注册到这个属性中
Map<HandlerMethod, CorsConfiguration> corsLookup
- 如果修饰类,这个类里所有接口方法都会注册到这个属性中
全局配置:
WebMvcConfigurer
局部配置:@CrossOrigin
跨域处理
就是往原先的HandlerExecutionChain
对象里添加一个CorsInterceptor
拦截器
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
// 1、如果是预检请求就会重写构建一个Handler(HttpRequestHandler)和过滤器链返回
if (CorsUtils.isPreFlightRequest(request)) {
HandlerInterceptor[] interceptors = chain.getInterceptors();
return new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
}
else {
// 2、否则就是添加一个CorsInterceptor来做跨域,并且优先级是0
chain.addInterceptor(0, new CorsInterceptor(config));
return chain;
}
}
-
如果是预检请求就会重新构建一个
Handler(PreFlightHandler)
和过滤器链返回。(预检请求不需要真正的Handler
来处理)这个Handler
就是用来处理预检请求的- 这个
Handler
最终也会调用CorsInterceptor
来处理跨域请求,然后返回值会返回一个null
- 这个
-
否则就是添加一个
CorsInterceptor
来做跨域,并且优先级是0(为了不被其他拦截器拦截跨域请求会将该跨域拦截器的顺序设置为第一个)
所以把CorsInterceptor
搞清楚就明白了。
CorsInterceptor
它是一个拦截器,所以会在执行拦截器的时候执行到它。
// 跨域处理器
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {
@Nullable
private final CorsConfiguration config;
public CorsInterceptor(@Nullable CorsConfiguration config) {
this.config = config;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// Consistent with CorsFilter, ignore ASYNC dispatches
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
if (asyncManager.hasConcurrentResult()) {
return true;
}
// 处理请求
return corsProcessor.processRequest(this.config, request, response);
}
@Override
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.config;
}
}
CorsProcessor
CorsInterceptor
又利用CorsProcessor
来处理跨域请求:
processRequest()
private static final Log logger = LogFactory.getLog(DefaultCorsProcessor.class);
@Override
@SuppressWarnings("resource")
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
HttpServletResponse response) throws IOException {
// Vary缓存
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);
if (config == null) {
if (preFlightRequest) {
rejectRequest(new ServletServerHttpResponse(response));
return false;
}
else {
return true;
}
}
// 处理给定的请求
return handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest);
}
handleInternal()
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config, boolean preFlightRequest) throws IOException {
// 源地址信息
String requestOrigin = request.getHeaders().getOrigin();
String allowOrigin = checkOrigin(config, requestOrigin);
HttpHeaders responseHeaders = response.getHeaders();
if (allowOrigin == null) {
logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
rejectRequest(response);
return false;
}
// 方法(如果是预检请求会从请求头中获取 Access-Control-Request-Method,如果不是就获取Method的值)
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;
}
// 请求头(如果是预检请求会从请求头中获取 Access-Control-Request-Headers,如果不是就获取全部的请求头)
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;
}
// 预检请求和POST、DELTE等都会设置
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;
}
最终会给预检请求设置上这些头,并根据缓存时间缓存起来,在多久内有效 不会再次发送预检请求。
而我们发的 POST、DELETE 这些只会给响应头带上Access-Control-Allow-Origin
、Access-Control-Allow-Credentials
、Vary
相关的头
注意:预检请求的请求方式和请求头会获取这两个请求头的值来的得到:Access-Control-Request-Method
、Access-Control-Request-Headers
跨域请求处理流程
第一次预检请求-OPTIONS
上面说了预检请求会给一个PreFlightHandler
来处理,这个Handler
里也会利用CorsProcessor
来处理跨域。由于它是预检请求,所以会根据我们的配置为预检请求带上相关的响应头。
它不会被CorsInterceptor
拦截到。
预检请求的响应头:
预检请求额外的请求头:
第二次请求-POST、DELTE等
预检请求过后,如果允许跨域,还会发一个我们的普通请求,这次就只会给我们携带Access-Control-Allow-Origin
、Access-Control-Allow-Credentials
、Vary
相关的头 这些响应头
后续的请求会根据我们配置的maxAge
缓存时间的值,来发送预检请求。如果配置了-1,那就是永久有效。
总结
原理
跨域处理:就是利用拦截器机制来处理。
CorsInterceptor
这个拦截器会利用CorsProcessor
跨域处理器来处理跨域,它会给我们的请求带上相关的响应头。
组件介绍
CorsProcessor
CorsProcessor
:跨域处理器
这个类是真正来处理跨域请求的类
CorsInterceptor
CorsInterceptor
:跨域拦截器
这个类是一个拦截器,利用拦截器机制来拦截普通请求,然后再利用CorsProcessor
来处理跨域
GET、POST、PUT、DELETE
PreFlightHandler
PreFlightHandler
:预检请求处理器
这个类来处理OPTIONS
(预检请求),它也会利用CorsProcessor
来处理跨域
它不会被CorsInterceptor
拦截
原因:如果是预检请求,重新构造一个Handler(PreFlightHandler)
返回,如果不是就添加跨域拦截器到拦截器链中
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
// 是否是跨域请求
if (CorsUtils.isPreFlightRequest(request)) {
HandlerInterceptor[] interceptors = chain.getInterceptors();
return new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
}
else {
// 添加跨域拦截器到拦截器链中
chain.addInterceptor(0, new CorsInterceptor(config));
return chain;
}
}