跨域的原因
同源策略限制了从同一个源加载的文档或脚本与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制,所以跨域是指违背了浏览器同源策略的限制。如果两个页面的协议,端口(如果有指定)和主机都相同,则两个页面具有相同的源。我们也可以把它称为“协议/主机/端口”。
注意:IE 未将端口号加入到同源策略的组成部分之中
因此 http://company.com:81/index.html
和 http://company.com/index.html
属于同源并且不受任何限制。
下表给出了相对http://store.company.com/dir/page.html
同源检测的示例:
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 成功 | 只有路径不同 |
http://store.company.com/dir/inner/another.html | 成功 | 只有路径不同 |
https://store.company.com/secure.html | 失败 | 不同协议 ( https和http ) |
http://store.company.com:81/dir/etc.html | 失败 | 不同端口 ( http:// 80是默认的) |
http://news.company.com/dir/other.html | 失败 | 不同域名 ( news和store ) |
一、jsonp
jsonp是最早出现的跨域方案,但它并不是官方的跨域解决方案,但要注意使用的前提是服务端支持这种跨域。jsonp是使用script(script标签引入的函数是属于window全局的)、img、iframe没有同源限制的标签,通过向服务端发送请求,将返回的数据作为指定的回调函数的参数,所以你只需要在另一个script中指定回调函数,这样就可以获取到服务端数据了,所以它只支持get请求,错误处理机制也不完善,但jsonp的优点是它可以兼容低版本的浏览器。
JSONP安全性问题
-
CSRF攻击
前端构造一个恶意页面,请求JSONP接口,收集服务端的敏感信息。如果JSONP接口还涉及一些敏感操作或信息(比如登录、删除等操作),那就更不安全了。
解决方法:验证JSONP的调用来源(Referer),服务端判断Referer是否是白名单,或者部署随机Token来防御。
-
XSS漏洞
不严谨的 content-type导致的 XSS 漏洞,想象一下 JSONP 就是你请求 http://youdomain.com?callback=douniwan
, 然后返回 douniwan({ data })
,那假如请求 http://youdomain.com?callback=<script>alert(1)</script>
不就返回 <script>alert(1)</script>({ data })
了吗,如果没有严格定义好 Content-Type( Content-Type: application/json ),再加上没有过滤 callback
参数,直接当 html 解析了,就是一个赤裸裸的 XSS 了。
解决方法:严格定义 Content-Type: application/json,然后严格过滤 callback
后的参数并且限制长度(进行字符转义,例如<换成<,>换成>)等,这样返回的脚本内容会变成文本格式,脚本将不会执行。服务器被黑,返回一串恶意执行的代码,可以将执行的代码转发到服务端进行校验JSONP内容校验,再返回校验结果。
二、CORS(跨域资源共享)
CORS 全称为 Cross Origin Resource Sharing(跨域资源共享),是官方推荐的跨域方案,但是CORS仍然是使用XMLHttpRequest发送请求,只是多了些字段告诉服务器允许跨域,它也需要服务端的支持。可以访问多种请求方式,处理机制完善,符合http规范,对于复杂请求,多一次验证,安全性更好,但不支持IE10以下浏览器。这种方式和正常发送请求写法上没有任何区别,基本都会在后端这里。每一次请求,浏览器一般都会先以 OPTIONS
请求方式发送一个预请求(不是所有请求都会发送 options),通过预请求从而获知服务器端对跨源请求支持的 HTTP
方法,使用额外的HTTP头来告诉浏览器,让运行在origin (domain)上的Web应用被准许访问来自不同源服务器上的指定的资源,在确认服务器允许该跨源请求的情况下,再以实际的 HTTP
请求方法发送那个真正的请求。推荐的原因是:只要第一次配好了,之后不管有多少接口和项目复用就可以了,一劳永逸的解决了跨域问题,而且不管是开发环境还是正式环境都能方便的使用。
注意,http://localhost:8080和http://127.0.0.1:8080对服务端来说是不一样的
CORS根据是否使用默认支持的字段和方法将请求分为“简单请求”与“非简单请求”,浏览器会根据不同类型作出不同处理,它们的区别是简单请求(不能接收或者发送cookie;不能使用setRequestHeader()设置自定义头部;调用getAllResponseHeader()得到的为空字符串)不会调用验证机制,而非简单请求则会通过浏览器先调用Preflighted Request透明服务器的验证机制,使用OPTIONS方法和服务器验证,通过验证,浏览器才会正式请求服务器。
三、proxy(服务端代理)
在 dev
开发模式下可以下使用 webpack 的 proxy
代理跨域,但这种方法在生产环境是不能使用的,在生产环境中需要使用 nginx
进行反向代理。不管是 proxy
和 nginx
的原理都是一样的,通过搭建一个中转服务器来转发请求规避跨域的问题,即
都是访问中间层,由中间层去访问目标服务器
开发环境 | 生产环境 |
---|---|
cors | cors |
proxy | nginx |
拓展:
如何阻止跨源访问?
- 阻止跨域写操作,只要检测请求中的一个不可测的标记(CSRF token)即可,这个标记被称为Cross-Site Request Forgery (CSRF) 标记。必须使用这个标记来阻止页面的跨站读操作。
- 阻止资源的跨站读取,需要保证该资源是不可嵌入的。阻止嵌入行为是必须的,因为嵌入资源通常向其暴露信息。
- 阻止跨站嵌入,需要确保你的资源不能是以上列出的可嵌入资源格式。多数情况下浏览器都不会遵守
Content-Type
消息头。例如,如果您在HTML文档中指定<script>
标记,则浏览器将尝试将HTML解析为JavaScript。 当您的资源不是您网站的入口点时,您还可以使用CSRF令牌来防止嵌入。
相关资料库:
https://panjiachen.github.io/awesome-bookmarks/blog/cs.html#dns
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API
其他跨域解决方案
- Nginx反向代理
postMessage
document.domain