国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Spring Cloud 輕松解決跨域,別再亂用了!

這篇具有很好參考價值的文章主要介紹了Spring Cloud 輕松解決跨域,別再亂用了!。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

問題

在Spring Cloud項目中,前后端分離目前很常見,在調(diào)試時,會遇到兩種情況的跨域:

前端頁面通過不同域名或IP訪問微服務的后臺,例如前端人員會在本地起HttpServer 直連后臺開發(fā)本地起的服務,此時,如果不加任何配置,前端頁面的請求會被瀏覽器跨域限制攔截,所以,業(yè)務服務常常會添加如下代碼設置全局跨域:

@Bean
public CorsFilter corsFilter() {
    logger.debug("CORS限制打開");
    CorsConfiguration config = new CorsConfiguration();
    # 僅在開發(fā)環(huán)境設置為*
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    config.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
    configSource.registerCorsConfiguration("/**", config);
    return new CorsFilter(configSource);
}

前端頁面通過不同域名或IP訪問SpringCloud Gateway,例如前端人員在本地起HttpServer直連服務器的Gateway進行調(diào)試。此時,同樣會遇到跨域。需要在Gateway的配置文件中增加:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
        # 僅在開發(fā)環(huán)境設置為*
          '[/**]':
            allowedOrigins: "*"
            allowedHeaders: "*"
            allowedMethods: "*"

那么,此時直連微服務和網(wǎng)關(guān)的跨域問題都解決了,是不是很完美?

Spring Cloud 教程推薦:https://www.javastack.cn/categories/Spring-Cloud/

No~ 問題來了,前端仍然會報錯:“不允許有多個’Access-Control-Allow-Origin’ CORS頭”。

Access to XMLHttpRequest at 'http://192.168.2.137:8088/api/two' from origin 'http://localhost:3200' has been blocked by CORS policy:
The 'Access-Control-Allow-Origin' header contains multiple values '*, http://localhost:3200', but only one is allowed.

仔細查看返回的響應頭,里面包含了兩份Access-Control-Allow-Origin頭。

我們用客戶端版的PostMan做一個模擬,在請求里設置頭:Origin : * ,查看返回結(jié)果的頭:

不能用Chrome插件版,由于瀏覽器的限制,插件版設置Origin的Header是無效的

Spring Cloud 輕松解決跨域,別再亂用了!

發(fā)現(xiàn)問題了:

VaryAccess-Control-Allow-Origin 兩個頭重復了兩次,其中瀏覽器對后者有唯一性限制!

分析

Spring Cloud Gateway是基于SpringWebFlux的,所有web請求首先是交給DispatcherHandler進行處理的,將HTTP請求交給具體注冊的handler去處理。

我們知道Spring Cloud Gateway進行請求轉(zhuǎn)發(fā),是在配置文件里配置路由信息,一般都是用url predicates模式,對應的就是RoutePredicateHandlerMapping 。所以,DispatcherHandler會把請求交給 RoutePredicateHandlerMapping.

Spring Cloud 輕松解決跨域,別再亂用了!

那么,接下來看下 RoutePredicateHandlerMapping.getHandler(ServerWebExchange exchange) 方法,默認提供者是其父類 AbstractHandlerMapping

@Override
public Mono<Object> getHandler(ServerWebExchange exchange) {
    return getHandlerInternal(exchange).map(handler -> {
        if (logger.isDebugEnabled()) {
            logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
        }
        ServerHttpRequest request = exchange.getRequest();
        // 可以看到是在這一行就進行CORS判斷,兩個條件:
        // 1. 是否配置了CORS,如果不配的話,默認是返回false的
        // 2. 或者當前請求是OPTIONS請求,且頭里包含ORIGIN和ACCESS_CONTROL_REQUEST_METHOD
        if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
            CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
            config = (config != null ? config.combine(handlerConfig) : handlerConfig);
            //此處交給DefaultCorsProcessor去處理了
            if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
                return REQUEST_HANDLED_HANDLER;
            }
        }
        return handler;
    });
}

