<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試被問跨域問題,怎么破?

          共 13763字,需瀏覽 28分鐘

           ·

          2022-08-25 18:34

          點擊關注公眾號,Java干貨及時送達

          作者:酒香逢

          來源:www.cnblogs.com/fnz0/p/15803011.html

          當你遇到跨域問題,不要立刻就選擇復制去嘗試,請詳細看完這篇文章再處理,我相信它能幫到你。

          分析前準備:

          前端網(wǎng)站地址:http://localhost:8080
          服務端網(wǎng)址:http://localhost:59200

          首先保證服務端是沒有處理跨域的,其次,先用postman測試服務端接口是正常的
          當網(wǎng)站8080去訪問服務端接口時,就產(chǎn)生了跨域問題,那么如何解決?接下來我把跨域遇到的各種情況都列舉出來并通過nginx代理的方式解決(后臺也是一樣的,只要你理解的原理)。
          跨域主要涉及4個響應頭:
          • Access-Control-Allow-Origin 用于設置允許跨域請求源地址 (預檢請求和正式請求在跨域時候都會驗證)
          • Access-Control-Allow-Headers 跨域允許攜帶的特殊頭信息字段 (只在預檢請求驗證)
          • Access-Control-Allow-Methods 跨域允許的請求方法或者說HTTP動詞 (只在預檢請求驗證)
          • Access-Control-Allow-Credentials 是否允許跨域使用cookies,如果要跨域使用cookies,可以添加上此請求響應頭,值設為true(設置或者不設置,都不會影響請求發(fā)送,只會影響在跨域時候是否要攜帶cookies,但是如果設置,預檢請求和正式請求都需要設置)。不過不建議跨域使用(項目中用到過,不過不穩(wěn)定,有些瀏覽器帶不過去),除非必要,因為有很多方案可以代替。
          網(wǎng)上很多文章都是告訴你直接Nginx添加這幾個響應頭信息就能解決跨域,當然大部分情況是能解決,但是我相信還是有很多情況,明明配置上了,也同樣會報跨域問題。
          什么是預檢請求?
          當發(fā)生跨域條件時候,覽器先詢問服務器,當前網(wǎng)頁所在的域名是否在服務器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答復,瀏覽器才會發(fā)出正式的XMLHttpRequest請求,否則就報錯。如下圖

          開始動手模擬:

          Nginx代理端口:22222 ,配置如下
          server {
              listen       22222;
              server_name  localhost;
              location  / {
                  proxy_pass  http://localhost:59200;
              }
          }
          測試代理是否成功,通過Nginx代理端口2222再次訪問接口,可以看到如下圖通過代理后接口也是能正常訪問
          接下來開始用網(wǎng)站8080訪問Nginx代理后的接口地址,報錯情況如下↓↓↓

          情況1:

          Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

          通過錯誤信息可以很清晰的定位到錯誤(注意看標紅部分)priflight說明是個預請求,CORS 機制跨域會首先進行 preflight(一個 OPTIONS 請求), 該請求成功后才會發(fā)送真正的請求。這一設計旨在確保服務器對 CORS 標準知情,以保護不支持 CORS 的舊服務器
          通過錯誤信息,我們可以得到是預檢請求的請求響應頭缺少了 Access-Control-Allow-Origin,錯哪里,我們改哪里就好了。修改Nginx配置信息如下(紅色部分為添加部分),缺什么就補什么,很簡單明了
          server {
              listen       22222;
              server_name  localhost;
              location  / {
                 add_header Access-Control-Allow-Origin 'http://localhost:8080';
                 proxy_pass  http://localhost:59200;
              }
          }
          哈哈,當滿懷歡喜的以為能解決后,發(fā)現(xiàn)還是報了同樣的問題
          不過我們的配置沒什么問題,問題在Nginx,下圖鏈接http://nginx.org/en/docs/http/ngx_http_headers_module.html
          add_header 指令用于添加返回頭字段,當且僅當狀態(tài)碼為圖中列出的那些時有效。如果想要每次響應信息都攜帶頭字段信息,需要在最后添加always(經(jīng)我測試,只有Access-Control-Allow-Origin這個頭信息需要加always,其他的不加always也會攜帶回來),那我們加上試試
          server {
              listen       22222;
              server_name  localhost;
              location  / {
                 add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
                 proxy_pass  http://localhost:59200;
              }
          }
          修改了配置后,發(fā)現(xiàn)生效了,當然不是跨域就解決了,是上面這個問題已經(jīng)解決了,因為報錯內(nèi)容已經(jīng)變了

          情況2:

          Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

          通過報錯信息提示可以得知,是跨域瀏覽器默認行為的預請求(option請求)沒有收到ok狀態(tài)碼,此時再修改配置文件,當請求為option請求時候,給瀏覽器返回一個狀態(tài)碼(一般是204)
          server {
              listen       22222;
              server_name  localhost;
              location  / {
                 add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
                 if ($request_method = 'OPTIONS') {
                      return 204;
                 }
                 proxy_pass  http://localhost:59200;
              }
          }
          當配置完后,發(fā)現(xiàn)報錯信息變了

          情況3:

          Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.

          意思就是預請求響應頭Access-Control-Allow-Headers中缺少頭信息authorization(各種情況會不一樣,在發(fā)生跨域后,在自定義添加的頭信息是不允許的,需要添加到請求響應頭Access-Control-Allow-Headers中,以便瀏覽器知道此頭信息的攜帶是服務器承認合法的,我這里攜帶的是authorization,其他的可能是token之類的,缺什么加什么),知道了問題所在,然后修改配置文件,添加對應缺少的部分,再試試
          server {
              listen       22222;
              server_name  localhost;
              location  / {
                 add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
                 if ($request_method = 'OPTIONS') {
                     add_header Access-Control-Allow-Headers 'authorization'#為什么寫在if里面而不是接著Access-Control-Allow-Origin往下寫?因為這里只有預檢請求才會檢查
                     return 204;
                }
              proxy_pass http://localhost:59200;
          }
          }
          此時發(fā)現(xiàn)報錯問題又回到了情況1
          經(jīng)測試驗證,只要if ($request_method = 'OPTIONS') 里面寫了 add_header ,當為預檢請求時外部配置的都會失效,為什么?↓↓。
          官方文檔是這樣說的:

          There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

          意思就是當前層級無 add_header 指令時,則繼承上一層級的add_header。相反的若當前層級有了add_header,就應該無法繼承上一層的add_header。

          配置修改如下:
          server {
              listen       22222;
              server_name  localhost;
              location  / {
                  add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
                  if ($request_method = 'OPTIONS') {
                      add_header Access-Control-Allow-Origin 'http://localhost:8080';
                      add_header Access-Control-Allow-Headers 'authorization';
                      return 204;
                  }
                  proxy_pass  http://localhost:59200;
              }
          }
          此時改完發(fā)現(xiàn)跨域問題已經(jīng)解決了,
          不過以上雖然解決了跨域問題,但是考慮后期可能Nginx版本更新,不知道這個規(guī)則會不會被修改,考慮到這樣的寫法可能會攜帶上兩個 Access-Control-Allow-Origin ,這種情況也是不允許的,下面會說到。所以配置適當修改如下:
          server {
              listen       22222;
              server_name  localhost;
              location  / {
                  if ($request_method = 'OPTIONS') {
                      add_header Access-Control-Allow-Origin 'http://localhost:8080';
                      add_header Access-Control-Allow-Headers 'authorization';
                      return 204;
                  }
                  if ($request_method != 'OPTIONS') {
                      add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
                  }
                  proxy_pass  http://localhost:59200;
              }
          }
          還沒完,繼續(xù)聊 ↓↓最新 Nginx 面試題整理好了,大家可以在Java面試庫小程序在線刷題。

          情況4:

          比較早期的API可能只用到了POST和GET請求,而Access-Control-Allow-Methods這個請求響應頭跨域默認只支持POST和GET,當出現(xiàn)其他請求類型時候,同樣會出現(xiàn)跨域異常。
          比如,我這里將請求的API接口請求方式從原來的GET改成PUT,在發(fā)起一次試試。在控制臺上會拋出錯誤:

          Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

          報錯內(nèi)容也講的很清楚,在這個預請求中,PUT方法是不允許在跨域中使用的,我們需要改下Access-Control-Allow-Methods的配置(缺什么加上么,這里我只加了PUT,可以自己加全一點),讓瀏覽器知道服務端是允許的
          server {
              listen 22222;
              server_name localhost;
              location / {
                  if ($request_method = 'OPTIONS') {
                      add_header Access-Control-Allow-Origin 'http://localhost:8080';
                      add_header Access-Control-Allow-Headers 'content-type,authorization';
                      add_header Access-Control-Allow-Methods 'PUT';#為這么只加在這個if中,不再下面的if也加上?因為這里只有預檢請求會校驗,當然你加上也沒事。
                      return 204;
                  }
                  if ($request_method != 'OPTIONS') {
                      add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
                  }
                  proxy_pass http://localhost:59200;
              }
          }
          這里注意一下,改成PUT類型后,Access-Control-Allow-Headers請求響應頭又會自動校驗content-type這個請求頭,和情況3是一樣的,缺啥補啥就行了。如果不加上content-type,則會報如下錯誤。(想簡單的話,Access-Control-Allow-Headers和Access-Control-Allow-Methods可以設置為 * ,表示全都匹配。但是Access-Control-Allow-Origin就不建議設置成 * 了,為了安全考慮,限制域名是很有必要的。)
          都加上后,問題就解決了,這里報405是我服務端這個接口只開放了GET,沒有開放PUT,而此刻我將此接口用PUT方法去請求,所以接口會返回這個狀態(tài)碼。

          情況5:

          最后再說一種情況,就是后端處理了跨域,就不需要自己在處理了(這里吐槽下,某些后端工程師自己改服務端代碼解決跨域,但是又不理解其中原理,網(wǎng)上隨便找段代碼黏貼,導致響應信息可能處理不完全,如method沒添加全,headers沒加到點上,自己用的那個可能復制過來的并不包含實際項目所用到的,沒有添加options請求返回狀態(tài)碼等,導致Nginx再用通用的配置就會可能報以下異常)

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



          意思就是此刻Access-Control-Allow-Origin請求響應頭返回了多個,而只允許有一個,這種情況當然修改配置去掉Access-Control-Allow-Origin這個配置就可以了,不過遇到這種情況,建議Nginx配置和服務端自己解決跨域只選其一。(這里注意如果按我上面的寫法,if $request_method = 'OPTIONS' 這個里面的Access-Control-Allow-Origin可不能刪除,刪除!='OPTIONS'里面的就好了,因為這里如果是預檢請求直接就ruturn了,請求不會再轉發(fā)到59200服務,如果也刪除了,就會報和情況1一樣的錯誤。所以為什么說要不服務端代碼層面解決跨域,要不就Nginx代理解決,不要混著搞,不然不明白原理的人,網(wǎng)上找一段代碼貼就很可能解決不了問題)

          ↓ ↓ ↓ ↓ ↓

          再貼一份完整配置(*號根據(jù)自己‘喜好’填寫):
          server {
              listen       22222;
              server_name  localhost;
              location  / {
                  if ($request_method = 'OPTIONS') {
                      add_header Access-Control-Allow-Origin 'http://localhost:8080';
                      add_header Access-Control-Allow-Headers '*';
                      add_header Access-Control-Allow-Methods '*';
                      add_header Access-Control-Allow-Credentials 'true';
                      return 204;
                  }
                  if ($request_method != 'OPTIONS') {
                      add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
                      add_header Access-Control-Allow-Credentials 'true';
                  }
                  proxy_pass  http://localhost:59200;
              }
          }

          或者:

          server {
              listen       22222;
              server_name  localhost;
              location  / {
                  add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
                  add_header Access-Control-Allow-Headers '*';
                  add_header Access-Control-Allow-Methods '*';
                  add_header Access-Control-Allow-Credentials 'true';
                  if ($request_method = 'OPTIONS') {
                      return 204;
                  }
                  proxy_pass  http://localhost:59200;
              }
          }
          最后,這是一篇解決跨域遇到問題解決問題的過程,如果認真看完了,我相信應該都能很容易的理解,并且在實際使用中自己解決該問題,希望能幫助到大家,以上內(nèi)容都是自己理解自己測試碼出來的,如有理解不對的地方,望大家指正。

            

          1、社區(qū)糾紛不斷:程序員何苦為難程序員?

          2、該死的單元測試,寫起來到底有多痛?

          3、互聯(lián)網(wǎng)人為什么學不會擺爛

          4、為什么國外JetBrains做 IDE 就可以養(yǎng)活自己,國內(nèi)不行?區(qū)別在哪?

          5、相比高人氣的Rust、Go,為何 Java、C 在工具層面進展緩慢?

          6、讓程序員早點下班的《技術寫作指南》

          點分享

          點收藏

          點點贊

          點在看

          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩小电影 | 台湾一区二区 | 精品无人区无码乱码毛片国产 | 无码毛片一区二区三区视频免费看 | 操鼻素材大全网站免费 |