當前位置: 妍妍網 > 碼農

別整復雜了!Nginx 可以輕松搞定跨域問題

2024-02-19碼農

點選關註公眾號,Java幹貨及時送達

原文:cnblogs.com/fnz0/p/15803011.html

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

分析前準備:

  • 前端網站地址:http://localhost:8080

  • 伺服端網址:http://localhost:59200

  • 首先保證伺服端是沒有處理跨域的,其次,先用postman測試伺服端介面是正常的

    當網站8080去存取伺服端介面時,就產生了跨域問題,那麽如何解決?接下來我把跨域遇到的各種情況都列舉出來並透過nginx代理的方式解決(後台也是一樣的,只要你理解的原理)。

    跨域主要涉及4個響應頭:

  • Access-Control-Allow-Origin 用於設定允許跨域請求源地址 (預檢請求和正式請求在跨域時候都會驗證)

  • Access-Control-Allow-Headers 跨域允許攜帶的特殊頭資訊欄位 (只在預檢請求驗證)

  • Access-Control-Allow-Methods 跨域允許的請求方法或者說HTTP動詞 (只在預檢請求驗證)

  • Access-Control-Allow-Credentials 是否允許跨域使用cookies,如果要跨域使用cookies,可以添加上此請求響應頭,值設為true(設定或者不設定,都不會影響請求發送,只會影響在跨域時候是否要攜帶cookies,但是如果設定,預檢請求和正式請求都需要設定)。不過不建議跨域使用(計畫中用到過,不過不穩定,有些瀏覽器帶不過去),除非必要,因為有很多方案可以代替。

  • 網上很多文章都是告訴你直接Nginx添加這幾個響應頭資訊就能解決跨域,當然大部份情況是能解決,但是我相信還是有很多情況,明明配置上了,也同樣會報跨域問題。

    什麽是預檢請求?:當發生跨域條件時候,覽器先詢問伺服器,當前網頁所在的網域名稱是否在伺服器的授權名單之中,以及可以使用哪些HTTP動詞和頭資訊欄位。只有得到肯定答復,瀏覽器才會發出正式的 XMLHttpRequest 請求,否則就報錯。如下圖


    開始動手模擬:

    Nginx代理埠:22222,配置如下

    server {
    listen 22222;
    server_name localhost;
    location / {
    proxy_pass http://localhost:59200;
    }
    }

    測試代理是否成功,透過Nginx代理埠2222再次存取介面,可以看到如下圖透過代理後介面也是能正常存取

    接下來開始用網站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 請求), 該請求成功後才會發送真正的請求。這一設計旨在確保伺服器對 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;
    }
    }

    哈哈,當滿懷歡喜的以為能解決後,發現還是報了同樣的問題

    不過我們的配置沒什麽問題,問題在Nginx

    add_header 指令用於添加返回頭欄位,若且唯若狀態碼為圖中列出的那些時有效。如果想要每次響應資訊都攜帶頭欄位資訊,需要在最後添加always(經我測試,只有 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;
    }
    }

    修改了配置後,發現生效了,當然不是跨域就解決了,是上面這個問題已經解決了,因為報錯內容已經變了

    情況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狀態碼,此時再修改配置檔,當請求為option請求時候,給瀏覽器返回一個狀態碼(一般是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;
    }
    }

    當配置完後,發現報錯資訊變了

    情況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(各種情況會不一樣,在發生跨域後,在自訂添加的頭資訊是不允許的,需要添加到請求響應頭 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; }
    }

    此時發現報錯問題又回到了情況1

    經測試驗證,只要 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;
    }
    }

    此時改完發現跨域問題已經解決了,

    不過以上雖然解決了跨域問題,但是考慮後期可能Nginx版本更新,不知道這個規則會不會被修改,考慮到這樣的寫法可能會攜帶上兩個 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;
    }
    }

    還沒完,繼續聊 ↓↓

    情況4:

    比較早期的API可能只用到了POST和GET請求,而 Access-Control-Allow-Methods 這個請求響應頭跨域預設只支持POST和GET,當出現其他請求型別時候,同樣會出現跨域異常。

    比如,我這裏將請求的API介面請求方式從原來的GET改成PUT,在發起一次試試。在控制台上會丟擲錯誤:

    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.

    報錯內容也講的很清楚,在這個預請求中,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方法去請求,所以介面會返回這個狀態碼。

    情況5:

    最後再說一種情況,就是後端處理了跨域,就不需要自己在處理了(這裏吐槽下,某些後端工程師自己改伺服端程式碼解決跨域,但是又不理解其中原理,網上隨便找段程式碼黏貼,導致響應資訊可能處理不完全,如method沒添加全,headers沒加到點上,自己用的那個可能復制過來的並不包含實際計畫所用到的,沒有添加options請求返回狀態碼等,導致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了,請求不會再轉發到59200服務,如果也刪除了,就會報和情況1一樣的錯誤。

    所以為什麽說要不伺服端程式碼層面解決跨域,要不就Nginx代理解決,不要混著搞,不然不明白原理的人,網上找一段程式碼貼就很可能解決不了問題

    再貼一份完整配置( * 號根據自己‘喜好’填寫):

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

    最後,這是一篇解決跨域遇到問題解決問題的過程,如果認真看完了,我相信應該都能很容易的理解,並且在實際使用中自己解決該問題,希望能幫助到大家,以上內容都是自己理解自己測試碼出來的,如有理解不對的地方,望大家指正。

    >>

    END

    精品資料,超贊福利,免費領

    微信掃碼/長按辨識 添加【技術交流群

    群內每天分享精品學習資料

    最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎並行JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud訊息佇列等多個型別),歡迎您的使用。

    👇👇

    👇點選"閱讀原文",獲取更多資料(持續更新中