美文网首页
Spring @CrossOrigin 通配符 解决跨域问题

Spring @CrossOrigin 通配符 解决跨域问题

作者: ff21994458a6 | 来源:发表于2019-08-15 17:01 被阅读0次

    本文由 简悦 SimpRead 转码, 原文地址 https://www.cnblogs.com/wangdaijun/p/11348463.html

    @CrossOrigin 通配符 解决跨域问题

    痛点:

    对很多 api 接口需要 开放 H5 Ajax 跨域请求支持 由于环境多套域名不同, 而 CrossOrigin 原生只支持 * 或者具体域名的跨域支持 所以想让 CrossOrigin 支持下通配 *.abc.com 支持所有 origin 为 abc.com 域 (包括各种子域名) 名来的 Ajax 请求支持跨域.

    解决思路:

    支持通配

    @CrossOrigin(origins = {".abc.com"}) 通配 主域 + 任意子域 www.abc.com order.api.abc.com dev.order.abc.com
    @CrossOrigin(origins = {"
    .order.abc.com"}) 通配 order 子域 子域名 dev.order.abc.com test.order.abc.com uat.order.abc.com 等

    Spring 默认支持 cors 拓展下 Spring 对跨域的处理类

    解决方案:

    获取 RequestMappingHandlerMapping 设置自定义 MyCorsProcessor 代替 DefaultCorsProcessor

    /**
     * 给requestMappingHandlerMapping 对象注入自定义 MyCorsProcessor
     * @author tomas
     * @create 2019/8/12
     **/
    @Configuration
    @EnableWebMvc
    public class MyWebMvcConfig extends DelegatingWebMvcConfiguration {
        @Bean
        public RequestMappingHandlerMapping requestMappingHandlerMapping() {
            RequestMappingHandlerMapping handlerMapping = super.requestMappingHandlerMapping();
            handlerMapping.setCorsProcessor(new MyCorsProcessor());
            return handlerMapping;
        }
    }
    
    
    /**
     * MyCorsProcessor 描述
     * 自定义 如果xxx.com域下的请求允许跨域
     *
     * @author tomas
     * @create 2019/8/12
     **/
    public class MyCorsProcessor extends DefaultCorsProcessor {
    
        /**
         * Check the origin of the request against the configured allowed origins.
         * @param requestOrigin the origin to check
         * @return the origin to use for the response, or {@code null} which
         * means the request origin is not allowed
         */
        @Nullable
        public String checkOrigin(CorsConfiguration config, @Nullable String requestOrigin) {
            if (!StringUtils.hasText(requestOrigin)) {
                return null;
            }
            if (ObjectUtils.isEmpty(config.getAllowedOrigins())) {
                return null;
            }
            if (config.getAllowedOrigins().contains(CorsConfiguration.ALL)) {
                if (config.getAllowCredentials() != Boolean.TRUE) {
                    return CorsConfiguration.ALL;
                }
                else {
                    return requestOrigin;
                }
            }
            AntPathMatcher pathMatcher = new AntPathMatcher("|");      
            for (String allowedOrigin :config.getAllowedOrigins()) {
                if (requestOrigin.equalsIgnoreCase(allowedOrigin)) {
                    return requestOrigin;
                }
                //推荐方式:正则  注意(CrossOrigin(origins = {"*.abc.com"}) ) 主域会匹配主域+子域   origins = {"*.pay.abc.com"} 子域名只会匹配子域
                if(pathMatcher.isPattern(allowedOrigin)&&pathMatcher.match(allowedOrigin,requestOrigin)){
                    return requestOrigin;
                }
                //不推荐方式:写死
                if(allowedOrigin.contains("*.abc.com")&& requestOrigin.contains("abc.com")){
                    return requestOrigin;
                }
            }
            return null;
        }
    }
    
    

    原理分析:

    Spring mvc cors

    Spring MVC 的文档这样说:
    Spring MVC 的 HandlerMapping 实现内置支持 CORS, 在成功映射一个请求到一个 handler 之后, HandlerMapping 会检查 CORS 配置以采取下一步动作。
    https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-cors-processing
    Spring MVC 会在找到 handler 后通过添加一个拦截器来检查 CORS 配置。

    下面来看一下 Spring MVC 中的 CORS 的实现。
    DispatcherServlet 调用 AbstractHandlerMapping 中的 getHandler() 方法:

      /**
         * Look up a handler for the given request, falling back to the default
         * handler if no specific one is found.
         * @param request current HTTP request
         * @return the corresponding handler instance, or the default handler
         * @see #getHandlerInternal
         */
    @Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
            Object handler = getHandlerInternal(request);
            if (handler == null) {
                handler = getDefaultHandler();
            }
            if (handler == null) {
                return null;
            }
            // Bean name or resolved handler?
            if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = obtainApplicationContext().getBean(handlerName);
            }
    
            HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
            if (CorsUtils.isCorsRequest(request)) {
                CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
                CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
                CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
                executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
            }
            return executionChain;
        }
    
    

    对于 Ajax 请求 getCorsHandlerExecutionChain 自动加上一个 CorsInterceptor 的拦截器:

     /**
         * Update the HandlerExecutionChain for CORS-related handling.
         * <p>For pre-flight requests, the default implementation replaces the selected
         * handler with a simple HttpRequestHandler that invokes the configured
         * {@link #setCorsProcessor}.
         * <p>For actual requests, the default implementation inserts a
         * HandlerInterceptor that makes CORS-related checks and adds CORS headers.
         * @param request the current request
         * @param chain the handler chain
         * @param config the applicable CORS configuration (possibly {@code null})
         * @since 4.2
         */
    protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
                HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
    
            if (CorsUtils.isPreFlightRequest(request)) {
                HandlerInterceptor[] interceptors = chain.getInterceptors();
                chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
            }
            else {
                chain.addInterceptor(new CorsInterceptor(config));
            }
            return chain;
        }
    
    
    

    AbstractHandlerMapping 中 私有 class CorsInterceptor

     
    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 {
                return corsProcessor.processRequest(this.config, request, response);
            }
            @Override
            @Nullable
            public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                return this.config;
            }
        }
    
    
    

    CorsInterceptor 中 preHandle 方法 实际处理 processRequest 的是 AbstractHandlerMapping.this.corsProcessor

    这个 corsProcessor =new DefaultCorsProcessor() 是一个默认的跨域处理类

    我们的重点就是 重写 DefaultCorsProcessor 的 checkOrigin 方法

     
        @Override
        @SuppressWarnings("resource")
        public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
                HttpServletResponse response) throws IOException {
    
            if (!CorsUtils.isCorsRequest(request)) {
                return true;
            }
                    ......
            return handleInternal(serverRequest, serverResponse, config, preFlightRequest);
        }
    
    
        /**
         * Handle the given request.
         */
        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();
    
            responseHeaders.addAll(HttpHeaders.VARY, Arrays.asList(HttpHeaders.ORIGIN,
                    HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));
    
            if (allowOrigin == null) {
                logger.debug("Rejecting CORS request because '" + requestOrigin + "' origin is not allowed");
                rejectRequest(response);
                return false;
            }
    
            ..........
            response.flush();
            return true;
        }
    
        /**
         * Check the origin and determine the origin for the response. The default
         * implementation simply delegates to
         * {@link org.springframework.web.cors.CorsConfiguration#checkOrigin(String)}.
         */
    
           // 重写此方法 支持通配符 或者支持正则表达式 写法见开头解决方案
        @Nullable
        protected String checkOrigin(CorsConfiguration config, @Nullable String requestOrigin) {
            return config.checkOrigin(requestOrigin);
        }
    }
    
    

    dispatcherServlet 中在真正 invoke handler 之前会先调用拦截器: 从而通过加的 cors 拦截器阻止请求。
    doDispatch 方法:

     // Determine handler adapter for the current request.
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
                    // Process last-modified header, if supported by the handler.
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (logger.isDebugEnabled()) {
                            logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }
                        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
    
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
    
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
    
                    applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
    
    
    

    注意问题:

    1. 如果您正在使用 Spring Security,请确保在 Spring 安全级别启用 CORS,并允许它利用 Spring MVC 级别定义的配置。在 Spring 安全级别启用 CORS
    @EnableWebSecurity
    
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    ​    @Override
    
    ​    protected void configure(HttpSecurity http) throws Exception {
    
    ​        http.cors().and()...
    
    ​    }
    
    }
    
    
    1. 全局 CORS 配置

    除了细粒度、基于注释的配置之外,您还可能需要定义一些全局 CORS 配置。这类似于使用筛选器,但可以声明为 Spring MVC 并结合细粒度 @CrossOrigin 配置。默认情况下,所有 origins and GET, HEAD and POST methods 是允许的。

    使整个应用程序的 CORS 简化为:

    @Configuration
    
    @EnableWebMvc
    
    public class WebConfig extends WebMvcConfigurer {
    
    ​    @Override
    
    ​    public void addCorsMappings(CorsRegistry registry) {
    
    ​        registry.addMapping("/**");
    
    ​    }
    
    }
    
    
    
    1. 基于过滤器的 CORS 支持

    作为上述其他方法的替代,Spring 框架还提供了 CorsFilter。在这种情况下,不用使用@CrossOrigin或``WebMvcConfigurer#addCorsMappings(CorsRegistry),,例如,可以在 Spring Boot 应用程序中声明如下的过滤器:

    @Configuration
    
    public class MyConfiguration {
    
    ​    @Bean
    
    ​    public FilterRegistrationBean corsFilter() {
    
    ​        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    
    ​        CorsConfiguration config = new CorsConfiguration();
    
    ​        config.setAllowCredentials(true);
    
    ​        config.addAllowedOrigin("http://domain1.com");
    
    ​        config.addAllowedHeader("*");
    
    ​        config.addAllowedMethod("*");
    
    ​        source.registerCorsConfiguration("/**", config);
    
    ​        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
    
    ​        bean.setOrder(0);
    
    ​        return bean;
    
    ​    }
    
    }
    
    

    感谢 @大神张林峰老师 @王昆老师 @中睿老师 给出的宝贵意见

    1、官方文档 https://spring.io/blog/2015/06/08/cors-support-in-spring-framework

    2、https://blog.csdn.net/weixin_33713503/article/details/88039675

    https://www.jianshu.com/p/d05303d34222

    https://www.cnblogs.com/helloz/p/10961039.html

    2、https://blog.csdn.net/taiyangnimeide/article/details/78305131

    3、https://blog.csdn.net/snowin1994/article/details/53035433


    相关文章

      网友评论

          本文标题:Spring @CrossOrigin 通配符 解决跨域问题

          本文链接:https://www.haomeiwen.com/subject/tozzjctx.html