注:

網(wǎng)上有些關(guān)于修改Gateway的CORS設定的方式,是跟前面SpringBoot一樣,實現(xiàn)一個CorsWebFilter的Bean,靠寫代碼提供 CorsConfiguration ,而不是修改Gateway的配置文件。其實本質(zhì),都是將配置交給corsProcessor去處理,殊途同歸。但靠配置解決永遠比hard code來的優(yōu)雅。

該方法把Gateway里定義的所有的 GlobalFilter 加載進來,作為handler返回,但在返回前,先進行CORS校驗,獲取配置后,交給corsProcessor去處理,即DefaultCorsProcessor

看下DefaultCorsProcessor的process方法:

@Override
public boolean process(@Nullable CorsConfiguration config, ServerWebExchange exchange) {

    ServerHttpRequest request = exchange.getRequest();
    ServerHttpResponse response = exchange.getResponse();
    HttpHeaders responseHeaders = response.getHeaders();

    List<String> varyHeaders = responseHeaders.get(HttpHeaders.VARY);
    if (varyHeaders == null) {
        // 第一次進來時,肯定是空,所以加了一次VERY的頭,包含ORIGIN, ACCESS_CONTROL_REQUEST_METHOD和ACCESS_CONTROL_REQUEST_HEADERS
        responseHeaders.addAll(HttpHeaders.VARY, VARY_HEADERS);
    }
    else {
        for (String header : VARY_HEADERS) {
            if (!varyHeaders.contains(header)) {
                responseHeaders.add(HttpHeaders.VARY, header);
            }
        }
    }

    if (!CorsUtils.isCorsRequest(request)) {
        return true;
    }

    if (responseHeaders.getFirst(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(response);
            return false;
        }
        else {
            return true;
        }
    }

    return handleInternal(exchange, config, preFlightRequest);
}

// 在這個類里進行實際的CORS校驗和處理
protected boolean handleInternal(ServerWebExchange exchange,
                                 CorsConfiguration config, boolean preFlightRequest) {

    ServerHttpRequest request = exchange.getRequest();
    ServerHttpResponse response = exchange.getResponse();
    HttpHeaders responseHeaders = response.getHeaders();

    String requestOrigin = request.getHeaders().getOrigin();
    String allowOrigin = checkOrigin(config, requestOrigin);
    if (allowOrigin == null) {
        logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
        rejectRequest(response);
        return false;
    }

    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;
    }

    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;
    }
    //此處添加了AccessControllAllowOrigin的頭
    responseHeaders.setAccessControlAllowOrigin(allowOrigin);

    if (preFlightRequest) {
        responseHeaders.setAccessControlAllowMethods(allowMethods);
    }

    if (preFlightRequest && !allowHeaders.isEmpty()) {
        responseHeaders.setAccessControlAllowHeaders(allowHeaders);
    }

    if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
        responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
    }

    if (Boolean.TRUE.equals(config.getAllowCredentials())) {
        responseHeaders.setAccessControlAllowCredentials(true);
    }

    if (preFlightRequest && config.getMaxAge() != null) {
        responseHeaders.setAccessControlMaxAge(config.getMaxAge());
    }

    return true;
}

可以看到,在DefaultCorsProcessor 中,根據(jù)我們在appliation.yml 中的配置,給Response添加了 VaryAccess-Control-Allow-Origin 的頭。

Spring Cloud 輕松解決跨域,別再亂用了!

再接下來就是進入各個GlobalFilter進行處理了,其中NettyRoutingFilter 是負責實際將請求轉(zhuǎn)發(fā)給后臺微服務,并獲取Response的,重點看下代碼中filter的處理結(jié)果的部分:

Spring Cloud 輕松解決跨域,別再亂用了!

其中以下幾種header會被過濾掉的:

