原文连接:https://ssshooter.com/2019-11-08-csrf-n-cors/javascript
本文主要涉及三个关键词:css
先解释何为同源:协议、域名、端口都同样,就是同源。html
url | 同源 |
---|---|
https://niconico.com | 基准 |
https://niconico.com/spirit | o |
https://sub.niconico.com/spirit | x |
http://niconico.com/spirit | x |
https://niconico.com:8080/spirit | x |
你之因此会遇到 跨域问题,正是由于 SOP 的各类限制。可是具体来讲限制了什么呢?前端
若是你说 SOP 就是“限制非同源资源的获取”,这不对,最简单的例子是引用图片、css、js 文件等资源的时候就容许跨域。java
若是你说 SOP 就是“禁止跨域请求”,这也不对,本质上 SOP 并非禁止跨域请求,而是在请求后拦截了请求的回应。这就就会引发后面说到的 CSRFnode
其实 SOP 不是单一的定义,而是在不一样状况下有不一样的解释:ios
下面是 3 个在实际应用中会遇到的例子:ajax
<img>
)进行操做,在 canvas 操做图片的时候会遇到这个问题若是没有了 SOP:json
SOP 带来安全,同时也会带来必定程度的麻烦,由于有时候就是有跨域的需求。绕过跨域的方案因为篇幅所限,而且网上也不少相关文章,因此不在这里展开解决跨域的方案,只给出几个关键词:canvas
对于 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 相关的主题。