一、跨域以及解决方案
1. 同源策略
- 浏览器同源策略限制请求
- 同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
- 限制以下行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 Js对象无法获得
- AJAX 请求不能发送
- 有三个标签是允许跨域加载资源:
<img src=XXX>
<link href=XXX>
<script src=XXX>
2. 解决方案
通过 jsonp 跨域
- script标签不受策略影响,可以动态生成script去请求数据,但是仅限 get 请求
- 原生实现
jsconst script = document.createElement('script'); script.type = 'text/javascript'; // 传参并指定回调执行函数为onBack script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack'; document.head.appendChild(script); // 回调执行函数 function onBack(res) { alert(JSON.stringify(res)); }
- vue 实现
- 如果使用 vue-resource 发送的请求
- axios [推荐]
- axios 不支持跨域
- 开发模式下,配置 vue-cli 代理即可,参考链接,具体中间件http-proxy-middleware
- 生产环境,一般通过 nginx 进行转发
- axios 不支持跨域
- script标签不受策略影响,可以动态生成script去请求数据,但是仅限 get 请求
document.domain + iframe跨域
- 仅限主域相同,子域不同的跨域应用场景
- 实现原理:两个页面都通过js强制设置 document.domain 为基础主域,就实现了同域html
// 父窗口:http://www.domain.com/a.html <iframe id="iframe" src="http://child.domain.com/b.html"> </iframe> <script> document.domain = 'domain.com'; const user = 'admin'; </script> // 子窗口:http://child.domain.com/b.html <script> document.domain = 'domain.com'; // 获取父窗口中变量 alert('get js data from parent ---> '+ window.parent.user); </script>
location.hash + iframe
- 实现原理: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
- 具体实现: A域:a.html -> B域:b.html -> A域:c.html,
- a 与 b 不同域只能通过 hash 值单向通信,b 与 c 也不同域也只能单向通信,但 c 与 a 同域,所以 c 可通过 parent.parent访问a页面所有对象。
window.name + iframe跨域
- window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
postMessage 跨域
- postMessage 一般用于解决以下问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
html<!-- a页面:http://www.domain1.com/a.html --> <iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"> </iframe> <script> const iframe = document.getElementById('iframe'); iframe.onload = function() { const data = { name: 'aym' }; // 向domain2传送跨域数据 iframe.contentWindow.postMessage (JSON.stringify(data), 'http://www.domain2.com'); }; // 接受domain2返回数据 window.addEventListener ('message', function(e) { alert('data from domain2 ---> ' + e.data); }, false); </script> <!-- b页面:http://www.domain2.com/b.html --> <script> // 接收domain1的数据 window.addEventListener ('message', function(e) { alert('data from domain1 ---> ' + e.data); const data = JSON.parse(e.data); if (data) { data.number = 16; // 处理后再发回domain1 window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com'); } }, false); </script>
- postMessage 一般用于解决以下问题:
跨域资源共享(CORS):主流的跨域解决方案 参考链接
- 服务端设置 Access-Control-Allow-Origin 即可
- 若要带cookie请求:前后端都需要设置。
- 前端: 检查前端设置是否带cookie:xhr.withCredentials = true;
- 通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
- 简单请求:
- 使用下列方法之一:
- GET
- POST
- Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
- 使用下列方法之一:
- 不符合以上条件的请求就肯定是复杂请求了。 复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求, 该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。
- OPTIONS 预检请求
- 请求头:
- Origin:当前请求源,和响应头里的Access-Control-Allow-Origin 对标, 是否允许当前源访问,Origin是不可修改的
- Access-Control-Request-Headers:本次真实请求的额外请求头,和响应头里的Access-Control-Allow-Headers对标,是否允许真实请求的请求头
- Access-Control-Request-Method:本次真实请求的额外方法,和响应头里的Access-Control-Allow-Methods对标,是否允许真实请求使用的请求方法
- 响应头:
- Access-Control-Allow-Credentials:
- 这里的Credentials(凭证)其意包括:Cookie ,授权标头或 TLS 客户端证书,默认CORS请求是不带Cookies的,这与JSONP不同,JSONP每次请求都携带Cookies的,当然跨域允许带Cookies会导致CSRF漏洞。如果非要跨域传递Cookies,web端需要给ajax设置withCredentials为true,同时,服务器也必须使用Access-Control-Allow-Credentials头响应。此响应头true意味着服务器允许cookies(或其他用户凭据)包含在跨域请求中。另外,简单的GET请求是不预检的,即使请求的时候设置widthCrenditials为true,如果响应头不带Access-Control-Allow-Credentials,则会导致整个响应资源被浏览器忽略。
- Access-Control-Allow-Headers
- Access-Control-Allow-Methods
- Access-Control-Allow-Origin
- Access-Control-Expose-Headers
- 在CORS中,默认的,只允许客户端读取下面六个响应头(在axios响应对象的headers里能看到):
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
- 如果这六个以外的响应头要是想让客户端读取到,就需要设置Access-Control-Expose-Headers这个为响应头名了,比如Access-Control-Expose-Headers: Token
- 在CORS中,默认的,只允许客户端读取下面六个响应头(在axios响应对象的headers里能看到):
- Access-Control-Max-Age:设置预检请求的有效时长,就是服务器允许的请求方法和请求头做个缓存。
- Access-Control-Allow-Credentials:
- 请求头:
- OPTIONS 预检请求
nginx 代理跨域
- nginx配置解决iconfont跨域
- 浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。text
location / { add_header Access-Control-Allow-Origin *; }
- 浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。
- 反向代理
- 跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
- 通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。text
#proxy服务器 server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; # 修改cookie里域名 index index.html index.htm; # 用webpack-dev-server等中间件代理接口访问nignx时, # 此时无浏览器参与,故没有同源限制,面的跨域配置可不启用 add_header Access-Control-Allow-Origin http://www.domain1.com; # 当前端只跨域不带cookie时,可为* add_header Access-Control-Allow-Credentials true; } }
- nginx配置解决iconfont跨域
nodejs 中间件代理跨域
- node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。
WebSocket协议跨域
- WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
- 原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。html
<div>user input: <input type="text"> </div> <script src="./socket.io.js"></script> <script> const socket = io('http://www.domain2.com:8080'); // 连接成功处理 socket.on('connect', function() { // 监听服务端消息 socket.on('message', function(msg) { console.log('data from server: ---> ' + msg); }); // 监听服务端关闭 socket.on('disconnect', function() { console.log('Server socket has closed.'); }); }); document.getElementsByTagName('input')[0].onblur = function() { socket.send(this.value); }; </script>