Spring Cloud 輕松解決跨域,別再亂用了!

很明顯,在圖里的第3步中,如果后臺服務返回的header里有 VaryAccess-Control-Allow-Origin ,這時由于是putAll,沒有做任何去重就加進去了,必然會重復,看看DEBUG結(jié)果驗證一下:

Spring Cloud 輕松解決跨域,別再亂用了!

驗證了前面的發(fā)現(xiàn)。

解決

解決的方案有兩種:

1. 利用 DedupeResponseHeader 配置:

spring:
    cloud:
        gateway:
          globalcors:
            cors-configurations:
              '[/**]':
                allowedOrigins: "*"
                allowedHeaders: "*"
                allowedMethods: "*"
          default-filters:
          - DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST

DedupeResponseHeader 加上以后會啟用DedupeResponseHeaderGatewayFilterFactory 在其中,dedupe方法可以按照給定策略處理值

private void dedupe(HttpHeaders headers, String name, Strategy strategy) {
  List<String> values = headers.get(name);
  if (values == null || values.size() <= 1) {
   return;
  }
  switch (strategy) {
  // 只保留第一個
  case RETAIN_FIRST:
   headers.set(name, values.get(0));
   break;
  // 保留最后一個
  case RETAIN_LAST:
   headers.set(name, values.get(values.size() - 1));
   break;
  // 去除值相同的
  case RETAIN_UNIQUE:
   headers.put(name, values.stream().distinct().collect(Collectors.toList()));
   break;
  default:
   break;
  }
 }
  • 如果請求中設置的Origin的值與我們自己設置的是同一個,例如生產(chǎn)環(huán)境設置的都是自己的域名xxx.com或者開發(fā)測試環(huán)境設置的都是*(瀏覽器中是無法設置Origin的值,設置了也不起作用,瀏覽器默認是當前訪問地址),那么可以選用RETAIN_UNIQUE策略,去重后返回到前端。
  • 如果請求中設置的Oringin的值與我們自己設置的不是同一個,RETAIN_UNIQUE策略就無法生效,比如 ”*“ 和 ”xxx.com“是兩個不一樣的Origin,最終還是會返回兩個Access-Control-Allow-Origin 的頭。此時,看代碼里,response的header里,先加入的是我們自己配置的Access-Control-Allow-Origin的值,所以,我們可以將策略設置為RETAIN_FIRST ,只保留我們自己設置的。

大多數(shù)情況下,我們想要返回的是我們自己設置的規(guī)則,所以直接使用RETAIN_FIRST 即可。實際上,DedupeResponseHeader 可以針對所有頭,做重復的處理。

2. 手動寫一個 CorsResponseHeaderFilterGlobalFilter 去修改Response中的頭。

@Component
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {

    private static final Logger logger = LoggerFactory.getLogger(CorsResponseHeaderFilter.class);

    private static final String ANY = "*";

    @Override
    public int getOrder() {
        // 指定此過濾器位于NettyWriteResponseFilter之后
        // 即待處理完響應體后接著處理響應頭
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
    }

    @Override
    @SuppressWarnings("serial")
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            exchange.getResponse().getHeaders().entrySet().stream()
                    .filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
                    .filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
                            || kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)
                            || kv.getKey().equals(HttpHeaders.VARY)))
                    .forEach(kv ->
                    {
                        // Vary只需要去重即可
                        if(kv.getKey().equals(HttpHeaders.VARY))
                            kv.setValue(kv.getValue().stream().distinct().collect(Collectors.toList()));
                        else{
                            List<String> value = new ArrayList<>();
                            if(kv.getValue().contains(ANY)){  //如果包含*,則取*
                                value.add(ANY);
                                kv.setValue(value);
                            }else{
                                value.add(kv.getValue().get(0)); // 否則默認取第一個
                                kv.setValue(value);
                            }
                        }
                    });
        }));
    }
}

此處有兩個地方要注意:

