什么是跨域
跨域:一个域下的文档或脚本试图去请求另一个域下的资源
广义的跨域包含一下内容:
1.资源跳转(链接跳转,重定向跳转,表单提交)
2.资源请求(内部的引用,脚本script,图片img,frame)
3.script内部发起的请求(ajax,dom请求,和js跨域调用
跨域问题出现:
只有浏览器端出现,直接用终端请求,是不会出现跨域拦截,是属于浏览器端的安全策略,浏览器将不同源的请求进行了拦截,限制了跨域资源访问
什么是同源
同源策略:same origin policy,如果两个资源(页面)`协议`,`域名`,`端口`都相同,那么就是同源。
即使:两个不同域名指向同一个ip,也算非同源
非同源(跨域)有哪些限制
* cookie,localstorage,indexDB无法读取
* Dom和JS对象无法互通
* Ajax请求不能发送
常见的跨域demo
URL 说明 是否允许通信
http://www.domain.com/a.js
http://www.domain.com/b.js 同一域名,不同文件或路径 允许
http://www.domain.com/lab/c.js
http://www.domain.com:8000/a.js
http://www.domain.com/b.js 同一域名,不同端口 不允许
http://www.domain.com/a.js
https://www.domain.com/b.js 同一域名,不同协议 不允许
http://www.domain.com/a.js
http://192.168.4.12/b.js 域名和域名对应相同ip 不允许
http://www.domain.com/a.js
http://x.domain.com/b.js 主域相同,子域不同 不允许
http://domain.com/c.js
http://www.domain1.com/a.js
http://www.domain2.com/b.js 不同域名 不允许
前端解决跨域的方法
-
JSONP
- 原理:由于在一个页面内部js如果要访问另一个非同源域的数据是被浏览器限制的,但是浏览器在解析和加载script标签的时候,是允许一个页面加载多个域的js标签,而js标签其实又是类似一种get请求的方式,只是返回的数据是一个JSON格式的脚本对象。
- 用法:将get请求封装到script标签中,利用script标签做get请求,最后解析script标签数据
- 代码:
var script = document.createElement('script') script.style = 'text/javascript' //将要请求的get地址传到script的src属性,并且添加回调函数 script.setAttribute('scr','http://www.domain2.com:8080/login?user=admin&callback=handleCallback') //将script标签放入文档 document.head.appendChild(script) //执行回调函数 function handleCallback(res){ //处理JSON格式数据 alert(JSON.stringify(res)) }
-
跨域资源共享
- 原理:
- cors:cross-origin -resouce sharing跨域资源共享,允许浏览器向跨源服务器发出http请求,需要浏览器和服务器同时支持
- 浏览器在发出跨域请求时:1。简单请求,直接在头部信息中带上origin信息,标识属于哪个源,服务端配置
access-control-allow-origin
接受哪些域名跨域访问,可以是*
允许所有 - 如果是非简单请求会在正式请求之前,发送预检请求
- 简单来说(简单请求):
- 是一个双端配合,允许打卡跨域资源限制的一种手段
- 浏览器端:请求头携带origin信息
- 服务端:配置
access-control-allow-origin
允许哪些源跨域
- 代码:
- 原生:
xmlHttpRequest
不需要配置请求头,简单请求会自动带上origin属性var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容 xhr.open('post','http://www.domain2.com:8080/login',true); xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); xhr.send('user=admin'); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } };
- AJAX:有一个属性
crossDomain
,配置为true之后会让请求头携带orgin跨域额外信息,但是不会自动包含cookie$.ajax({ ... xhrFields: { withCredentials: true // 前端设置是否带cookie },crossDomain: true,// 会让请求头中包含跨域的额外信息,但不会含cookie ... });
- VUE中的axios,默认也会把origin放到请求头,不需要额外配置
- 原生:
- 服务端配置
/* * 导入包:import javax.servlet.http.HttpServletResponse; * 接口参数中定义:HttpServletResponse response */ // 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/' response.setHeader("Access-Control-Allow-Origin","http://www.domain1.com"); // 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示 response.setHeader("Access-Control-Allow-Credentials","true"); // 提示OPTIONS预检时,后端需要设置的两个常用自定义头 response.setHeader("Access-Control-Allow-Headers","Content-Type,X-Requested-With");
- 原理:
-
反向代理(NodeJs中间件代理跨域)(Vue代理跨域)(Nginx转发代理)
-
场景:当服务器端无法修改cors,并且client全部换成jsonp方式比较繁琐时,只能通过中间代理服务器,将代理服务器设置为支持CORS或者代理服务器和请求页面同源,使页面可以直接请求代理服务器,在通过代理服务器进行接口代理,代理请求目标接口地址,返回数据
-
原理:跨域限制仅是浏览器端的安全策略,并不是http协议的固有限制,所以中间服务器在参数和cookie有效的情况下是可以正常的请求目标服务器的,Vue(
node+webpack+webpack-dev-server
)中配置proxy
就是启动一个同源的服务进行接口代理 -
注意:
-
代码:
- webpack-dev-server代理
module.exports = { entry: {},module: {},... devServer: { historyApiFallback: true,proxy: [{ context: '/login',target: 'http://www.domain2.com:8080',// 代理跨域目标接口 changeOrigin: true,secure: false,// 当代理某些https服务报错时用 cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改 }],noInfo: true } }
- node express反向代理
var express = require('express'); var proxy = require('http-proxy-middleware'); var app = express(); app.use('/',proxy({ // 代理跨域目标接口 target: 'http://www.domain2.com:8080',changeOrigin: true,// 修改响应头信息,实现跨域并允许带cookie onProxyRes: function(proxyRes,req,res) { res.header('Access-Control-Allow-Origin','http://www.domain1.com'); res.header('Access-Control-Allow-Credentials','true'); },// 修改响应信息中的cookie域名 cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改 })); app.listen(3000); console.log('Proxy server is listen at port 3000...');
- Nginx转发代理
#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; } }
-
-
其他js HACK方法
-
主域相同子域不同场景:docment.domain+iframe
-
三个页面跨域互传 : location.hash + iframe
-
window.name + iframe
-
H5新增的postMessage(跨窗口消息互通方法)
-
关于iframe:
-
location.hash:
- url 末尾
#
后面跟随的时页面片段标识符,用来表示浏览器渲染哪部分页面信息,但是改变这个值页面不会重新刷新, 并且设置之后,所有页面可以通过document对象访问该窗口的location 信息进行获取,可以实现巧妙跨域 - 代码:
//父窗口可以把信息,写入子窗口的片段标识符。 var src = originURL + ‘#’ + data; document.getElementById(‘myIFrame’).src = src; //子窗口通过监听hashchange事件得到通知。 window.onhashchange = checkMessage; function checkMessage() { var message = window.location.hash; // … }
- url 末尾
-
同理,window.name也是一种hack方法实现跨域的方式
-
浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性。
window.name = data;
接着,子窗口跳回一个与主窗口同域的网址。
location = ‘http://parent.url.com/xxx.html’;
然后,主窗口就可以读取子窗口的window.name了。
var data = document.getElementById(‘myFrame’).contentWindow.name;
这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。
-
-
postMessage
-
总结:
常用的,还是代理,还有CORS,或者是JSONP这三种方式,禁止跨域本来就是浏览器的一种安全策略,虽然有一定限制开发者的手脚,但是在一些特殊的网站或者安全性要求比较高的网站,网络安全还是不可忽视的
非常感谢:下面的文章给了我很多的帮助,感谢各位前行者的辛苦付出,可以点击查阅更多信息