Web —— @CrossOrigin实现跨域原理

零、上篇回顾

Web —— 何为跨域?为何出现?如何解决?

一、SpringMVC执行流程

让我们先来回顾一下SpringMVC执行流程,其中CORS的实现主要在HandlerMapping实现。 image

二、HanndlerMapping中如何实现CORS? —— 基于Spring 5.3.9 版本

1. AbstractHandlerMapping

AbstractHandlerMapping类是HanndlerMapping接口的实现类,我们来看看他有哪些CORS相关的属性及方法。

    private CorsProcessor corsProcessor = new DefaultCorsProcessor();
    
    public void setCorsProcessor(CorsProcessor corsProcessor) {
        Assert.notNull(corsProcessor, "CorsProcessor must not be null");
        this.corsProcessor = corsProcessor;
    }

    public CorsProcessor getCorsProcessor() {
        return this.corsProcessor;
    }
    
    // 内部类---------------------------------------------------------
    private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource {
        @Nullable
        private final CorsConfiguration config;

        public PreFlightHandler(@Nullable CorsConfiguration config) {
            this.config = config;
        }

        public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
            AbstractHandlerMapping.this.corsProcessor.processRequest(this.config, request, response);
        }

        @Nullable
        public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
            return this.config;
        }
    }
    private class CorsInterceptor implements HandlerInterceptor, CorsConfigurationSource {
        @Nullable
        private final CorsConfiguration config;

        public CorsInterceptor(@Nullable CorsConfiguration config) {
            this.config = config;
        }
        
        // ----------------核心方法2----------------
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            return asyncManager.hasConcurrentResult() ? true : AbstractHandlerMapping.this.corsProcessor.processRequest(this.config, request, response);
        }

        @Nullable
        public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
            return this.config;
        }
    }
    
    // ----------------核心方法1----------------
    protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
        if (CorsUtils.isPreFlightRequest(request)) {
            HandlerInterceptor[] interceptors = chain.getInterceptors();
            return new HandlerExecutionChain(new AbstractHandlerMapping.PreFlightHandler(config), interceptors);
        } else {
            chain.addInterceptor(0, new AbstractHandlerMapping.CorsInterceptor(config));
            return chain;
        }
    }

2. CorsProcessor类

上面我们提到当执行getCorsHandlerExecutionChain方法时,若是CORS请求,则在 拦截器处理链链首 添加CORS拦截器。
chain.addInterceptor(0, new AbstractHandlerMapping.CorsInterceptor(config));
CorsInterceptor拦截器为内部类,其核心方法为preHandle,主要是调用了this.corsProcessor.processRequest(this.config, request, response)
CorsProcessor只是接口,其具体实现类为DefaultCorsProcessor。
DefaultCorsProcessor.processRequest具体实现如下

    public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 对于CORS请求,需要通过CORS相关Header帮助判断是否直接返回缓存即可
        Collection<String> varyHeaders = response.getHeaders("Vary");
        if (!varyHeaders.contains("Origin")) {
            response.addHeader("Vary", "Origin");
        }
        if (!varyHeaders.contains("Access-Control-Request-Method")) {
            response.addHeader("Vary", "Access-Control-Request-Method");
        }
        if (!varyHeaders.contains("Access-Control-Request-Headers")) {
            response.addHeader("Vary", "Access-Control-Request-Headers");
        }
        
        // 这部分检测如果不是CORS请求,则直接返回true
        if (!CorsUtils.isCorsRequest(request)) {
            return true;
        } else if (response.getHeader("Access-Control-Allow-Origin") != null) {
            // 如果已经存相关HEADER,代表前面已经有Filter/Inteceptor进行了CORS相关设置
            logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");
            return true;
        } else {
            // 判断是否为CORS预检请求——OPTIONS方法
            boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
            if (config == null) {
                if (preFlightRequest) {
                    this.rejectRequest(new ServletServerHttpResponse(response));
                    return false;
                } else {
                    return true;
                }
            } else {
                // 处理逻辑核心,请看具体实现
                return this.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 = this.checkOrigin(config, requestOrigin);       // 判断是否匹配Origin,不匹配则返回null
        HttpHeaders responseHeaders = response.getHeaders();
        if (allowOrigin == null) {                                          // 这里代表未成功匹配Origin,拒绝请求
            logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
            this.rejectRequest(response);
            return false;
        } else {
            HttpMethod requestMethod = this.getMethodToUse(request, preFlightRequest);
            List<HttpMethod> allowMethods = this.checkMethods(config, requestMethod);
            if (allowMethods == null) {
                logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
                this.rejectRequest(response);
                return false;
            } else {
                // -----------------!!! 真正添加CORS 相关 Header 参数的位置 !!!-----------------
                List<String> requestHeaders = this.getHeadersToUse(request, preFlightRequest);
                List<String> allowHeaders = this.checkHeaders(config, requestHeaders);
                if (preFlightRequest && allowHeaders == null) {
                    logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
                    this.rejectRequest(response);
                    return false;
                } else {
                    // -----------------!!! 设置Access-Control-Allow-Origin !!!-----------------
                    responseHeaders.setAccessControlAllowOrigin(allowOrigin);
                    if (preFlightRequest) {
                    // -----------------!!! 设置Access-Control-Allow-Methods !!!-----------------
                        responseHeaders.setAccessControlAllowMethods(allowMethods);
                    }
                    // -----------------!!! 设置Access-Control-Expose-Headers !!!-----------------
                    if (preFlightRequest && !allowHeaders.isEmpty()) {
                        responseHeaders.setAccessControlAllowHeaders(allowHeaders);
                    }
                    // -----------------!!! 设置Access-Control-Expose-Headers !!!-----------------
                    if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
                        responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
                    }
                    // -----------------!!! 设置Access-Control-Allow-Credentials !!!-----------------
                    if (Boolean.TRUE.equals(config.getAllowCredentials())) {
                        responseHeaders.setAccessControlAllowCredentials(true);
                    }
                    // -----------------!!! 设置Access-Control-Max-Age !!!-----------------
                    if (preFlightRequest && config.getMaxAge() != null) {
                        responseHeaders.setAccessControlMaxAge(config.getMaxAge());
                    }

                    response.flush();
                    return true;
                }
            }
        }
    }