1)根據(jù)下圖可以看到,在取得返回值后,F(xiàn)ilter的Order 值越大,越先處理Response,而真正將Response返回到前端的,是 NettyWriteResponseFilter, 我們要想在它之前修改Response,則Order 的值必須比NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER 大。

Spring Cloud 輕松解決跨域,別再亂用了!

2)修改后置filter時,網(wǎng)上有些文字使用的是 Mono.defer去做的,這種做法,會從此filter開始,重新執(zhí)行一遍它后面的其他filter,一般我們會添加一些認證或鑒權(quán)的 GlobalFilter ,就需要在這些filter里用ServerWebExchangeUtils.isAlreadyRouted(exchange) 方法去判斷是否重復執(zhí)行,否則可能會執(zhí)行二次重復操作,所以建議使用fromRunnable 避免這種情況。

作者:EdisonXu - 徐焱飛
來源:http://edisonxu.com/2020/10/14/spring-cloud-gateway-cors.html

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協(xié)程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優(yōu)雅的方式?。?/p>

5.《Java開發(fā)手冊(嵩山版)》最新發(fā)布,速速下載!

覺得不錯,別忘了隨手點贊+轉(zhuǎn)發(fā)哦!文章來源地址http://www.zghlxwxcb.cn/news/detail-709739.html

到了這里,關(guān)于Spring Cloud 輕松解決跨域,別再亂用了!的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務器費用

