SpringMVC原理(3)-跨域处理

4 阅读5分钟

前面分析了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);
 }
  1. 调用hasCorsConfigurationSource()判断是否有全局配置跨域

  2. MappingRegistry中找是否有局部配置了跨域@CrossOrigin

    1. 如果修饰类,这个类里所有接口方法都会注册到这个属性中Map<HandlerMethod, CorsConfiguration> corsLookup

全局配置:WebMvcConfigurer

image.png

局部配置:@CrossOrigin

image.png

跨域处理

就是往原先的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;
     }
 }
  1. 如果是预检请求就会重新构建一个Handler(PreFlightHandler)和过滤器链返回。(预检请求不需要真正的Handler来处理)这个Handler就是用来处理预检请求的

    1. 这个Handler最终也会调用CorsInterceptor处理跨域请求,然后返回值会返回一个null
  2. 否则就是添加一个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-OriginAccess-Control-Allow-CredentialsVary相关的头

注意:预检请求的请求方式和请求头会获取这两个请求头的值来的得到:Access-Control-Request-MethodAccess-Control-Request-Headers

跨域请求处理流程

第一次预检请求-OPTIONS

上面说了预检请求会给一个PreFlightHandler来处理,这个Handler里也会利用CorsProcessor来处理跨域。由于它是预检请求,所以会根据我们的配置为预检请求带上相关的响应头。

它不会被CorsInterceptor拦截到。

预检请求的响应头:

image.png

预检请求额外的请求头:

image.png

第二次请求-POST、DELTE等

预检请求过后,如果允许跨域,还会发一个我们的普通请求,这次就只会给我们携带Access-Control-Allow-OriginAccess-Control-Allow-CredentialsVary相关的头 这些响应头

image.png

后续的请求会根据我们配置的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;
   }
}