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