Spring @CrossOrigin 通配符 解決跨域問題

本文由 簡悅 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

http://www.lxweimin.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


?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容