走过路过来看看各种实现跨域的方式~
跨域是什么?
说到跨域我们先来说说同源,同源是指"协议+域名+端口"三者相同,所谓的跨域就是跨协议、跨域名、跨端口。
跨域导致的问题
由于安全原因,跨域访问是被各大浏览器所禁止的。
Cookie、LocalStorage 和 IndexDB 无法跨域读取
假如你登录了某个银行网站那么该网站会发送cookie作为登录凭证。假如此时你访问了恶意网站,对其发送请求如果可以携带cookie跨域,那么cookie会发送到恶意网站,此时就可以模拟用户去银行网站发送请求。
DOM同源策略也一样,不能通过iframe引入其他域下的页面直接操作其dom元素
在自己的网站嵌入别人的网站,如果嵌入的网站有登录功能,如果可以跨域获得嵌入页面的元素,那在我们的网站上可以轻松获取到用户的账号密码。
ajax请求不能跨域发送
如果不限制ajax跨域发送数据,就相当于网站的所有接口对所有人都是公开的
跨域解决方案
一.jsonp
jsonp主要是利用标签的src属性没有受同源策略限制来获取来自其他域名的数据。我们通常使用 img/script 标签。
先举个例子~ (bd搜索框) 在访问bd进行搜索时我们可以很轻松的获取搜索接口
https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=hello&cb=list
接口返回的结果
// 不难发现cb传什么返回的就是什么 list({q:"hello",p:false,s:["hello kitty","hello kitty乐园","hello 树先生","hello world","hellobike","hello tv","hello女神","hellotalk","hello语音","hello venus"]}); 复制代码开始来写我们的例子
<script>function list(data){console.log(data);} </script> <script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=hello&cb=list"></script> 复制代码好了这就已经实现了跨域了,我们访问了bd的接口!
加深点印象我们自己来写下服务端吧!
// client <script>function list(data){console.log(data);} </script> <script src="http://localhost:3000/getData?cb=list"></script> // server let express = require('express'); let app = express(); app.get('/getData', (req,res) => {let cbName = req.query.cbres.end(`${cbName}({name:'jw'})`); }); app.listen(3000); 复制代码缺点:它只支持GET请求而不支持POST等其它类型的HTTP请求,可能会导致xss攻击
二.CORS
CORS(Cross-Origin Resource Sharing, 跨源资源共享)是W3C出的一个标准,想要实现跨域主要靠服务器进行一些设置。客户端不用做任何更改!
先列点杂七杂八的东西,后面我们会用到!
Access-Control-Allow-Origin 允许的域
Access-Control-Allow-Credentials 允许携带cookie
Access-Control-Expose-Headers 允许客户端获取哪个头
Access-Control-Allow-Methods 允许的方法
Access-Control-Allow-Headers 允许哪些头
Access-Control-Max-Age 预检请求的结果缓存
废话不多说先上例子~启动两个服务
// 通过3000端口来启动index.html let express = require('express'); let app = express(); app.use(express.static(__dirname)); app.listen(3000);// 4000端口有getData接口 let express = require('express'); let app = express(); app.use(express.static(__dirname)); app.get('/getData', (req,res) => {console.log(req.headers);res.end('{name:"jw"}') }); app.listen(4000); 复制代码在html发送ajax请求
let xhr = new XMLHttpRequest(); xhr.open('get','/getData',true); xhr.onreadystatechange = function () {if(xhr.readyState === 4){if(xhr.status >=200 && xhr.status<300|| xhr.status ===304){console.log(xhr.response)}} } xhr.send(); 复制代码 我们可以发现虽然没有获取到数据但是4000端口其实是接收到了响应的,只是浏览器默认屏蔽了响应结果第一次改造服务端:
当请求发送过来时,要根据当前客户端的origin属性判断是否允许跨域
let express = require('express'); let app = express(); app.use(express.static(__dirname)); let whiteList = ['http://localhost:3000']; app.use(function (req,res,next) {if (whiteList.includes(req.headers.origin)) {// 设置某个域可以允许访问,也可以使用* 但是不建议这样做res.setHeader('Access-Control-Allow-Origin', req.headers.origin)}next(); }) app.get('/getData', (req,res) => {console.log(req.headers);res.end('{name:"jw"}'); }); app.listen(4000); 复制代码默认跨域是不携带cookie的,可是我想带!强制设置携带cookie
// client document.cookie = 'a=1' let xhr = new XMLHttpRequest(); xhr.withCredentials = true; 复制代码第二次改造服务端:
app.use(function (req,res,next) {if (whiteList.includes(req.headers.origin)) {res.setHeader('Access-Control-Allow-Origin', req.headers.origin); + res.setHeader('Access-Control-Allow-Credentials',true)}next(); }) 复制代码客户端还是不满意想在传递些自定义的头!
document.cookie = 'a=1' let xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.open('get','http://localhost:4000/getData',true); xhr.setRequestHeader('name', 'jw'); 复制代码第三次改造服务端:
app.use(function (req,res,next) {if (whiteList.includes(req.headers.origin)) {res.setHeader('Access-Control-Allow-Origin', req.headers.origin);res.setHeader('Access-Control-Allow-Credentials',true); + res.setHeader('Access-Control-Allow-Headers',"name");}next(); }); 复制代码客户端又发现发送put请求时。又出现了恶心的问题......
xhr.open('put','http://localhost:4000/getData',true); 复制代码 这回我一眼就看到了问题,肯定是没有设置允许接收的方法第四次改造服务端:
app.use(function (req,res,next) {if (whiteList.includes(req.headers.origin)) {res.setHeader('Access-Control-Allow-Origin', req.headers.origin);res.setHeader('Access-Control-Allow-Credentials',true);res.setHeader('Access-Control-Allow-Headers',"name"); + res.setHeader('Access-Control-Allow-Methods','PUT');}next(); }); app.put('/getData', (req,res) => {console.log(req.headers);res.end('{name:"jw"}'); }); 复制代码等等怎么多了个请求?
原来是这样对于非简单请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。类型为 options。
第五次改造服务端:
app.use(function (req,res,next) {if (whiteList.includes(req.headers.origin)) {res.setHeader('Access-Control-Allow-Origin', req.headers.origin);res.setHeader('Access-Control-Allow-Credentials',true);res.setHeader('Access-Control-Allow-Headers',"name");res.setHeader('Access-Control-Allow-Methods','PUT');// 设置预检查发送的时间 + res.setHeader('Access-Control-Max-Age',6000);if (req.method === 'OPTIONS'){res.end();}else{next();}} }); 复制代码到此客户端终于不搞事了。服务端为了表示友好给他写了个头
app.put('/getData', (req,res) => {res.setHeader('info','ok');res.end('{name:"jw"}'); }); 复制代码xhr.onreadystatechange = function () {if(xhr.readyState === 4){if(xhr.status >=200 && xhr.status<300|| xhr.status ===304){ + console.log(xhr.getResponseHeader('info'));console.log(xhr.response);}} } 复制代码这才发现客户端原来是收不到的,好吧!还需要告诉客户端可以拿到这个值
app.put('/getData', (req,res) => {res.setHeader('info', 'ok');res.setHeader('Access-Control-Expose-Headers', 'info');res.end('{name:"jw"}'); }); 复制代码到此! 终于知道cors是个什么鬼了,就是设置各种约定好的头
CORS 不仅使用方便,支持所有类型请求,具有权限控制,而且浏览器原生支持,我们可以轻易的处理请求异常。利于排查错误。所以我们大多数情况下会首选该方式。在低版本浏览器可以使用jsonp配合其他方式来兼容。
三.postMessage
在来看看两个窗口是如何实现跨域的,先看看H5的postMessage! postMessage()方法可以安全地实现跨源通信
现在的需求是这样的 http://127.0.0.1:3000/a.html 想对http://127.0.0.1:4000/b.html 说我爱你,之后b页面收到消息后对其说我不爱你~,这事多么悲伤的一个故事#_#。
// a.html <iframe src="http://localhost:4000/b.html" id="frame" onload="load()" frameborder="0"></iframe> <script>function load() {let frame = document.getElementById('frame');frame.contentWindow.postMessage('我爱你','http://localhost:4000');window.onmessage = function (e) {console.log(e.data);}} </script>// b.html <script>window.onmessage = function (e) {console.log(e.data);e.source.postMessage('我不爱你',e.origin);} </script> 复制代码四.document.domain
domain方式跨域要先保证两个页面属于同一个域下的 我们在hosts文件增加两个映射关系,位置在 C:\Windows\System32\drivers\etc
127.0.0.1 a.fullstackjavascript.cn 127.0.0.1 b.fullstackjavascript.cn 复制代码// a.html <iframe src="http://b.fullstackjavascript.cn:4000/b.html" frameborder="0" id="frame"onload="load()" ></iframe> <script>function load() {document.domain = 'fullstackjavascript.cn';console.log(frame.contentWindow.name);} </script>// b.html <script>document.domain = 'fullstackjavascript.cn';var name = 'jw'; </script> 复制代码访问http://a.fullstackjavascript.cn:3000/a.html,发现是可以拿到另一个页面中的值
五.location.hash
用hash传递数据就比较有意思了,需要借助第三个页面实现。
先用粗俗的话介绍一下,先有三个页面hash1,hash2,hash3他们之间的关系是hash1和hash2是同域下的hash3是独立域下的,我们通过hash1页面用iframe引入hash3页面,可以在引入时给hash3页面传递hash值,hash3接到hash值后算出需要返回结果,在创建iframe引入hash2把结果通过hash的方式传递给hash2,hash2和hash1是同域的,hash2可以直接操控hash1的值,此时hash1页面可以监控hash值的变化来获取hash2的数据
效果是没问题的,不过感觉有点麻烦~
六.window.name
使用window.name跨域是利用切换路径后window上的name属性是会保留的。不懂?没关系,举个例子,还是有三个页面a,b,c。a和b是同域下的,c自己一个域。a先引用c,c将想表达的内容放到name,属性上之后,a改变引用路径,改成引用b,此时name属性不会被删除,因为a,b是同域的,所以可以直接获取。
// a.html <iframe src="http://a.fullstackjavascript.cn:4000/c.html" id="myFrame" onload="load()" frameborder="0"></iframe> <script>let first = truefunction load() {if(first){myFrame.src = 'http://b.fullstackjavascript.cn:3000/c.html';first = false}else{let name = myFrame.contentWindow.name;console.log(name);}} </script> // c.html <script>window.name = '我爱你' </script> 复制代码直接访问http://b.fullstackjavascript.cn:3000/a.html发现可以拿到我们想要的结果啦~
七.WebSocket
WebSocket最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话。这个不是咱们考虑的问题。咱们现在的话题是跨域,恰恰WebSocket是没有同源限制的!
可以直接创建socket对象和服务器通信发消息
// socket.html <script>let socket = new WebSocket('ws://localhost:4000');socket.onopen= function () {socket.send('我爱你');}socket.onmessage = function (e) {console.log(e.data);} </script> 复制代码服务端需要ws模块
$ npm install ws 复制代码let express = require('express'); let app = express(); let WebSockect = require('ws'); let wss = new WebSockect.Server({port:4000}); wss.on('connection',function (ws) {ws.on('message',function (data) {console.log(data);ws.send('我不爱你');}); }) 复制代码我们都知道好东西往往不兼容,我们可以使用socket.io模块保证兼容性问题。这里我就不过多去介绍websocket的使用了
八.Proxy
我们来看看服务端是如何实现代理的,也就是我们常说的反向代理!大家可能都对webpack比较熟悉了,webpack-dev-server内置了http-proxy-middleware。那好吧咱们就用express配合这个中间件插件来试试这东西怎么玩。
// 3000服务器 let express = require('express'); let app = express(); var proxy = require('http-proxy-middleware'); let proxyOptions = {target: 'http://localhost:4000',changeOrigin: true }; app.use('/api', proxy(proxyOptions)); app.listen(3000); 复制代码// 4000服务器 let express = require('express'); let app = express(); app.get('/api', (req,res) => {res.end('ok'); }) app.listen(4000); 复制代码当我们访问http://localhost:3000/api路径时请求会被转发到4000服务器上,将4000服务器上的结果响应给客户端
九.Nginx
我相信大家对Nginx都并不陌生,他可以实现代理我们的接口。这里我就简单的使用一下展示下效果! 这里以windows系统为例:
先要安装nginx 下载地址
更改conf/nginx.conf增加访问代理
location /api/ {proxy_pass http://localhost:4000; } 复制代码双击nginx.exe运行,访问localhost/api发现返回了4000服务器上的结果。
废了这么多口水,终于把前端常用的跨域手段介绍了一遍!有什么疑问欢迎联系我!
结语
喜欢的点个赞吧^_^!
支持我的可以给我打赏哈!
总结
以上是生活随笔为你收集整理的走过路过来看看各种实现跨域的方式~的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 局域网物理机与虚拟机的互通访问
- 下一篇: kafka comsumer