getway網關跨域問題記錄


一.問題產生環境

1.1 為什么會產生跨域問題?

跨域不一定都會有跨域題。

因為跨域問題是瀏覽器對於ajax請求的一種安全限制;

一個頁面發起的 ajax請求,只能是與當前頁域名相同的路徑,這能有效的阻止跨站攻擊;

因此:

跨域問題是針對ajax的一種限制但是這卻給我們的開發帯來了不便,而且在實際生產環境中,

肯定會有很多台服務器之間交互,地址和端口都可能不同,怎么辦?

 

1.2  因為公司微服務項目是前后端分離,前后分離后采用了SpringCloud Getway網關技術,這樣請求會經歷三個流程:

1.前端請求  ===》2.網關 ==》3.后端接口

1.這是單獨項目的前段代碼:

復制代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" /> 
<title>Insert title here</title>
</head>
<!--JQuery在線引用-->
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
        function form_btn(){
                 var list = JSON.stringify([    
                        {phone: "17320427943", region_code: "620100"},    
                        {phone: "17320427943", region_code: "620100"}    
                    ]);
                 alert(list);
            $.ajax({
                url: "http://localhost:8083/p1upgrade/app/api/delPhoneWarnConf?token=1_grand",
                data: list,
                type: "POST",
                contentType: "application/json;charset=utf-8",
                success: function(result){
                var list = eval(result);//解析json  
                  alert(list.data);
            }
            });
        } 
    
</script>
<body>
    <h1>springboot訪問第一個html頁面</h1>
    <button onclick="form_btn()">提交</button>
</body>
</html>
復制代碼

 

 2.網關部分的代碼:application.yml

復制代碼
server:
  port: 8083
