24.什麼是跨域?解決方案有哪些?

程序员子龙發表於2024-04-30

為什麼會出現跨域問題

存在瀏覽器同源策略,所以才會有跨域問題。那麼瀏覽器是出於何種原因會有跨域的限制呢。其實不難想到,跨域限制主要的目的就是為了使用者的上網安全。

同源策略導致的跨域是瀏覽器單方面拒絕響應資料,伺服器端是處理完畢並做出了響應的。

什麼是同源策略

一個url由三部分組成:協議,域名(ip地址),埠

只有當協議,域名,埠都一致的時候,才被稱為同源。

而同源策略規定,只有傳送請求的那一邊和接受請求的那一邊處於同源的情況下,瀏覽器才會接受響應。

常見的跨域場景:

而當我們的請求不符合同源策略的時候。往往會出現以下錯誤👇

什麼是跨域及怎麼解決跨域問題?[通俗易懂]

跨域的常見解決方案

jsonp

jq的ajax自帶解決跨域的方法。底層原理採用的JSONP的跨域解決方案。如下

function callback(){
    console.log("這是列印日誌")
}

$.ajax({
    url: 'http://www.domain2.com:8080/login',
    type: 'get',
    dataType: 'jsonp',  // 請求方式為jsonp  設定跨域的重點
    jsonpCallback: "callBack",  // 回撥函式
});

jsonp跨解實現流程:

新增響應頭解決

CORS簡介

CORS是一個W3C標準,全稱是"跨域資源共享”(Cross-origin resource sharing)。它允許瀏覽器向跨源(協議 + 域名 + 埠)伺服器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。CORS需要瀏覽器和伺服器同時支援。它的通訊過程,都是瀏覽器自動完成,不需要使用者參與。

對於開發者來說,CORS通訊與同源的AJAX/Fetch通訊沒有差別,程式碼完全一樣。瀏覽器一旦發現請求跨源,就會自動新增一些附加的頭資訊,有時還會多出一次附加的請求,但使用者不會有感覺。因此,實現CORS通訊的關鍵是伺服器。只要伺服器實現了CORS介面,就可以跨源通訊。

瀏覽器發出CORS簡單請求,只需要在頭資訊之中增加一個Origin欄位。

瀏覽器發出CORS非簡單請求,會在正式通訊之前,增加一次OPTIONS查詢請求,稱為"預檢"請求(preflight)。瀏覽器先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP動詞和頭資訊欄位。只有得到肯定答覆,瀏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。

簡單請求就是HEAD、GET、POST請求,並且HTTP的頭資訊不超出以下幾種欄位 Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type 注:Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

反之,就是非簡單請求。

其實實現CORS很簡單,就是在服務端加一些響應頭,並且這樣做對前端來說是無感知的,很方便。

詳解響應頭:

  • Access-Control-Allow-Origin 該欄位必填。它的值要麼是請求時Origin欄位的具體值,要麼是一個*,表示接受任意域名的請求。
  • Access-Control-Allow-Methods 該欄位必填。它的值是逗號分隔的一個具體的字串或者*,表明伺服器支援的所有跨域請求的方法。注意,返回的是所有支援的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次"預檢"請求。
  • Access-Control-Expose-Headers 該欄位可選。CORS請求時,XMLHttpRequest物件的getResponseHeader()方法只能拿到6個基本欄位:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他欄位,就必須在Access-Control-Expose-Headers裡面指定。
  • Access-Control-Allow-Credentials 該欄位可選。它的值是一個布林值,表示是否允許傳送Cookie.預設情況下,不發生Cookie,即:false。對伺服器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type欄位的型別是application/json,這個值只能設為true。如果伺服器不要瀏覽器傳送Cookie,刪除該欄位即可。
  • Access-Control-Max-Age 該欄位可選,用來指定本次預檢請求的有效期,單位為秒。在有效期間,不用發出另一條預檢請求。

順便提一下,如果在開發中,發現每次發起請求都是兩條,一次OPTIONS,一次正常請求,注意是每次,那麼就需要配置Access-Control-Max-Age,避免每次都發出預檢請求。

Access-Control-Allow-Origin響應頭的意思是,安全同行的請求。

舉個例子 http://192.168.0.103:8080http://192.168.0.102:8080 傳送了請求,結果因為域名不一樣,在返回資訊的時候因為IP地址不一致被攔截。

但是如果http://192.168.0.102:8080 在響應頭中的 Access-Control-Allow-Origin 欄位中攜帶上屬性值'http://192.168.0.103:8080' 如下

//響應頭
Access-Control-Allow-Origin':'http://192.168.0.103:8080'

這就等於告訴瀏覽器,http://192.168.0.102:8080 這個地址是安全的,請不要攔截。

這樣,http://192.168.0.103:8080 就可以接受來自 http://192.168.0.102:8080 返回的資訊。

當然,我們也可以進行所有域名均不攔截的設定(如下)

//響應頭
// * 代表所有域名均不攔截
Access-Control-Allow-Origin':'*'

過濾器解決跨域

import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter(filterName = "CorsFilter ")
@Configuration
public class CorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin","*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        chain.doFilter(req, res);
    }
}

使用@CrossOrigin註解

public class GoodsController {
@CrossOrigin(origins = "http://localhost:4000")
@GetMapping("goods-url")
public Response queryGoodsWithGoodsUrl(@RequestParam String goodsUrl) throws Exception {}
}  

Nginx代理跨域

location / {  
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

    if ($request_method = 'OPTIONS') {
        return 204;
    }
    proxy_pass http://192.168.12.12:8081;
} 

給OPTIONS 新增 204的返回原因:是為了處理在傳送POST請求時Nginx依然拒絕訪問的錯誤,傳送"預檢請求"時,需要用到方法 OPTIONS ,所以伺服器需要允許該方法。

預檢請求(preflight request)

跨域資源共享(CORS)標準新增了一組 HTTP 首部欄位,允許伺服器宣告哪些源站有許可權訪問哪些資源。另外,規範要求,對那些可能對伺服器資料產生副作用的HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 型別的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發起一個預檢請求(preflight request),從而獲知服務端是否允許該跨域請求。伺服器確認允許之後,才發起實際的 HTTP 請求。在預檢請求的返回中,伺服器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關資料)。

其實Content-Type欄位的型別為application/json的請求就是上面所說的搭配某些 MIME 型別的 POST 請求,CORS規定,Content-Type不屬於以下MIME型別的,都屬於預檢請求。

閘道器解決跨域

@Configuration
public class GlobalCorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        // 這裡僅為了說明問題,配置為放行所有域名,生產環境請對此進行修改
        config.addAllowedOrigin("*");
        // 放行的請求頭
        config.addAllowedHeader("*");
        // 放行的請求方式,主要有:GET, POST, PUT, DELETE, OPTIONS
        config.addAllowedMethod("*"); 
        // 暴露頭部資訊
        config.addExposedHeader("*"); 
        // 是否傳送cookie
        config.setAllowCredentials(true); 
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

總結

跨域全稱為Cross-Origin Resource Sharing,意為跨域資源共享,是一種允許當前域(domain)的資源被其他域(domain)的指令碼請求訪問的機制,通常由於同源安全策略,瀏覽器會禁止這種跨域請求。

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章