什么是跨域 & 跨域的3種解決方案


什么是跨域

所謂同源(即指在同一個域)就是兩個頁面具有相同的協議(protocol),主機(host)端口號(port)

同源策略是瀏覽器的一個安全功能,不同源的客戶端腳本在沒有明確授權的情況下,不能讀寫對方資源。 同源策略是瀏覽器安全的基石

同源策略會阻止一個域的 javascript 腳本和另外一個域的內容進行交互。例如辦公內外網環境,當我們訪問外網一個惡意網站的時候,惡意網站就會利用我們的主機向內網的 url 發送 ajax 請求,破壞或盜取數據

 當一個請求url的協議、域名、端口三者之間任意一個與當前頁面url不同即為跨域

當前頁面url 被請求頁面url 是否跨域 原因
http://www.test.com/ http://www.test.com/index.html 同源(協議、域名、端口號相同)
http://www.test.com/ https://www.test.com/index.html 跨域 協議不同(http/https)
http://www.test.com/ http://www.baidu.com/ 跨域 主域名不同(test/baidu)
http://www.test.com/ http://blog.test.com/ 跨域 子域名不同(www/blog)
http://www.test.com:8080/ http://www.test.com:7001/ 跨域 端口號不同(8080/7001)

瀏覽器的非同源限制以及3種解決思路#

非同源限制

  1. 無法讀取非同源網頁的 Cookie、LocalStorage 和 IndexedDB
  2. 無法接觸非同源網頁的 DOM
  3. 無法向非同源地址發送 AJAX 請求,即 XHR 請求

跨域的解決思路 1 —— 避免非同源限制

  1. 讓瀏覽器不做限制,指定參數,讓瀏覽器不做校驗,但該方法不太合理,因為它需要每個人都去做改動
  2. 不要發出 XHR 請求,這樣就算是跨域,瀏覽器也不會有非同源限制,解決方案是 JSONP,通過動態創建一個 script,通過 script 發出請求

跨域的解決思路 2 —— 跨源資源共享方案

  1. 根據 W3C 的跨源資源共享方案,在被調用方修改代碼,加上字段,告訴瀏覽器該網站支持跨域

跨域的解決思路 3 —— 隱藏跨域

  1. 使用 Nginx 反向代理,在 a 域名里面的的請求地址使用反向代理指向 b 域名,讓瀏覽器以為一直在訪問 a 網站,不觸發跨域限制