相關(guān)文章

  • 解決 Axios 跨域問題,輕松實現(xiàn)接口調(diào)用

    解決 Axios 跨域問題,輕松實現(xiàn)接口調(diào)用

    跨域是指訪問另外一個域的資源,由于瀏覽器的同源策略,默認情況下使用 XMLHttpRequest 和 Fetch 請求時是不允許跨域的。跨域的根本原因是瀏覽器的同源策略,這是由瀏覽器對 JavaScript 施加的安全限制。 跨域請求被阻止 (Cross-Origin Request Blocked) : 這是由瀏覽器實施的同源策略

    2024年02月05日
    瀏覽(314)
  • 解密Spring Cloud微服務調(diào)用:如何輕松獲取請求目標方的IP和端口

    公眾號「架構(gòu)成長指南」,專注于生產(chǎn)實踐、云原生、分布式系統(tǒng)、大數(shù)據(jù)技術(shù)分享。 目的 Spring Cloud 線上微服務實例都是2個起步,如果出問題后,在沒有ELK等日志分析平臺,如何確定調(diào)用到了目標服務的那個實例,以此來排查問題 效果 可以看到服務有幾個實例是上線,并

    2024年02月05日
    瀏覽(17)
  • 【Spring Cloud】深入探索統(tǒng)一網(wǎng)關(guān) Gateway 的搭建,斷言工廠,過濾器工廠,全局過濾器以及跨域問題

    【Spring Cloud】深入探索統(tǒng)一網(wǎng)關(guān) Gateway 的搭建,斷言工廠,過濾器工廠,全局過濾器以及跨域問題

    在微服務架構(gòu)中,網(wǎng)關(guān)是至關(guān)重要的組件,具有多重職責,為整個系統(tǒng)提供了一系列關(guān)鍵功能。從下面的微服務結(jié)構(gòu)圖中,我們可以明確網(wǎng)關(guān)的幾項主要作用: 微服務結(jié)構(gòu)圖: 請求過濾與安全: 用戶的所有請求首先經(jīng)過網(wǎng)關(guān),這使得網(wǎng)關(guān)成為系統(tǒng)的第一道防線。通過對傳入

    2024年02月07日
    瀏覽(24)
  • 什么是跨域問題 ?Spring MVC 如何解決跨域問題 ?Spring Boot 如何解決跨域問題 ?

    什么是跨域問題 ?Spring MVC 如何解決跨域問題 ?Spring Boot 如何解決跨域問題 ?

    目錄 1. 什么是跨域問題 ? 2. Spring MVC 如何解決跨域問題 ? 3. Spring Boot 如何解決跨域問題 ?? 跨域問題指的是不同站點之間,使用 ajax 無法相互調(diào)用的問題。 跨域問題的 3 種情況: 1. 協(xié)議不同,例如 http 和 https; http://127.0.0.1:8080 https://127.0.0.1:8080 2. 域名不同; 一級域名、

    2024年02月10日
    瀏覽(307)
  • 別再滿屏找日志了!推薦一款 IDEA 日志管理插件,看日志輕松多了!

    別再滿屏找日志了!推薦一款 IDEA 日志管理插件,看日志輕松多了!

    Grep Console是一款方便開發(fā)者對idea控制臺輸出日志進行個性化管理的插件。 支持自定義規(guī)則來過濾日志信息; 支持不同級別的日志的輸出樣式的個性化配置; 總結(jié):通過過濾功能、輸出日志樣式配置功能,可以更方便開發(fā)者在大量的日志信息中篩選出自己比較關(guān)注的日志信息

    2024年02月07日
    瀏覽(16)
  • 【Spring security 解決跨域】

    【Spring security 解決跨域】

    主頁傳送門:?? 傳送 ??Spring Security是一個功能強大且高度可定制的,主要負責為Java程序提供聲明式的身份驗證和訪問控制的安全框架。其前身是Acegi Security,后來被收納為Spring的一個子項目,并更名為了Spring Security。Spring Security的底層主要是基于Spring AOP和Servlet過濾器來實

    2024年02月13日
    瀏覽(18)
  • Spring Security——09,解決跨域

    Spring Security——09,解決跨域

    瀏覽器出于安全的考慮,使用 XMLHttpRequest對象發(fā)起 HTTP請求時必須遵守同源策略,否則就是跨 域的HTTP請求,默認情況下是被禁止的。 同源策略要求源相同才能正常進行通信,即協(xié)議、域名、端口號都完全一致。 前后端分離項目,前端項目和后端項目一般都不是同源的,所以

    2024年04月15日
    瀏覽(22)
  • 快速解決Spring Boot跨域困擾:使用CORS實現(xiàn)無縫跨域支持

    什么是跨域? 跨域(Cross-Origin Issue)的存在是因為瀏覽器的安全限制,它防止惡意網(wǎng)站利用跨域請求來獲取用戶的敏感信息或執(zhí)行惡意操作。瀏覽器通過實施同源策略來限制網(wǎng)頁在不同源之間進行資源訪問或交互的情況。當一個網(wǎng)頁的協(xié)議、域名、或端口與當前頁面的協(xié)議、

    2024年02月12日
    瀏覽(82)
  • Spring Boot中解決跨域問題(CORS)

    Spring Boot中解決跨域問題(CORS)

    首先解釋什么是跨域,跨域就是前端和后端的端口號不同;會產(chǎn)生跨域問題,這里瀏覽器的保護機制(同源策略)。 同源策略:前端和后端的協(xié)議、域名、端口號三者都相同叫做同源。 我們看一下不同源: VUE:http://localhost:8080 Spring: http://localhost:8081/list 當我們出現(xiàn)跨域問題

    2024年02月06日
    瀏覽(88)
  • 解決Spring Boot跨域問題(配置JAVA類)

    解決Spring Boot跨域問題(配置JAVA類)

    跨域問題指的是不同端口之間,使用 ajax 無法相互調(diào)用的問題??缬騿栴}本質(zhì)是瀏覽器的一種保護機制,它是為了保證用戶的安全,防止惡意網(wǎng)站竊取數(shù)據(jù)。 比如前端用的端口號為8081,后端用的端口號為8080,后端想接收前端發(fā)送的數(shù)據(jù)就會出現(xiàn)跨域問題。 如圖所示: 這里

    2024年01月17日
    瀏覽(93)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包