讀完這篇跨域,大抵心下清楚其他跨域的不必看罷了。 ---魯迅
目錄
一、什麼時候出現跨域
每個web開發者都會遇到跨域。他們或多或少會知道大概類似於下面這樣的知識:
如果請求來源和請求目標的不在同一個服務器上會發生跨域。
但跨域的原因僅此於此嗎?先看一個現象。
服務器就一個url
@RequestMapping("")
public String index (){
return "ok";
}
然後我們開始測試,1號測試,在postman裏輸入請求:localhost:8080。結果得到了一個ok,並沒有報什麼跨域的錯誤。
那麼僅此一個例子就可以判定上面的那個知識是錯誤的,因爲我在postman裏直接輸入的話,並沒有請求來源,但是有請求目標,他們是不一致的。
但我覺得可以換個方案,2號測試,寫一個html來訪問試圖去迎合上面那個知識,注意,這個html就直接放本地盤符啊,不要和服務器代碼放一起,否則他們就是在同一個服務器上了,自然不好引起跨域。
<html>
<script src="jquery.js"></script>
<script>
$.post("http://localhost:8080",{},function(data){
console.log(data)
})
</script>
<html>
本地直接打開後,出現了跨域的問題。
竟然請求失敗了,請求來源和目標不一致,出現了跨域問題。
爲什麼用postman可以請求,而使用html就出現跨域問題呢?
很簡單。跨域問題出現的其中一個要素就是:跨域是瀏覽器裏纔會出現。跨域本身就是瀏覽器爲了解決一些安全問題而設置的警告攔截。
那麼瀏覽器裏的來源目標不一致的請求都會產生跨域嗎?
我們看下3號測試,在瀏覽器地址裏輸入:localhost:8080.
咦?竟然不跨域了?
4號測試,我們寫個a標籤試下,這個a標籤的來源可以是任何地址。
<a href="http://localhost:8080">點我</a>
也不跨域。
跨域問題出現的第二個要素是:只有js的xhr纔會發生跨域,html標籤並不會,包括不僅限於a標籤,form標籤等。
js跨域,實際上就意味着ajax,vue等js框架同樣會有跨域。
跨域問題還有一個要素,當然,就是上面那個不完整的常見知識,我們說的再精準一些:來源和目標的協議、主機、端口有任意一個不同
只有以上三點同時出現,纔會引出跨域問題,再重申一遍:
跨域三要素:
瀏覽器、js、目標來源不一致。
二、跨域的時候究竟發生了什麼?我們怎麼解決?
我們在上面2號測試的時候,瀏覽器報錯下面:
Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
它意思是說,你的目標是http://localhost:8080/,但是來源是null.這不一致,說不定是偷渡,所以我要檢查一番,看看服務器給你開發票了沒用,結果發現返回的響應頭裏沒“Access-Control-Allow-Origin”這張發票,所以違法。
Access-Control-Allow-Origin就是允許的來源。看來我要想解決它。必須在服務器設置下。
5號測試,服務器寫個filter,加入這行代碼
response.setHeader("Access-Control-Allow-Origin", "123");
運行一下,又報錯跨域了。跟上次一樣
Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'null' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains the invalid value '123'.
仔細一看,還有點不一樣,這段意思是,這張發票是開給123的,而你是“null”,所以也不合法。
那就6號測試:
response.setHeader("Access-Control-Allow-Origin", "null");
運行起來,這次沒報錯了,
那麼解決跨域的一個操作就是:
服務端設置允許來源Access-Control-Allow-Origin,如果希望省事,那麼可以允許任何來源
response.setHeader("Access-Control-Allow-Origin", "*");
如果只是這樣就好了,但是我們最好加一些難度,比如,7號測試,添加一個token來看看會是怎麼樣的
<html>
<script src="jquery.js"></script>
<script>
$.ajax({
url:'http://localhost:8080',
type:'post',
headers:{
'token':'123456'
},
data:{},
success:function(data){console.log("sucess");},
});
</script>
<html>
它又報錯跨域錯誤了,
Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'null' has been blocked by CORS policy: Request header field token is not allowed by Access-Control-Allow-Headers in preflight response.
儘管我英語不是太好,但是對比着上次的錯誤我大概能猜出來,竟然還需要另一張發票Access-Control-Allow-Headers。
是的,如果你自定義了header,那麼同時還需要另外一張發票。
我們修改下服務器返回發票,8號測試:
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Headers", "token,token1,token2");
看,我們開了header是token/token1/token2的發票,你的自定義header要是在其中,沒問題。
那麼解決跨域的第二個操作就是:
如果請求有自定義header,服務端設置允許來源Access-Control-Allow-Headers,如果希望省事,那麼可以允許任何來源
response.setHeader("Access-Control-Allow-Headers", "*");
走到這裏,其實大部分跨域問題都已經解決了。但是我們可以更進一步,這次我們設置下修改下method,由get/post方法改成put,9號測試:
<html>
<script src="jquery.js"></script>
<script>
$.ajax({
url:'http://localhost:8080',
type:'put',
data:{},
success:function(data){console.log("sucess");},
});
</script>
<html>
它又報錯了。
Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'null' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
我閉上眼睛都知道我要做什麼了,
如果請求的method比較稀奇古怪,不是get也不是post啊,那也要設置,不過,乾脆省事些吧。
response.setHeader("Access-Control-Allow-Methods", "*");
當然還有另一個設置,
如果你需要跨域驗證cookie的話。
response.setHeader("Access-Control-Allow-Credentials", "true")
解決方案總結一下吧:
response.setHeader("Access-Control-Allow-Origin", "*");//允許任何請求來源
response.setHeader("Access-Control-Allow-Methods", "*");//允許任何method
response.setHeader("Access-Control-Allow-Headers", "*");//允許任何自定義header
response.setHeader("Access-Control-Allow-Credentials", "true");//允許跨域cookie
三、隱藏的祕密
發生原因和解決方案都已經列出,但跨域還有隱藏的一個祕密,這個就當瞭解一下吧。
如果我們去抓包2號測試和9號測試,對比就會發現他們的結果完全不同。
2號測試請求可以請求到數據,返回了ok。但是瀏覽器報錯跨域。
9號測試請求,它的真實method竟然不是put,而是option,它無法請求到數據,瀏覽器報錯跨域。
這究竟是爲什麼呢?
其實跨域的請求,在瀏覽器看來又分爲簡單請求和非簡單請求。
簡單請求瀏覽器就直接請求,和正常的沒什麼區別,然後根據返回發票判斷是否跨域,如果跨域,就報錯,返回來的數據就扔了。
非簡單請求瀏覽器發起一個一模一樣但是method是option的測試請求,先去嗅探返回發票,如果發票正確,那就再去請求一次真實數據,如果發票錯誤,就報錯。
瀏覽器判斷簡單非簡單請求的依據有:
以下簡單請求,其他非簡單請求:
1.請求方式:GET、POST、HEAD
2.HTTP頭部信息不超過一下幾種字段:
Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(只有三個值application/x-www-form-urlencoded、multipart/form-data、text/plain)
這意味着如果是有自定義header或者contextType=applcaition/json的算是非簡單請求
四、jsonp
有網友給的解決方案是ajax請求時,類型改成jsonp,jsonp其實不能算是xhr請求,而是script請求,
localhost:8080/aaa相當於請求localhost:8080/aaa.js,然後再另行處理。
這種方式可以,但也意味着服務端要配合,修改默認返回方式等。也可以。