跨域问题的场景和解决方案多种多样, 只要是做前端开发, 总会遇到. 而且面试时也是必问的问题. 所以自己学习总结记录一下.
前置知识: 浏览器的同源策略
首先需要明确一点: 协议, 域名, 端口都相同才叫同源.
同源政策的目的, 是为了保证用户信息的安全, 防止恶意的网站窃取数据.
设想这样一种情况: A 网站是一家银行, 用户登录以后, 又去浏览其他网站. 如果其他网站可以读取 A 网站的 Cookie, 会发生什么?
很显然, 如果 Cookie 包含隐私 (比如存款总额), 这些信息就会泄漏. 更可怕的是, Cookie 往往用来保存用户的登录状态, 如果用户没有退出登录, 其他网站就可以冒充用户, 为所欲为. 因为浏览器同时还规定, 提交表单不受同源政策的限制.
由此可见,"同源政策" 是必需的, 否则 Cookie 可以共享, 互联网就毫无安全可言了.
受到同源限制:
(1) 无法读取不同源的 Cookie,LocalStorage 和 IndexDB .
(2) 无法获得不同源的 DOM .
(3) 不能向不同源的服务器发送 Ajax 请求.
不受同源限制:
在浏览器中,<script>,<img>,<iframe>,<link > 等标签都可以跨域加载资源, 而不受同源策略的限制.
浏览器对跨域访问的判定:
CORS 机制把跨域请求分为两类: 简单请求和非简单请求.
(1) 请求方法是以下三种方法之一: HEAD,GET,POST
(2)HTTP 的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
Content-Type: 只限于三个值 application/x-www-form-urlencoded,multipart/form-data,text/plain
凡是不同时满足上面两个条件, 就属于非简单请求. 浏览器对这两种请求的处理, 是不一样的.
简单请求:
浏览器会带上 Origin 的请求头发送到服务器, 服务器根据 Origin 判断是否许可. 如果许可就会带上 CORS 相关想要头, 如果不在许可范围内就不会带上 CORS 相关的响应头. 浏览器再根据响应头中是否有相关的 CORS 响应头, 来判断拦截响应 body 和抛出错误.
非简单请求:
非简单请求会在发真正的请求之前发送一个 OPTIONS 的带着 Origin,Access-Control-Request-Method,Access-Control-Request-Headers 等 CORS 相关的请求头的预检请求到服务器, 服务器确认可以这样请求, 就会返回带着 Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers 等 CORS 相关的响应头的响应, 浏览器检查到相关的 CORS 响应头, 说明通过预检可以继续发送真正的请求; 服务器确认不可以, 则不会返回这些相关响应头, 浏览器没检查到 CORS 的响应头就会抛出错误.
关于跨域的几个问题
为什么 a.wang.com 访问 wang.com 也算跨域?
因为历史上, 出现过不同的公司共用域名, a.wang.com 和 wang.com 不一定是同一个网站, 浏览器谨慎起见, 认为这是不同的源.
为什么不同端口也算跨域?
原因同上, 一个端口一个公司的情况也不是没有的.
记住: 安全链条的强度取决于最弱的一环, 所有和安全相关的问题都要谨慎对待.
为什么两个网站的 IP 一样, 也算跨域?
原因同上, 因为 IP 也是可以共用的.
为什么可以跨域使用 CSS,JS 和图片等?
同源策略限制的是数据访问, 我们引用 CSS,JS 和图片的时候, 其实并不知道其内容, 我们只是在引用.
解决方案 1: 跨域资源共享 (CORS)
从原理上讲实现 CORS 通信的关键是服务器. 只要服务器实现了 CORS 接口, 就可以跨源通信.
服务器要给接口的响应头设置: Access-Control-Allow-Origin:*
解决方案 2:JSONP 跨域
我们在跨域的时候由于当前的浏览器不支持 CORS 或者因为某些条件不支持 CORS, 我们必须使用另外一种方式来跨域, 于是我们就请求一个 JS 文件, 这个 JS 文件会执行一个回调, 回调里面就有我们需要的数据.
- let script = document.createElement('script');
- script.src = 'http://www.wang.cn/login?username=wang&callback=callback';
- document.body.appendChild(script);
- function callback(res) {
- console.log(res);
- }
JSONP 跨域优点
兼容 IE 并实现跨域
JSONP 跨域缺点
由于是 script 标签, 所以读不到 Ajax 那么精确的状态, 不知道状态码是什么, 也不知道响应头是什么, 它只知道成功和失败.
不支持 post(因为是 script 标签, 所以只支持 get 请求)
手写 JSONP
手写 JSONP 并返回 Promise 对象
参数 url,data:JSON 对象, callback 函数
原理:<script > 元素不受同源策略的影响, 可以进行 Ajax 传输. 当 script 元素访问时, 返回由回调函数进行包裹的 JSON 数据. 在回调函数中获取数据进行处理.
- function JSONP(url, data = {}, callback = 'callback') {
- // 处理 JSON 对象, 拼接 url
- data.callback = callback
- let params = []
- for (let key in data) {
- params.push(key + '=' + data[key])
- }
- console.log(params.join('&'))
- // 创建 script 元素
- let script = document.createElement('script')
- script.src = url + '?' + params.join('&')
- document.body.appendChild(script)
- // 返回 promise
- return new Promise((resolve, reject) => {
- Windows[callback] = (data) => {
- try {
- resolve(data)
- } catch (e) {
- reject(e)
- } finally {
- // 移除 script 元素
- script.parentNode.removeChild(script)
- console.log(script)
- }
- }
- })
- }
调用方法
1, 创建 script 元素, 设置 src 属性, 并插入文档中, 同时触发 Ajax 请求.
2, 返回 Promise 对象, then 函数才行继续, 回调函数中进行数据处理
3,script 元素删除清理
- JSONP('http://photo.sina.cn/aj/index', {
- page: 1,
- cate: 'recommend'
- }, 'jsoncallback').then(data => {
- console.log(data)
- })
其他解决方案
如修改 document.domain 实现主域相同不同子域的框架间的交互等, 在实际项目中都很少使用, 这里不再赘述.
来源: http://www.jianshu.com/p/4bc22c754e45