跨域最优解

作者: lihuanji | 来源:发表于2018-07-25 20:01 被阅读109次

项目中和面试经常遇到跨域问题,老生常谈的问题,今天就来讲讲跨域:

浏览器不支持跨域?

  1. 用户登录A网站,在cookie里面存入了登录信息,用户再登录B网站,如果没有同源策略,B网站能够轻易拿到用户A网站信息,去伪造用户向A网站发送信息。
  2. DOM元素,iframe嵌入页面,如果没有同源策略,嵌入一个淘宝页面,伪造成淘宝钓鱼网站,用户输入账户密码可以通过dom操作拿到账户信息。
  3. ajax,LocalStorage也不支持跨域,不能随意拿取信息。

总的来说浏览器不支持跨域是为了安全,但在项目中有些情况下需要去跨域。

先讲讲什么是同源策略

  • 协议
  • 域名
  • 端口

3个都一样为同域,其中某一个不一样那么就跨域了。

列如你的网站域名是http://www.xxx.com

但是在浏览器中link标签(加载css) img(加载图片) script(加载js) 标签不受同源策略,可以随便跨域,去加载其他域下面的资源。

jsonp

由于script标签不受同源策略,可以用script去跨域,原理就是创建一个script标签,src地址去引入其他域下的js文件并且带入参数和回调函数,js文件返回一个执行函数,去执行window下的回调函数。

例如现在要向www.xxx.com去请求数据,首先我们在window下声明一个函数a,然后创建一个script标签<script src="www.xxx.com?params=xxx&cb=a"></script>,params代表请求参数,cb指定成功后需要调用的回调函数,这个请求返回一个js文件内容a({data: 'xxx'}),执行了最开始在window下声明的函数a并且传入我们需要的数据。

下面来看看,如果封装一个简单的jsonp函数

    function jsonp({ url, params }) {
        return new Promise((resolve, reject) => {
            // 创建script标签
            const script = document.createElement('script');
            
            // window下声明回调函数
            window.cb = function (data) {
                resolve(data);
                document.body.removeChild(script);
            }
            
            const arr = []
            
            // 拼接参数
            for (let key in params) {
                arr.push(`${key}=${params[key]}`);
            }
            
            script.src = `${url}?${arr.join('&')&cb=cb}`
            
            // 加载文件
            document.body.appendChild(script);
        })
    }
    
    
    jsonp({
        url: 'www.xxx.com/xxx',
        params: {xx: xx},
    }).then((data) => {
        // data数据
    })

后端实现

const express = require('express');
const app = express();

app.get('/xxx', function(req, res) {
    const { xx, cb } = req.query;
    
    // 返回cb(xxx)
    res.end(`${cb}(获取到数据${xx})`);
})

app.listen(80)

jsonp缺点: 只支持get请求并且不安全,如果加载第三方返回script标签,会出现恶意攻击(xss)。

cors

解决jsonp缺点 支持get post put delet请求,由服务端控制,安全性高,前端正常发送ajax请求,项目中最常用的方式。

上面说了同源策略是浏览器的行为,其实我们的请求能够到达服务器,只是浏览器给屏蔽掉了数据,cors就是利用http header头告诉浏览器一些信息,浏览器放开同源策略。

简单请求

  1. 必须是以下三种方法
  • get
  • post
  • head(什么是head请求? -> 只返回响应头,不会返回响应内容,http1.0定义的方法,前端用的很少)
  1. 请求头只能包含以下字段
  • Accept

  • Accept-Language

  • Content-Language

  • Last-Event-ID

  • Content-Type:application/x-www-form-urlencoded multipart/form-data text/plain

如果不满足上面的简单请求,那么就是非简单请求,非简单请求的时候浏览器会首先发出一个预检请求OPTIONS到服务器,把将要发送的请求方法,请求头给服务器,如果服务器返回成功,那么浏览器才会发出正式的请求,否则报错。

服务器可以设置的header头

  • Access-Control-Allow-Origin: www.baidu.com: 允许百度这个域下的请求,可以配置多个域,以逗号隔开,还能设置为*(*代表所有域都能请求但是origin写*不能允许携带cookie凭证
  • Access-Control-Allow-Methods: POST,GET,PUT,DELETE: 允许哪些方法,以逗号隔开
  • Access-Control-Allow-Headers: name,token: 表示服务器支持name和token字段
  • Access-Control-Allow-Credentials: true: 允许cookie跨域
  • Access-Control-Max-Age: 6: 预检请求的有效期,单位为秒,相当于把预检请求缓存下来,下次直接发送正式请求,不用再去预检是否服务器支持该请求方法
  • Access-Control-Expose-Headers: token: 允许用js获取响应头里的token值
  • Access-Control-Request-Headers: token: 指定浏览发送请求时,需要带上的额外请求头

用express简单实现一个cors跨域服务端

    const express = require('express');
    const app = express();
    
    app.use((req, res, next) => {
        
        res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');
        res.setHeader('Access-Control-Allow-Methods', 'POST,GET,PUT,DELETE');
        res.setHeader('Access-Control-Allow-Headers', 'name,token');
        res.setHeader('Access-Control-Allow-Credentials', true);
        res.setHeader('Access-Control-Max-Age', 6);
        res.setHeader('Access-Control-Expose-Headers', 'token');
        res.setHeader('Access-Control-Request-Headers', 'token');
        
        // 如果为预检请求,直接返回同意,避免浪费之后处理资源
        if (req.method === 'OPTIONS') {
            res.end();
        }
        
        next();
    });
    
    app.put('/uset', (req, res) => {
        res.end('yes');
    });

postMessage

除了与服务器通信需要跨域,有时候会存在iframe加载跨域,window.open()一个标签页跨域通信,html5为了解决这个问题新增了postMessage方法。

postMessage(需要传递的参数, 目标源)

iframe:

localhost:3000 下 a.html

<iframe 
    src="http://localhost:4000/b.html"
    id="frame"
    onload="load()"
></iframe>
<script>
    function load () {
        const i = document.getElementById('frame')
        i.contentWindow.postMessage('send', 'http://localhost:4000/')
    }
    
    window.addEventListener('message', function(e) {
        console.log(e.data) // 接收到ok
    })
</script>

localhost:5000 下 b.html
<script>
 window.addEventListener('message', function(e) {
     console.log(e.data) 接收到send
     
     e.source.postMessage('ok', e.origin)
 }, false)
</script>

注意: 一定要在onload结束之后再发送postMessage,否则子页面接收不到消息

window.open()

localhost:3000 下 a.html
<input type="button" value="打开窗口" onclick="open_new()">
<script>
    function open_new() {
        const newWindow = window.open('http://localhost:4000/b.html');
        
        // 跨域无法监听onload 如果同域下跨域使用
        newWindow.onload = function () {
            newWindow.postMessage('open', 'http://localhost:4000/')
        }

        setTimeout(() => {
            newWindow.postMessage('open', 'http://localhost:4000/')
        }, 1000);
    }

    window.addEventListener('message', function(e) {
        console.log(e.data)
    })
</script>

localhost:5000 下 b.html
<script>
window.addEventListener('message', function(e){
    console.log(e)
}, false);
        
window.opener.postMessage('Nice to see you', 'http://localhost:3000/');
</script>

window.open()如果是跨域,则无法监听onload事件

document.domain

该方法存在一定的限制条件,必须是2个网页的一级域名相同,用法也很简单,可以使2个页面共享cookie。

例如: a.xxx.comb.xxx.com:

同时设置docment.domain = 'xxx.com'

服务器设置cookie时也设置到xxx.com

这样的话,在这个一级域名下的所有二三级域名,都可以互通cookie

window.name

利用window.name改变网页地址,该值不变的特点,可以做到跨域。

a网站iframe加载b网站,b网站把需要传输的数据放入window.name中,然后重定向到a网站下同域的网址,这时a网站可以顺利的拿到window.name属性。

localhost: 3000 下的a.html

<iframe id="fra" src="http://localhost:4000/b.html" onload="load()"></iframe>
<script>
    function load(){
        const f = document.getElementById('fra');

        console.log(f.contentWindow.name);
    }
</script>

localhost: 4000 下的b.html
<script>
    window.name = 'hello'

    window.location = 'http://localhost:3000/c.html'
</script>

localhost: 3000 下的c.html
无需任何代码,建一个空的html文件

location.hash

hash值得变化不会导致页面刷新,通过a页面改变b页面hash值,b页面不能直接通过parent去修改a页面的hash值,需要通过加载一个a域下的代理iframe修改,从而实现跨域通信。

localhost: 3000 下的a.html

<iframe id="fra" src="http://localhost:4000/b.html" onload="load()"></iframe>
<script>
    function load(){
        const f = document.getElementById('fra');
        f.src = 'http://localhost:4000/b.html#hello'
    }
    window.addEventListener('hashchange', function() {
        console.log(window.location.hash)
    })
</script>

localhost: 4000 下的b.html

<script>
    addEventListener('hashchange', function() {
        console.log(window.location.hash)
        
        const ifr = document.createElement('iframe');
        ifr.style.display = 'none';
        ifr.src = 'http://localhost:3000/c.html#yes';
        document.body.appendChild(ifr);
    })
</script>

localhost: 3000 下的c.html
<script>
    parent.parent.location.hash = window.location.hash.substring(1);
</script>

other

以上就是项目中最常用到的跨域方式,还有一种与服务器通信方式websocket不受浏览器同源策略。平常在开发中经常用到的webpack-dev-server,nginx,http-server...利用的是代理去请求。

先是本地起了一个代理服务器,前端发送http到代理服务器,由代理服务器请求后端的接口,跨域是浏览器的行为,2个服务器之间是没有同源策略的,所有代理服务器拿到数据后,再返回给前端。代理服务器和前端同源。

image

相关文章

  • 跨域最优解

    项目中和面试经常遇到跨域问题,老生常谈的问题,今天就来讲讲跨域: 浏览器不支持跨域? 用户登录A网站,在cooki...

  • 深入跨域问题(1) - 初识 CORS 跨域资源共享

    阅读目录: 深入跨域问题(1) - 初识 CORS 跨域资源共享(本篇)深入跨域问题(2) - 利用 CORS 解...

  • nginx解决跨域问题

    一. 产生跨域的原因 1.浏览器限制 2.跨域 3.XHR(XMLHttpRequest)请求 二. 解决思路 解...

  • 深入跨域问题(3) - 利用 JSONP 解决跨域

    深入跨域问题(1) - 初识 CORS 跨域资源共享;深入跨域问题(2) - 利用 CORS 解决跨域深入跨域问题...

  • 如何克服ajax跨域

    背景 最近做项目经常碰到跨域的问题,总在联调中浪费了不少时间,当然若时间充裕的话,跨域基本都是可以通jsonp来解...

  • 关于设置env等环境变量的思考

    1、如何处理跨域后台处理跨域前端处理跨域浏览器处理跨域 前端本地处理跨域:代理线上跨域的处理方式:Nginx反向代...

  • Web前后端跨域问题处理

    跨域问题有前台跨域(iframe间)和后台跨域。 前台跨域的解决方案可以采用跨域文档通讯(Cross domain...

  • JS跨域

    js跨域说明https://www.cnblogs.com/zuoci/p/7239891.html vue环境解...

  • 跨域

    跨域 什么是跨域: 解决跨域 通过jsonp原理:在页面引入跨域js和css时,没有存在跨域问题.因此可以动态创建...

  • 跨域问题详解分析

    参考文档 CORS详解 跨域资源共享 CORS 详解 js中几种实用的跨域方法原理详解 跨域的那些事儿 跨域与跨域...

网友评论

    本文标题:跨域最优解

    本文链接:https://www.haomeiwen.com/subject/ayymmftx.html