有一點必需要注意:跨域並非請求發不出去,請求能發出去,服務端能收到請求並正常返回結果,只是結果被瀏覽器攔截了。之因此會跨域,是由於受到了同源策略的限制,同源策略要求源相同才能正常進行通訊,即協議、域名、端口號都徹底一致。javascript
那麼是出於什麼安全考慮纔會引入這種機制呢?html
其實主要是用來防止 CSRF 攻擊的。簡單點說,CSRF 攻擊是利用用戶的登陸態發起惡意請求。前端
也就是說,沒有同源策略的狀況下,A 網站能夠被任意其餘來源的 Ajax 訪問到內容。若是你當前 A 網站還存在登陸態,那麼對方就能夠經過 Ajax 得到你的任何信息。固然跨域並不能徹底阻止 CSRF。html5
而後咱們來考慮一個問題,請求跨域了,那麼請求到底發出去沒有? 請求必然是發出去了,可是瀏覽器攔截了響應。你可能會疑問明明經過表單的方式能夠發起跨域請求,爲何 Ajax 就不會。由於歸根結底,跨域是爲了阻止用戶讀取到另外一個域名下的內容,Ajax 能夠獲取響應,瀏覽器認爲這不安全,因此攔截了響應。可是表單並不會獲取新的內容,因此能夠發起跨域請求。同時也說明了跨域並不能徹底阻止 CSRF,由於請求畢竟是發出去了。java
經常使用的跨域方法有JSONP
,CORS
,postmessage
等,web
JSONP
是JSON with padding
的簡寫,是應用JSON
的一種新方法,JSONP
看起來和JSON
差很少,只不過是被包含在函數調用的JSON
,像這樣:callback({name: 'nany'})
。express
經過<script>
標籤引入一個js
文件,這個js
文件載成功後會執行咱們在url
參數中指定的函數,而且會把咱們須要的json
數據做爲參數傳入,jsonp
是須要服務器端配合的。json
前端:後端
<script>
function getPrice(data){
console.log(data);
}
</script>
<script
type="text/javascript"
src="http://sdffw.b2b.com/getSupplyPrice?callback=getPrice&bcid=47296567">
</script>
複製代碼
後端:跨域
const url = require('url');
require('http').createServer((req, res) => {
const data = {};
const callback = url.parse(req.url, true).query.callback ;
res.writeHead(200)
res.end(`${callback}(${JSON.stringify(data)})`)
*// 服務器收到請求後,解析參數,*
*// 將callback(data)以字符串的形式返還數據,前端頁面會將callback(data)做爲js執行*
*// 調用jsonpCallback(data)函數。*
}).listen(3000, '127.0.0.1');
複製代碼
callback
是先後臺約定的查詢參數,服務器端返回一個能執行的js
文件,這個js
文件是調用callback
對應的參數值即getPrice
執行,而且返回對應的數據,咱們能夠在getPrice
方法裏面來處理返回的數據,最終返回的結果以下:
getPrice({
"data":{"priceType":"0","unit":"斤"},
"message":"價格獲取成功!!!",
"state":"1"
})
複製代碼
在開發中可能會遇到多個JSONP
請求的回調函數名是相同的,這時候就須要本身封裝一個JSONP
,如下是簡單實現:
function jsonp(url, jsonpCallback, success) {
let script = document.createElement('script')
script.src = url
script.async = true
script.type = 'text/javascript'
window[jsonpCallback] = function(data) {
success && success(data)
}
document.body.appendChild(script)
}
jsonp('http://xxx', 'callback', function(value) {
console.log(value)
})
複製代碼
JSONP
跨域不像下面的CORS
跨域那樣受同源政策的影響,並且兼容性也比較好,但JSONP
跨域也有其缺點,主要表如今:
GET
請求而不支持POST
等其它類行的 HTTP
請求。HTTP
請求這種狀況,不能解決不一樣域的兩個頁面或 iframe
之間進行數據通訊的問題。JSONP
從其餘域中加載代碼執行,若是該域不安全而且夾帶一些惡意代碼,會存在安全隱患 要肯定JSONP
請求是否失敗並不容易CORS
須要瀏覽器和後端同時支持。IE 8
和 9
須要經過 XDomainRequest
來實現。
瀏覽器會自動進行 CORS
通訊,實現 CORS
通訊的關鍵是後端。只要後端實現了 CORS
,就實現了跨域。服務端設置 Access-Control-Allow-Origin
就能夠開啓 CORS
。 該屬性表示哪些域名能夠訪問資源,若是設置通配符則表示全部網站均可以訪問資源。
雖然設置 CORS
和前端沒什麼關係,可是經過這種方式解決跨域問題的話,會在發送請求時出現兩種狀況,分別爲簡單請求和複雜請求。
以 Ajax
爲例,當知足如下條件時,會觸發簡單請求
使用下列方法之一:
GET
HEAD
POST
Content-Type 的值僅限於下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
請求中的任意 XMLHttpRequestUpload 對象均沒有註冊任何事件監聽器; XMLHttpRequestUpload 對象可使用 XMLHttpRequest.upload 屬性訪問。
複製代碼
那麼很顯然,不符合以上條件的請求就確定是複雜請求了。
對於複雜請求來講,首先會發起一個預檢請求,該請求是option
方法的,經過該請求來知道服務端是否容許跨域請求。對於預檢請求來講,若是你使用過Node
來設置CORS
的話,可能會遇到過這麼一個坑。
如下以 express 框架舉例:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS')
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials'
)
next()
})
複製代碼
該請求會驗證你的Authorization
字段,沒有的話就會報錯。
當前端發起了複雜請求後,你會發現就算你代碼是正確的,返回結果也永遠是報錯的。由於預檢請求也會進入回調中,也會觸發next()
方法,由於預檢請求並不包含Authorization
字段,因此服務端會報錯。
想解決這個問題很簡單,只須要在回調中過濾option
方法便可。
res.statusCode = 204
res.setHeader('Content-Length', '0')
res.end()
複製代碼
POST
請求方式,CORS
是一種新型的跨域問題的解決方案,存在兼容問題,僅支持IE 10
以上這種方式只能用於二級域名相同的狀況下,好比a.test.com
和 b.test.com
適用於該方式。
只須要給頁面添加 document.domain = 'test.com'
表示二級域名都相同就能夠實現跨域。 修改document.domain
的方法只適用於不一樣子域的框架間的交互。
window.postMessage(message,targetOrigin)
方法是html5
新引進的特性,可使用它來向其它的window
對象發送消息,不管這個window
對象是屬於同源或不一樣源,目前IE8+
、FireFox
、Chrome
、Opera
等瀏覽器都已經支持window.postMessage
方法。
調用postMessage
方法的window
對象是指要接收消息的那一個window
對象,該方法的第一個參數message
爲要發送的消息,類型只能爲字符串;第二個參數targetOrigin
用來限定接收消息的那個window
對象所在的域,若是不想限定域,可使用通配符*
。
須要接收消息的window
對象,但是經過監聽自身的message
事件來獲取傳過來的消息,消息內容儲存在該事件對象的data
屬性中。
// 發送消息端
window.parent.postMessage('message', 'http://test.com')
// 接收消息端
var mc = new MessageChannel()
mc.addEventListener('message', event => {
var origin = event.origin || event.originalEvent.origin
if (origin === 'http://test.com') {
console.log('驗證經過')
}
})
複製代碼
window
的name
屬性有個特徵:
在一個窗口(window)
的生命週期內,窗口載入的全部的頁面都是共享一個window.name
,每一個頁面對window.name
都有讀寫的權限,window.name
是持久存在一個窗口載入過的全部頁面中的,並不會因新頁面的載入而進行重置。
web sockets原理:
在JS建立了web socket
以後,會有一個HTTP
請求發送到瀏覽器以發起鏈接。取得服務器響應後,創建的鏈接會使用HTTP
升級從HTTP
協議交換爲web sockt
協議。
CSRF(Cross site request forgery)
,即跨站請求僞造。咱們知道XSS是跨站腳本攻擊,就是在用戶的瀏覽器中執行攻擊者的腳本,來得到其cookie
等信息。而CSRF
確是借用用戶的身份,向web server
發送請求,由於該請求不是用戶本意,因此稱爲「跨站請求僞造」。
CSRF
通常的攻擊過程是,攻擊者向目標網站注入一個惡意的CSRF
攻擊URL
地址(跨站url)
,當(登陸)用戶訪問某特定網頁時,若是用戶點擊了該URL
,那麼攻擊就觸發了,咱們能夠在該惡意的url
對應的網頁中,利用 來向目標網站發生一個get
請求,該請求會攜帶cookie
信息,因此也就借用了用戶的身份,也就是僞造了一個請求,該請求能夠是目標網站中的用戶有權限訪問的任意請求。也可使用javascript
構造一個提交表單的post
請求。好比構造一個轉帳的post
請求。
因此CSRF
的攻擊分爲了兩步,首先要注入惡意URL
地址,而後在該地址中寫入攻擊代碼,利用 等標籤或者使用Javascript
腳本
由於僞造的請求通常是從第三方網站發起的,因此第一個防護方法就是判斷referer
頭,若是不是來自本網站的請求,就斷定爲CSRF
攻擊。可是該方法只能防護跨站的CSRF
攻擊,不能防護同站的CSRF
攻擊(雖然同站的CSRF
更難)。
每個重要的post
提交頁面,使用一個驗證碼,由於第三方網站是沒法得到驗證碼的。還有使用手機驗證碼,好比轉帳是使用的手機驗證碼。
每個網頁包含一個web server
產生的token
,提交時,也將該token
提交到服務器,服務器進行判斷,若是token
不對,就斷定位CSRF
攻擊。
將敏感操做get
改成post
,而後在表單中使用token
. 儘可能使用post
也有利於防護CSRF
攻擊。
註明一下,整篇文章爲學習筆記,多方參考總結,若有版權衝突,請留言,收到消息後會標明版權出處。