补充知识: HTTP Header 中 Vary字段作用
Vary HTTP 响应头决定如何满足未来的请求头,以决定一个缓存的响应是否可以使用,而不是请求从源服务器一个新的一个。
Vary的意义在于告诉代理服务器/缓存/CDN,如何判断是否直接返回缓存资源即可。比如Vary中有User-Agent,那么即使相同的请求,如果用户使用IE打开了一个页面,再用Fir> efox打开这个页面的时候,CDN/代理会认为是不同的页面,如果Vary中没有User-Agent,那么CDN/代理会认为是相同的页面,直接给用户返回缓存的页面,而不会再去web服务器请求相应的页面。

三、思考

  1. Spring Web 5.3.9版本CORS是通过CorsInterceptor实现的,CorsInterceptor实现了HandlerInterceptor接口,因此可知其实际是通过拦截器实现的。那么在Spring Web中,Filter和InterceptorInterceptor的区别是什么?

1.过滤器:
  依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据.
  比如:在过滤器中修改字符编码;在过滤器中修改 HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等

2.拦截器:
  依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用.
  因此可以使用spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。

  1. Filter和Interceptor执行顺序?Interceptor链先执行还是Filter链先执行?
    首先,Filter是基于Servlet的,通过即< url-pattern >/hello< /url-pattern >标签产生关联,其他相关标签是< serlvet-mapping >和< filter-mapping标签 >。
<servlet-mapping>  
    <servlet-name>servlet</servlet-name>  
    <url-pattern>/hello</url-pattern>  
    <url-pattern>/world</url-pattern>
    <url-pattern>/home1</url-pattern> 
</servlet-mapping> 

<filter-mapping>
    <filter-name>filter</filter-name>
    <url-pattern>/hello</url-pattern>
    <url-pattern>/home2</url-pattern>  
</filter-mapping>

而Interceptor是基于MVC的,相关xml配置。

<!-- 拦截器 -->  
  <mvc:interceptors>  
      <!-- 对所有请求都拦截,公共拦截器可以有多个 -->  
      <bean name="baseInterceptor" class="com.scorpios.interceptor.BaseInterceptor" />  
    
    <mvc:interceptor> 
        <!-- 对/test.html进行拦截 -->       
          <mvc:mapping path="/test.html"/>  
          <!-- 特定请求的拦截器只能有一个 -->  
          <bean class="com.scorpios.interceptor.TestInterceptor" />  
      </mvc:interceptor>  
  </mvc:interceptors>

Filter的执行是有先后顺序的,根据在web.xml中配置的先后顺序。其主要有三个方法init、doFilter、destory,我们主要关注doFilter方法,在这个方法中chain.doFilter会将请求及响应发送给下一个Filter,我们在这句调用前执行前置操作及后置操作即可。

public class LoggerFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("初始化参数,在创建Filter时自动调用,当我们需要设置初始化参数的时候,可以写到该方法中");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String contextPath = request.getServletContext().getContextPath();
        System.out.println("执行Filter前置操作");
        chain.doFilter(request, response);
        System.out.println("执行Filter后置操作");
    }

    @Override
    public void destroy() {
        System.out.println("在销毁Filter时自动调用");
    }
}

最后来点图片帮助理解 (图片来自参考资料的文章中)。

image

image

参考资料:
https://blog.csdn.net/chenleixing/article/details/44573495
https://www.jianshu.com/p/1b61e5556521
https://www.jb51.net/article/211240.htm

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值