跨域

一、什麼是跨域?

當協議、子域名、主域名、端口號中任意一個不相同時,都算做不一樣域,不一樣域之間相互請求資源,就算做「跨域」。由於JavaScript出於安全考慮,有同源策略。

有一點必需要注意:跨域並非請求發不出去,請求能發出去,服務端能收到請求並正常返回結果,只是結果被瀏覽器攔截了。之因此會跨域,是由於受到了同源策略的限制,同源策略要求源相同才能正常進行通訊,即協議、域名、端口號都徹底一致。javascript

那麼是出於什麼安全考慮纔會引入這種機制呢html

其實主要是用來防止 CSRF 攻擊的。簡單點說,CSRF 攻擊是利用用戶的登陸態發起惡意請求。前端

也就是說,沒有同源策略的狀況下,A 網站能夠被任意其餘來源的 Ajax 訪問到內容。若是你當前 A 網站還存在登陸態,那麼對方就能夠經過 Ajax 得到你的任何信息。固然跨域並不能徹底阻止 CSRF。html5

而後咱們來考慮一個問題,請求跨域了,那麼請求到底發出去沒有? 請求必然是發出去了,可是瀏覽器攔截了響應。你可能會疑問明明經過表單的方式能夠發起跨域請求,爲何 Ajax 就不會。由於歸根結底,跨域是爲了阻止用戶讀取到另外一個域名下的內容,Ajax 能夠獲取響應,瀏覽器認爲這不安全,因此攔截了響應。可是表單並不會獲取新的內容,因此能夠發起跨域請求。同時也說明了跨域並不能徹底阻止 CSRF,由於請求畢竟是發出去了。java

二、如何跨域

經常使用的跨域方法有JSONPCORSpostmessage等,web

(1) JSONP

JSONPJSON with padding的簡寫,是應用JSON的一種新方法,JSONP看起來和JSON差很少,只不過是被包含在函數調用的JSON,像這樣:callback({name: 'nany'})express

JSONP跨域原理

經過<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請求是否失敗並不容易

(2) CORS

CORS 須要瀏覽器和後端同時支持。IE 89 須要經過 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()
複製代碼

CORS 的優缺點:

  • 使用簡單方便,更爲安全
  • 支持 POST 請求方式,
  • CORS是一種新型的跨域問題的解決方案,存在兼容問題,僅支持IE 10以上

(3) 降域(document.domain)

這種方式只能用於二級域名相同的狀況下,好比a.test.comb.test.com 適用於該方式。

只須要給頁面添加 document.domain = 'test.com' 表示二級域名都相同就能夠實現跨域。 修改document.domain的方法只適用於不一樣子域的框架間的交互。

(4) postMessage

window.postMessage(message,targetOrigin) 方法是html5新引進的特性,可使用它來向其它的window對象發送消息,不管這個window對象是屬於同源或不一樣源,目前IE8+FireFoxChromeOpera等瀏覽器都已經支持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('驗證經過')
    }
})
複製代碼

(5) window.name

windowname屬性有個特徵:

在一個窗口(window)的生命週期內,窗口載入的全部的頁面都是共享一個window.name,每一個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的全部頁面中的,並不會因新頁面的載入而進行重置。

(6) Web Sockets

web sockets原理:

在JS建立了web socket以後,會有一個HTTP請求發送到瀏覽器以發起鏈接。取得服務器響應後,創建的鏈接會使用HTTP升級從HTTP協議交換爲web sockt協議。

補一下腦:

CSRF攻擊原理

CSRF(Cross site request forgery),即跨站請求僞造。咱們知道XSS是跨站腳本攻擊,就是在用戶的瀏覽器中執行攻擊者的腳本,來得到其cookie等信息。而CSRF確是借用用戶的身份,向web server發送請求,由於該請求不是用戶本意,因此稱爲「跨站請求僞造」。

CSRF通常的攻擊過程是,攻擊者向目標網站注入一個惡意的CSRF攻擊URL地址(跨站url),當(登陸)用戶訪問某特定網頁時,若是用戶點擊了該URL,那麼攻擊就觸發了,咱們能夠在該惡意的url對應的網頁中,利用 來向目標網站發生一個get請求,該請求會攜帶cookie信息,因此也就借用了用戶的身份,也就是僞造了一個請求,該請求能夠是目標網站中的用戶有權限訪問的任意請求。也可使用javascript構造一個提交表單的post請求。好比構造一個轉帳的post請求。

因此CSRF的攻擊分爲了兩步,首先要注入惡意URL地址,而後在該地址中寫入攻擊代碼,利用 等標籤或者使用Javascript腳本

CSRF防護

referer

由於僞造的請求通常是從第三方網站發起的,因此第一個防護方法就是判斷referer 頭,若是不是來自本網站的請求,就斷定爲CSRF攻擊。可是該方法只能防護跨站的CSRF攻擊,不能防護同站的CSRF攻擊(雖然同站的CSRF更難)。

使用驗證碼

每個重要的post提交頁面,使用一個驗證碼,由於第三方網站是沒法得到驗證碼的。還有使用手機驗證碼,好比轉帳是使用的手機驗證碼。

使用 token

每個網頁包含一個web server產生的token,提交時,也將該token提交到服務器,服務器進行判斷,若是token不對,就斷定位CSRF攻擊。

將敏感操做get改成post,而後在表單中使用token. 儘可能使用post也有利於防護CSRF攻擊。

註明一下,整篇文章爲學習筆記,多方參考總結,若有版權衝突,請留言,收到消息後會標明版權出處。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息