為什麼會出現跨域問題
存在瀏覽器同源策略,所以才會有跨域問題。那麼瀏覽器是出於何種原因會有跨域的限制呢。其實不難想到,跨域限制主要的目的就是為了使用者的上網安全。
同源策略導致的跨域是瀏覽器單方面拒絕響應資料,伺服器端是處理完畢並做出了響應的。
什麼是同源策略
一個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:8080 向http://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 釋出!