1. 程式人生 > >No 'Access-Control-Allow-Origin' header is present之 為什麼會跨域及解決方案

No 'Access-Control-Allow-Origin' header is present之 為什麼會跨域及解決方案

1 瀏覽器的限制

2 跨域

3 瀏覽器傳送的是 XHR (XMLHttpRequest)請求

當以上三個條件都滿足時瀏覽器會丟擲跨域請求異常(記住是瀏覽器丟擲的異常,和服務端沒太大關係),在講跨域請求解決方案前先了解幾個問題。

1 http請求中,哪些是常見的簡單請求,哪些是非簡單請求

常見的簡單請求:請求方法為:GET ,HEAD,POST,請求header裡面無自定義頭,Content-Type為以下幾種:text/plain  multipart/form-data application/x-www-form-urlencoded

常見的非簡單請求 :請求方法為:put delete的ajax請求,傳送json格式的ajax請求,帶自定義頭的ajax請求

2 瀏覽器在傳送跨域請求時候,會有哪些過程

如果是簡單請求,瀏覽器會先發送請求,然後判斷伺服器返的返回頭中是否支援跨域請求,否則丟擲跨域異常

如果是非簡單請求,瀏覽器會先發出OPTIONS請求方法的檢測命令,判斷伺服器是否支援跨域請求,如果支援則傳送真正的請求,如果不支援則丟擲跨域異常,因此一個非簡單請求每次會發送兩個請求,後面跨域解決方案會講到快取OPTIONS預檢請求

跨域解決方案

方案1:  禁用瀏覽器跨域校驗,即允許跨域訪問,(這種方案不可取,不可能讓所有的瀏覽器設定允許跨域訪問)

谷歌瀏覽器禁用跨域校驗: 建立一個快捷方式傳送到桌面 ,快捷方式--》右鍵---》屬性頁面中的目標輸入框里加上  --disable-web-security --user-data-dir=C:\Program Files (x86)\Google\Chrome\Application (注意:--user-data-dir的值就是瀏覽器安裝目錄。)

方案2: 採用jsonp方式,需要後臺和前臺同時改動程式碼,

1 前臺需要設定callback引數,如果使用的是jquery ajax 那麼dateType屬性設定為jsonp,jquery框架會自動設定引數名為callback的請求引數,也可以通過jsonp屬性修改jsonp請求引數名,其他js框架根據具體api使用,

2 後臺接收到callback引數後認為是jsonp請求,需要返回jsonp格式,普通json請求返回的content-Type是application/json,而jsonp返回的是application/javascript,同時也證明了jsonp請求服務端返回的是js指令碼

3 jsonp請求引數名前後約定需要相同,例如jquery預設使用的是callback

弊端:jsonp 需要前後端都去修改程式碼,且jsonp是通過動態建立script指令碼傳送請求,僅支援 GET方法,jsonp發出的請求不是xhr請求,也是能解決跨域的原因

方案3 服務端解決跨域問題

通過編寫filter在response物件中新增響應頭,告訴瀏覽器允許跨域訪問,* 號程式碼允許所有的請求域名,所有的請求方法跨域訪問

//告訴瀏覽器允許所有的域訪問
//注意 * 不能滿足帶有cookie的訪問,Origin 必須是全匹配
//resp.addHeader("Access-Control-Allow-Origin", "*");
//解決辦法通過獲取Origin請求頭來動態設定
String origin = request.getHeader("Origin");
if (StringUtils.hasText(origin)) {
    resp.addHeader("Access-Control-Allow-Origin", origin);
}
//允許帶有cookie訪問
resp.addHeader("Access-Control-Allow-Credentials", "true");
//告訴瀏覽器允許跨域訪問的方法
resp.addHeader("Access-Control-Allow-Methods", "*");
//告訴瀏覽器允許帶有Content-Type,header1,header2頭的請求訪問
//resp.addHeader("Access-Control-Allow-Headers", "Content-Type,header1,header2");
//設定支援所有的自定義請求頭
String headers = request.getHeader("Access-Control-Request-Headers");
if (StringUtils.hasText(headers)){
    resp.addHeader("Access-Control-Allow-Headers", headers);
}
//告訴瀏覽器快取OPTIONS預檢請求1小時,避免非簡單請求每次傳送預檢請求,提升效能
resp.addHeader("Access-Control-Max-Age", "3600");

方案4 Spring框架提供了跨域解決方案

spring提供了 @CrossOrigin註解使用者解決跨域問題,同時支援全域性配置
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/api/**")
			.allowedOrigins("http://domain2.com")
			.allowedMethods("PUT", "DELETE")
			.allowedHeaders("header1", "header2", "header3")
			.exposedHeaders("header1", "header2")
			.allowCredentials(false).maxAge(3600);
	}
}

方案5 服務端通過ngnix解決跨域問題 

location /{
            proxy_pass http://localhost:8080/;
           
            #告訴瀏覽器允許跨域訪問的方法
            add_header Access-Control-Allow-Methods *;
            # 告訴瀏覽器快取OPTIONS預檢請求1小時
            add_header Access-Control-Max-Age 3600;
            #允許帶有cookie訪問
            add_header Access-Control-Allow-Credentials true;
            #注意 * 不能滿足帶有cookie的訪問,Origin 必須是全匹配,這裡通過變數獲取
            add_header Access-Control-Allow-Origin $http_origin;
            #設定支援所有的自定義請求頭
            add_header Access-Control-Allow-Headers $http_access_control_request_headers;
            #如果預檢請求,則返回成功,不需要轉發到後端
            if ($request_method = OPTIONS){
                return 200;
            }
        }

方案6 客戶端通過nginx隱藏跨域

#轉發全部以/api開頭的請求到web伺服器
   location  /api
   {
        proxy_pass http://127.0.0.1:8080/api;
   }