原文連接:ssshooter.com/2019-11-08-…javascript
本文主要涉及三個關鍵詞:php
先解釋何爲同源:協議、域名、端口都同樣,就是同源。css
url | 同源 |
---|---|
niconico.com | 基準 |
niconico.com/spirit | o |
sub.niconico.com/spirit | x |
niconico.com/spirit | x |
niconico.com:8080/spirit | x |
你之因此會遇到 跨域問題,正是由於 SOP 的各類限制。可是具體來講限制了什麼呢?html
若是你說 SOP 就是「限制非同源資源的獲取」,這不對,最簡單的例子是引用圖片、css、js 文件等資源的時候就容許跨域。前端
若是你說 SOP 就是「禁止跨域請求」,這也不對,本質上 SOP 並非禁止跨域請求,而是在請求後攔截了請求的迴應。這就就會引發後面說到的 CSRFjava
其實 SOP 不是單一的定義,而是在不一樣狀況下有不一樣的解釋:node
下面是 3 個在實際應用中會遇到的例子:ios
<img>
)進行操做,在 canvas 操做圖片的時候會遇到這個問題若是沒有了 SOP:ajax
SOP 帶來安全,同時也會帶來必定程度的麻煩,由於有時候就是有跨域的需求。繞過跨域的方案因爲篇幅所限,而且網上也不少相關文章,因此不在這裏展開解決跨域的方案,只給出幾個關鍵詞:json
對於 ajax
對於 iframe
CSRF(Cross-site request forgery)跨站請求僞造,是一種常見的攻擊方式。是指 A 網站正常登錄後,cookie 正常保存,其餘網站 B 經過某種方式調用 A 網站接口進行操做,A 的接口在請求時會自動帶上 cookie。
上面說了,SOP 能夠經過 html tag 加載資源,並且 SOP 不阻止接口請求而是攔截請求結果,CSRF 偏偏佔了這兩個便宜。
因此 SOP 不能做爲防範 CSRF 的方法。
對於 GET 請求,直接放到<img>
就能神不知鬼不覺地請求跨域接口。
對於 POST 請求,不少例子都使用 form 提交:
<form action="<nowiki>http://bank.com/transfer.do</nowiki>" method="POST">
<input type="hidden" name="acct" value="MARIA" />
<input type="hidden" name="amount" value="100000" />
<input type="submit" value="View my pictures" />
</form>
複製代碼
歸根到底,這兩個方法不報跨域是由於請求由 html 控制,你沒法用 js 直接操做得到的結果。
對於 ajax 請求,在得到數據以後你能肆意進行 js 操做。這時候雖然同源策略會阻止響應,但依然會發出請求。由於執行響應攔截的是瀏覽器而不是後端程序。事實上你的請求已經發到服務器並返回告終果,可是迫於安全策略,瀏覽器不容許你繼續進行 js 操做,因此報出你熟悉的 blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
。
因此再強調一次,同源策略不能做爲防範 CSRF 的方法。
不過能夠防範 CSRF 的例外仍是有的,瀏覽器並非讓全部請求都發送成功,上述狀況僅限於簡單請求,相關知識會在下面 CORS 一節詳細解釋。
SOP 被 CSRF 佔了便宜,那真的是一無可取嗎?
不是!是否記得 SOP 限制了 cookie 的命名區域,雖然請求會自動帶上 cookies,可是攻擊者不管如何仍是沒法獲取 cookie 的內容自己。
因此應對 CSRF 有這樣的思路:同時把一個 token 寫到 cookie 裏,在發起請求時再經過 query、body 或者 header 帶上這個 token。請求到達服務器,覈對這個 token,若是正確,那必定是能看到 cookie 的本域發送的請求,CSRF 則作不到這一點。(這個方法用於先後端分離,後端渲染則能夠直接寫入到 dom 中)
示例代碼以下:
var csrftoken = Cookies.get('csrfToken')
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method)
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader('x-csrf-token', csrftoken)
}
},
})
複製代碼
跨域是瀏覽器限制,可是若是服務器設置了 CORS 相關配置,在返回服務器的信息頭部會加上 Access-Control-Allow-Origin
,瀏覽器看到這個字段的值與當前的源匹配,就會解鎖跨域限制。
HTTP/1.1 200 OK
Date: Sun, 24 Apr 2016 12:43:39 GMT
Server: Apache
Access-Control-Allow-Origin: http://www.acceptmeplease.com
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: application/xml
Content-Length: 423
複製代碼
對於 CORS,請求分兩種。
符合上面兩個條件的都爲 CORS 簡單請求。簡單請求都會直接發到服務器,會形成 CSRF。
不符合簡單請求要求的請求都須要先發送預檢請求(Preflight Request)。瀏覽器會在真正請求前發送 OPTION 方法的請求向服務器詢問當前源是否符合 CORS 目標,驗證經過後纔會發送正式請求。
例如使用 application/json 傳參的 POST 請求就是非簡單請求,會在預檢中被攔截。
再例如使用 PUT 方法請求,也會發送預檢請求。
上面提到的能夠防範 CSRF 的例外,就是指預檢請求。即便跨域成功請求預檢,但真正請求並不能發出去,這就保證了 CSRF 沒法成功。
與同域不一樣,用於跨域的 CORS 請求默認不發送 Cookie 和 HTTP 認證信息,先後端都要在配置中設定請求時帶上 cookie。
這就是爲何在進行 CORS 請求時 axios 須要設置 withCredentials: true
。
下面是 node.js 的後臺 koa 框架的 CORS 設置:
/**
* CORS middleware
*
* @param {Object} [options]
* - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is request Origin header
* - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH'
* - {String|Array} exposeHeaders `Access-Control-Expose-Headers`
* - {String|Array} allowHeaders `Access-Control-Allow-Headers`
* - {String|Number} maxAge `Access-Control-Max-Age` in seconds
* - {Boolean} credentials `Access-Control-Allow-Credentials`
* - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown
* @return {Function} cors middleware
* @api public
*/
複製代碼
順帶一提,Access-Control-Allow-Credentials
設爲true
時,Access-Control-Allow-Origin
強制不能設爲 *
,爲了安全,也是挺麻煩啊...
暫時先說到這,如有疑問請在評論區提出,之後應該會講講 XSS、CSP 和 http/https 相關的主題。