JSONP#

  • 普通請求值 XHR,希望得到服務端返回的 content-type 一般是 json
  • JSONP 發出的是 script 請求,希望得到的返回是 js 腳本
    Content-Type 是指 http/https 發送信息至服務端時的內容編碼類型,在 HTTP 協議消息頭中,使用 Content-Type 來表示請求和響應中的媒體類型信息。它用來告訴服務端如何處理請求的數據,以及告訴客戶端(一般是瀏覽器)如何解析響應的數據,比如顯示圖片,解析並展示 html 等等。
    
        並不是請求或響應獨有的參數

    什么是跨域 & 跨域的3種解決方案

    所謂同源(即指在同一個域)就是兩個頁面具有相同的協議(protocol),主機(host)端口號(port)

    同源策略是瀏覽器的一個安全功能,不同源的客戶端腳本在沒有明確授權的情況下,不能讀寫對方資源。 同源策略是瀏覽器安全的基石

    同源策略會阻止一個域的 javascript 腳本和另外一個域的內容進行交互。例如辦公內外網環境,當我們訪問外網一個惡意網站的時候,惡意網站就會利用我們的主機向內網的 url 發送 ajax 請求,破壞或盜取數據

    瀏覽器的非同源限制以及3種解決思路#

    非同源限制

    1. 無法讀取非同源網頁的 Cookie、LocalStorage 和 IndexedDB
    2. 無法接觸非同源網頁的 DOM
    3. 無法向非同源地址發送 AJAX 請求,即 XHR 請求

    跨域的解決思路 1 —— 避免非同源限制

    1. 讓瀏覽器不做限制,指定參數,讓瀏覽器不做校驗,但該方法不太合理,因為它需要每個人都去做改動
    2. 不要發出 XHR 請求,這樣就算是跨域,瀏覽器也不會有非同源限制,解決方案是 JSONP,通過動態創建一個 script,通過 script 發出請求

    跨域的解決思路 2 —— 跨源資源共享方案

    1. 根據 W3C 的跨源資源共享方案,在被調用方修改代碼,加上字段,告訴瀏覽器該網站支持跨域

    跨域的解決思路 3 —— 隱藏跨域

    1. 使用 Nginx 反向代理,在 a 域名里面的的請求地址使用反向代理指向 b 域名,讓瀏覽器以為一直在訪問 a 網站,不觸發跨域限制

    JSONP#

    • 普通請求值 XHR,希望得到服務端返回的 content-type 一般是 json
    • JSONP 發出的是 script 請求,希望得到的返回是 js 腳本

    Content-Type 是指 http/https 發送信息至服務端時的內容編碼類型,在 HTTP 協議消息頭中,使用 Content-Type 來表示請求和響應中的媒體類型信息。它用來告訴服務端如何處理請求的數據,以及告訴客戶端(一般是瀏覽器)如何解析響應的數據,比如顯示圖片,解析並展示 html 等等。

    並不是請求或響應獨有的參數

    JSONP 原理#

    以 JQuery 為例,發送 ajax 請求的時候,設置dataType:"jsonp",將使用 JSONP 方式調用函數,函數的 url 變為myurl?callback=e5bbttt的形式,e5bbttt 就是一個臨時方法名,后端會根據callback的值返回一個 js 腳本,如

    <script>
     e5bbttt({"a":"aaa","b":"bbb"});
    </script>

    Query 會提前根據 ajax 中 success 的內容生成一個臨時函數,名字就是 xxx

    $.ajax({
    // 其他省略
      dataType:"jsonp",
      success:function(data){
      console.log(data.a);
      console.log(data.b);
    }, jsonp:
    "e5bbttt" }) //JQuery 生成的臨時函數 function e5bbttt(data){ ajaxObject.success(data); }

    服務端返回給客戶端的e5bbttt({"a":"aaa","b":"bbb"});,相當於立即調用了 JQuery 生成的e5bbttt函數,用完這個函數就銷毀了

    JSONP 也算是一個約定俗成的“協議”,callback 是約定俗成的作為定義臨時函數名的參數。如果想自定義這個參數名,需要在 ajax 中用 jsonp 屬性定義。

     JSONP 的弊端#

    1. 需要服務器改動代碼
    2. 只支持 GET 請求
    3. 發送的不是 xhr 請求
    4. 不安全

    后端解決跨域

    在服務器端解決跨域有2種解決思路

    • 在被調用后端應用解決:在響應頭增加指定字段,告訴瀏覽器允許調用。這種解決方案的請求是直接從瀏覽器發送給后端服務器,在瀏覽器上會看到 b.com 的 url
    • 在前端服務器解決:這是隱藏跨域的解決方案。這種跨域請求不是直接從瀏覽器發送的,而是從中間的 http 服務器(前端應用所在服務器)轉發過去的,在瀏覽器中看到的還是 a.com 的 url,所以不會認為是跨域。但是該到 b.com 的請求還是會到 b.com

    跨域原理及后端解決思路#

    依據瀏覽器同源策略,非同源腳本不可操作其他源下面的對象。想要操作其他源下的對象就需要跨域。綜上所述,在同源策略的限制下,非同源的網站之間不能發送 ajax 請求。如有需要,可通過降域或其他技術實現。

    為了解決瀏覽器跨域問題,W3C 提出了跨源資源共享方案,即 CORS(Cross-Origin Resource Sharing)。

    CORS 可以在不破壞即有規則的情況下,通過后端服務器實現 CORS 接口,就可以實現跨域通信。

    CORS 將請求分為兩類:簡單請求和非簡單請求,分別對跨域通信提供了支持。

    簡單請求#

    1. 在 CORS 出現前,發送 HTTP 請求時在頭信息中不能包含任何自定義字段,且 HTTP 頭信息不超過以下幾個字段:
      1. Accept
      2. Accept-Language
      3. Content-Language
      4. Last-Event-ID
      5. Content-Type 僅為這3種
        • application/x-www-form-urlencoded
        • multipart/form-data
        • text/plain
    2. 一個簡單請求:請求方法是 GET HEAD POST 且滿足條件1
      GET /test HTTP/1.1
      Accept: */*
      Accept-Encoding: gzip, deflate, sdch, br
      Origin: http://www.test.com
      Host: www.test.com

      對於簡單請求,CORS 的策略是請求時在請求頭中增加一個 Origin 字段,表示請求發出的域。服務器收到請求后,根據該字段判斷是否允許該請求訪問。

      • 如果允許,則在 HTTP 頭信息中添加 Access-Control-Allow-Origin 字段,並返回正確的結果
      • 如果不允許,則不添加 Access-Control-Allow-Origin 字段

      除了上面提到的 Access-Control-Allow-Origin,還有幾個字段用於描述 CORS 返回結果

      • Access-Control-Allow-Credentials:可選,用戶是否可以發送、處理cookie
      • Access-Control-Expose-Headers:可選,可以讓用戶拿到的字段。有幾個字段無論是否允許跨域都可以拿到的:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma

    非簡單請求#

  • 一般是發送 JSON 格式的 ajax 請求,或帶有自定義頭的請求

    對於非簡單請求的跨源請求,瀏覽器會在真實請求發出前,增加一次 OPTION 請求,稱為預檢請求(preflightrequest)。預檢請求將真實請求的信息,包括請求方法、自定義頭字段、源信息添加到 HTTP 頭信息字段中,詢問服務器是否允許這樣的操作

    例如一個 GET 請求的預檢請求,包含一個自定義參數 X-Custom-Header

    OPTIONS /test HTTP/1.1
    Origin: http://www.test.com
    Access-Control-Request-Method: GET // 請求使用的 HTTP 方法
    Access-Control-Request-Headers: X-Custom-Header // 請求中包含的自定義頭字段
    Host: www.test.com

    服務器收到請求時,需要分別對 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 進行驗證,驗證通過后,會在返回 HTTP 頭信息中添加:

    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: http://www.test.com // 允許的域
    Access-Control-Allow-Methods: GET, POST, PUT, DELETE // 允許的方法
    Access-Control-Allow-Headers: X-Custom-Header // 允許的自定義字段
    Access-Control-Allow-Credentials: true // 是否允許用戶發送、處理 cookie
    Access-Control-Max-Age: 172800 // 預檢請求的有效期,單位為秒。有效期內,不需要發送預檢請求,ps 48小時

    當預檢請求通過后,瀏覽器才會發送真實請求到服務器。這樣就實現了跨域資源的請求訪問。

    所以后端處理其實處理的就是這次預檢請求

  • 注意:

    在 Chrome 和 Firefox 中,如果 Access-Control-Allow-Methods 中並未允許 GET/POST/HEAD 請求,但允許跨域了,瀏覽器還是會允許 GET/POST/HEAD 這些簡單請求訪問,這時就必須在后台用其他辦法禁掉這些 Method

    后端應用處理 - Filter&HttpServletResponse 方法#

    這種方法不會用到 Spring,對 Servlet 也可以使用

    在 web.xml 中配置

    <!-- 跨域 -->
    <filter>
      <filter-name>webFliter</filter-name>
      <filter-class>com.n031.filter.WebFliter</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>webFliter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>

    編寫 java 類

    import javax.servlet.*;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    public class WebFliter implements Filter {
        @Override
      public void init(FilterConfig filterConfig) throws ServletException {
    
        }
       @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
          HttpServletRequest req = (HttpServletRequest)request;
          HttpServletResponse res = (HttpServletResponse) response;
          // 允許跨域的域名,設置*表示允許所有域名
          String origin = req.getHeader("Origin");
           if ("abcdefg".contains(origin)) {  // 滿足指定的條件
             res.addHeader("Access-Control-Allow-Origin", origin);
           }
            res.addHeader("Access-Control-Allow-Origin", "http://www.test.com");
          // 允許跨域的方法,可設置*表示所有
            res.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
          // 允許的自定義字段
       String headers = req.getHeader("Access-Control-Request-Headers"); // 獲取 request 發來的自定義字段
        res.addHeader("Access-Control-Allow-Headers", headers);
         // 或者
         // res.addHeader("Access-Control-Allow-Headers", "X-Custom-Header");
        // 預檢請求的有效期,單位為秒。有效期內,不需要發送預檢請求,ps 48小時
         res.addHeader("Access-Control-Max-Age", "172800");
        // 還可以有其他配置...
           chain.doFilter(request, response);
      }
    
    
        @Override
    
        public void destroy() {
    
        }
    
    
    }

    后端應用處理 - Spring 方法#

    Spring 解決跨域的方法很多,感覺就和茴字有五種寫法一樣。這里列舉的並不全。

    先看下原理。說實話雖然搞不懂為什么這么做,但看了下這個類的源碼確實是這么寫的。

    本質都是構造CorsConfiguration然后委托給DefaultCorsProcessor實現

    public class CorsConfiguration {
        private List<String> allowedOrigins;
        private List<String> allowedMethods;
        private List<String> allowedHeaders;
        private List<String> exposedHeaders;
        private Boolean allowCredentials;
        private Long maxAge;
    
    }

    DefaultCorsProcessorprocessRequest處理步驟如下(spring-web 5.1.8-RELEASE

    1. 判斷是否是包含 Origin 字段,不包含就放行,否則繼續判斷
    2. 判斷 Response 的 Header 是否已經包含 Access-Control-Allow-Origin。如果包含,證明已經被處理過了,放行,否則繼續判斷
    3. 判斷是否同源,如果是則放行,否則繼續判斷
    4. 到此步基本已經得出這是個跨域請求的結論。然后看配置了 CORS 規則
      • 沒有配置,且是預檢請求,則拒絕該請求(說明該應用禁止跨域)
      • 沒有配置,且不是預檢請求,跳過跨域處理(有可能導致返回數據被瀏覽器攔截)
      • 配置了,則根據配置的規則(CorsConfiguration)決定是否放行

    在 Controller 上添加 @CrossOrigin 注解#

    這種方式適合只有一兩個 rest 接口需要跨域或者沒有網關的情況下,這種處理方式就非常簡單,適合在原來基代碼基礎上修改,影響比較小。

     


免責聲明!

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



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