spring:
  cloud:
    gateway:
      routes:
      - id: json_route
        uri: http://192.168.1.206:8080
        predicates:
        - Header=Accept, .*json.*
      - id: binary_route1
        uri: http://192.168.1.206:8080
        predicates:
        - Path=/p1upgrade/app/api/**
      - id: userCheck121
        uri: http://192.168.1.206:8080
        predicates:
        - Path=/p1upgrade/sysManage/userCheck121
      - id: appPicManage
        uri: http://192.168.1.206:8080
        predicates:
        - Path=/p1upgrade/picManage/**
      - id: path_route
        uri: http://192.168.1.206:8080
        predicates:
        - Path=/**
復制代碼

3.后端接口部分代碼:只是問題重現,接口部分代碼沒有優化

復制代碼
@RequestMapping("delPhoneWarnConf")
    public void delPhoneWarnConf(HttpServletResponse rps,
            @RequestBody List<Map<String,Object>> list) {
        rps.setContentType("application/json;charset=UTF-8");
        List<Map<String, Object>> maps=list;
        Integer rtcount=0;
        for (Object object : maps) {
            Map <String,Object> ret = (Map<String, Object>) object;//取出list里面的值轉為map
            rtcount+=alarmConfigService.delPhoneWarnConf(ret.get("phone").toString(),ret.get("region_code").toString());
        }
        JSONObject result=SystemUtils.responseBody(0, "success", rtcount);
        try {
            rps.getWriter().write(result.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
復制代碼

 

二.錯誤現象

三.解決辦法

    目前常用的跨域解決方案有三種:

     (1) jsonp:

                        最早的解決方案,利用script標簽可以跨域的原理實現

                  缺點:需要服務的支持,

        只能發起get請求

    (2)nginx反向代理:

                  思路是利用nginx把跨域反向代理為不跨域,支持各種請求方式

              缺點:需要nginx進行額外配置,語義不清晰

    (3)CORS:

                     思路:規范化的跨域請求方案,安全可靠

      優勢:在服務端進行控制是否允許跨域,靠自定義規則。支持各種請求方式。

                      缺點:會產生額外的請求

 

 這里一般會采用第三種CORS的跨域方案;

四.什么是CORS

CORS是一個w3c標准,全稱是"跨域資源共享"(Cross-origin resource sharing),

但一個請求url的協議,域名,端口三者之間任意與當前頁面地址不同即為跨域.它允許閱覽器向跨源服務器發送XMLHttpRequest請求,從而克服AJAX只能同源使用的限制.

 

CORS需要瀏覽需和服務器同時支持。

     瀏覽器端:

               目前,所有瀏覽器都支持該功能,瀏覽器不能低於IE10(E10以下不行)。整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。

   服務端:

    CORS通信與AAX沒有任何差別,因此你不需要改變以前的業務邏輯。只不過,瀏覽器會在請求中攜帶一些頭信息,我們需要以此判斷是否允許其域,然后在響應頭中加入一些信息即可。這一般通過過濾器完成即可

 

五.原理

非簡單請求的CORS請求,會在正式通信前進行一次Http查詢請求,又稱預檢請求。

瀏覽器先請求服務器,當前網頁所在域名是否在服務器許可名單中以及可以使用那些HTTP動詞和頭信息字段,當客戶端得到肯定答復時,瀏覽器才會正式發出XMLHttpRequest請求。否則就會報錯。

    

預檢”請求的樣板:

OPTIONS /cors HTTP/1.1 
Access-Control-Request-Headers: content-type Access-Control-Request-Method: POST Origin: http://localhost:8082 Referer: http://localhost:8082/hello User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
Origin :指出當前請求屬於哪個域(協議+域名+端口)。服務會根據這個值決定是否允許其跨域
Access-Control-Request-Method: 接下來會用到的請求方式get,post,put等 
Access-Control-Request-Headers:會額外用到的頭信息
User-Agent: 瀏覽器代理

“預檢”請求的響應:

 

 六.實現網關跨域請求

雖然原理比較復雜,但是操作起來沒那么難:

  •   瀏覽器端都由瀏覽器自動完成,我們無需操心
  • 服務端可以通過攔截器統一實現,不必每次都去進行跨域判定的編寫

事實上,Spring已經幫我們寫好了CORS的跨域過濾器,內部已經實現了判定邏輯。

  • spring-webmvc:CorsFilter
  • spring-webflux:CorsWebFilter

springcloud_gateway集成的是webflux,所以這里使用的是CorsWebFilter

6.1 在啟動類中編寫一個配置類,並且注冊CorsWebFilter

復制代碼
    @Bean
    public CorsWebFilter  corsWebFilter() {
        //cors跨域對象
         CorsConfiguration config = new CorsConfiguration();
         config.addAllowedOrigin("http://localhost:8082");// #允許向該服務器提交請求的URI,*表示全部允許,在SpringMVC中,如果設成*,會自動轉成當前請求頭中的Origin
         config.setAllowCredentials(true); // 允許cookies跨域
         config.addAllowedMethod("*");// 允許提交請求的方法,*表示全部允許
         config.addAllowedHeader("*");// #允許訪問的頭信息,*表示全部
         config.setMaxAge(18000L);// 預檢請求的緩存時間(秒),即在這個時間段里,對於相同的跨域請求不會再預檢了

           
         //cors過濾對象
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
         source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
復制代碼

 

6.2 CorsWebFilter實現的是WebFilter,所以也可以在啟動類中編寫一個配置類,並且注冊WebFilter,代碼如下,

 

復制代碼
 1 @Bean
 2     public WebFilter corsFilter() {
 3         return new WebFilter() {
 4 
 5             @Override
 6             public Mono<Void> filter(ServerWebExchange ctx, WebFilterChain chain) {
 7                     ServerHttpRequest request = ctx.getRequest();
 8                     if (CorsUtils.isCorsRequest(request)) {
 9                         ServerHttpResponse response = ctx.getResponse();
10                         HttpHeaders headers = response.getHeaders();
11                         headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
12                         headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,"*");
13                         headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS,"*");
14                         headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
15                         headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
16                         headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
17                         if (request.getMethod() == HttpMethod.OPTIONS) {
18                             response.setStatusCode(HttpStatus.OK);
19                             return Mono.empty();
20                         }
21                     }
22                     return chain.filter(ctx);
23             }
24             
25         };
26     }
復制代碼

 

 

以上6.1和6.2兩種方案經過測試都是可以的,任意采用一種即可,建議采用6.1代碼相對簡單點,

6.3 效果展示

 

 

 

 

 

 

 七.因為網關和后端項目都有配置跨域配置,會導致另一種報錯

7.1報錯現象

 

 

 響應頭信息:

 

 

7.2 接口返回狀態已經是200,說明已經訪問成功了,但是因為項目后端啟動之前也配置過跨域配置,所以會與網關配置沖突,導致響應頭中返回了兩個可接受的域,正常一般是一個,導致報錯還存在,
后端啟動類跨域配置,主要是對主接口跨越進行控制,
復制代碼
//springcloud-前端跨域問題的解決方案全局配置
    @Bean
    public WebMvcConfigurerAdapter corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/anzhou/*").allowCredentials(true).allowedMethods("*").maxAge(3600).allowedHeaders("*");
                registry.addMapping("/sysManage/*").allowedOrigins("*").allowCredentials(true).allowedMethods("*").maxAge(3600).allowedHeaders("*");
                registry.addMapping("/sysManage/*").allowedOrigins("*").allowCredentials(true).allowedMethods("*").maxAge(3600).allowedHeaders("*");
                registry.addMapping("/polluSource/*").allowedOrigins("*").allowCredentials(true).allowedMethods("*").maxAge(3600).allowedHeaders("*");
                registry.addMapping("/app/api/*").allowedOrigins("*").allowCredentials(true).allowedMethods("*").maxAge(3600).allowedHeaders("*");
                registry.addMapping("/checkManagement/*").allowCredentials(true).allowedMethods("*").maxAge(3600).allowedHeaders("*");
                registry.addMapping("/minisite/*").allowedOrigins("*").allowCredentials(true).allowedMethods("*").maxAge(3600).allowedHeaders("*");
                registry.addMapping("/**").allowedOrigins("*").allowCredentials(true).allowedMethods("*").maxAge(3600).allowedHeaders("*");
            }
        };
    }
復制代碼

所以,應該原因應該是沖突了,但是網關不配置跨域那么請求在網關就被攔截了,根本不會訪問到后台接口,所以最后,最好的解決辦法就是把跨域配置全都配置在網關中,由網關統一管理,


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM