• 前端常见跨域解决方案


    undefined

    啊。本来是打算昨天总结这块内容的。然而,突然忘了之前是在哪个地方看到的文章,找了半天硬是没找到,奈何就放弃了。好在今天乱刷着各种技术文章,终于看到了!!那么,请看跨域!

    什么是跨域?

    跨域是指一个域下的文档或者脚本试图去请求另一个域下的资源。这里描述的是广义的跨域。

    • 资源跳转:A链接、重定向、表单提交
    • 资源嵌入: <link>、<script>/<img>/<frame>等dom标签,以及样式中的background:url()等的文件外链
    • 脚本请求:js发起的ajax请求、dom和js对象的跨域操作。

    然而,我们一般讨论的都是狭义的跨域,也就是由浏览器同源策略限制引起的一类请求场景。

    什么是同源策略

    同源策略/SOP(same origin policy)是一种约定,是浏览器最核心也是最基本的安全功能,如果缺乏了同源策略,浏览器很容易受到XSS、CSRF(跨站请求伪造)等算计。

    场景:
    假设用户在访问银行网站的时候并没有登出。他又跑到了任意其他网站上,刚好这个网站上有恶意的js代码,在后台请求银行网站的信息。因为用户目前仍然是银行站点的登录状态,那么恶意代码就可以在银行站点做任何事情。

    所以同源策略限制以下几种行为:

    1. Cookie、LocalStorage 和 IndexDB无法读取
    2. DOM 和 JS对象无法获取
    3. AJAX请求不能发送

    所谓的同源就是指“协议 + 域名 + 端口”三者要相同,即便两个不同的域名指向同一个ip地址,也非同源。具体的跨域场景看下表:

    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://x.domain.com/b.js
    http://domain.com
    主域相同,子域不同 不允许
    http://www.domain.com/a.js
    http://www.domain2.com/b.js
    http://domain.com
    不同域名 不允许

    跨域的解决方案

    然而跨域的需求却是存在的,两个互相可信的站点之间可以保持信息沟通是存在需求的。于是,想办法实现跨域吧~~

    jsonp实现跨域

    有关jsonp的内容已经在之前的文章Ajax的实现(ajax与jsonp)/)说得挺详细的了,直接跳转吧~

    但是jsonp有一个缺点:只能实现get一种请求。

    document.domain + iframe跨域

    此方案同样有所缺点:仅限主域相同,子域不同的跨域应用场景。
    实现原理: 两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

    父窗口:

    1
    2
    3
    4
    5
    <iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
    <script>
    document.domain = 'domain.com';
    var user = 'admin';
    </script>

    子窗口

    1
    2
    3
    4
    <script>
    document.domain = 'domain.com';
    alert('get js data from parent' + window.parent.user);
    </script>

    location.hash + iframe

    实现原理:A域与B域要相互通信,通过中间页c来实现。三个页面,不同域之间利用iframelocation.hash传值,相同域之间直接用js访问来通信。

    具体实现:A域:a.html => B域:b.html => A域:c.html, a 与 b 不同于只能通过hash值单向通信,b 与 c 也不同于也只能单向通信,但是c 与 a 同域,所以c可通过parent.parent访问a页面的所有对象。

    a.html:(http://www.domain1.com/a.html)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <iframe id="iframe" src="http://www.domain2.com/b.html" style="display=none;">
    <script>
    var iframe = document.getElementById('iframe');
    // 向b.html传hash值
    setTimeout(function () {
    iframe.src = iframe.src + '#user=admin';//改变url的hash值
    }, 1000);
    // 开放给同域c.html的回调方法
    function onCallback(res) {
    alert('data from c.html:' + res);
    }

    b.html:(http://www.domain2.com/b.html)

    1
    2
    3
    4
    5
    6
    7
    <iframe id="iframe" src="http://www.domain1.com/c.html" display="none;">
    <script>
    var iframe = document.getElementById('iframe');
    window.onhasChange = function() {
    iframe.src =iframe.src + location.hash; //通过hash传递给c页面
    }
    </script>

    c.html:(http://www.domain1.com/c.html)

    1
    2
    3
    4
    5
    <script>
    window.onhasChange = function() {
    window.parent.parent.onCallback('hello:' + location.hash);
    };
    </script>

    window.name + iframe

    window.name的独特之处:name值在不同的页面(甚至不同的域名)加载后依旧存在,并且可以支持非常长的name值(2MB);
    实现原理:通过动态创建一个iframe,访问跨域页,在跨域成功后跳转回同域的代理页,利用name长期存在的特性便可以获取到数据传递给同域的本页

    a.html:(http://www.domain1.com/a.html)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement('iframe');
    // 加载跨域页
    iframe.src = url;
    // 这里会触发两次onoload事件,第一次是加载跨域页,第二次是加载代理页
    iframe.onload = function() {
    if(state === 0) {
    // 这里触发的是加载跨域页,切换到代理页
    iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
    state = 1;
    } else if (state === 1) {
    // 这里触发的是加载代理页,读取完同域window.name后
    callback(iframe.contentWindow.name);
    // 销毁iframe,释放内存,保证不被其他域iframe js访问
    destoryFrame();
    }
    }
    // 销毁iframe
    function destoryFrame() {
    iframe.contentWindow.document.write('');
    iframe.contentWindow.close();
    document.body.removeChild(iframe);
    }
    }
    // 请求跨域b页面数据
    proxy('http://www.domain2.com/b.html', function(data){
    alert(data);
    })

    proxy.html

    中间代理页,与a.html同域,内容为空即可

    b.html

    1
    2
    3
    大专栏  前端常见跨域解决方案ine"><script>
    window.name = 'data data data ...';
    <script>

    总结: 通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全的操作。

    postMessage跨域

    postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

    1. 页面和其打开的新窗口的数据传递(属于两个tab页之间的通信)
    2. 多窗口之间消息传递
    3. 页面与嵌套的iframe消息传递
    4. 上面三个场景的跨域数据传递

    用法:postMesssage(data, origin)方法接受两个参数

    • data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化
    • origin: 协议 + 主机 + 端口号,也可以设置为* ,表示可以传递给任意窗口,如果要指定和当前窗口同源的话,设置为’/‘

    a.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;">
    <script>
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
    var data = {
    name: 'aym'
    };
    // 向domain2传递跨域数据
    iframe.contentWindow.postMesssage(JSON.stringify(data), 'http://www.domain2.com')
    }
    // 接受domain2的返回数据
    window.addEventListener('message', function(e) {
    alert('data from domain2' + e.data)
    }, 1000)

    b.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script>
    window.addEventListener('message', function(e) {
    alert(e.data);
    var data = JSON.parse(e.data);
    if(data) {
    data.num = 16;
    window.parent.postMesssage(JSON.stringify(data), 'http://www.domain1.com');
    }
    })
    </scirpt>

    跨域资源共享(CORS)

    普通跨域请求: 只需要服务端设置Access-Control-Alloww-Origin即可,前端无须设置。

    带cookie请求: 前后端都需要设置字段,另外需注意:所带cookie为跨域请求接口所在域的cookie,而非当前页。

    目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。

    CORS和JSONP对比

    • JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
    • 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
    • JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。

    CORS与JSONP相比,无疑更为先进、方便和可靠。

    有关CORS的详情,可以跳转详解CORS跨域资源共享

    前端设置:

    1. 原生ajax
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      var xhr = new XMLHttpRequest(); //IE8 9需要使用window.XDomainRequest兼容
      // 前端设置是否带cookie
      xhr.withCredentials = true;
      xhr.open('post', 'http://www.domain2.com:8080/login', ture);
      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);
      }
      }

    服务端设置:

    若后端设置成功,前端浏览器控制台则不会出现跨域报错信息,反之,说明没设成功。

    1
    2
    3
    4
    5
    6
    // 跨域后台设置
    res.writeHead(200, {
    'Access-Control-Allow-Credentials': 'true', // 后端允许发送Cookie
    'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允许访问的域(协议+域名+端口)
    'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本无法读取cookie
    });

    nginx代理跨域(未写)

    这里nginx的使用。。。没学过,所以只当挖了个坑吧 因为和下面的nodejs结合vue实现差不多,所以,以后有机会接触再回来补这里的

    nodejs中间件代理跨域

    1. Vue框架的跨域(1次跨域)
      利用node + webpack + webpack-dev-server代理接口跨域。在开发环境下,由于vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域,无须设置headers跨域信息了。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      module.exports = {
      entry: {},
      module: {},
      ...
      devServer: {
      historyApiFallback: true,
      proxy: [{
      context: '/login',
      target: 'http://www.domain2.com:8080', // 代理跨域目标接口
      changeOrigin: true,
      cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改
      }],
      noInfo: true
      }
      }

    在一次慕课网的学习,提到到dev-sever.js进行手动设置代理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 手动添加接口代理 axios
    var apiRoutes = express.Router()
    apiRoutes.get('/getDiscList', function (req, res) {
    let url = 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg'
    axios.get(url, {
    headers: {
    referer: 'https://c.y.qq.com',
    host: 'c.y.qq.com'
    },
    params: req.query
    }).then((response) => {
    res.json(response.data)
    }).catch((err) => {
    console.log(err);
    })
    })

    1. 非vue(2次跨域)
      利用node + express + http-proxy-middleware搭建一个proxy服务器。

    前端代码

    1
    2
    3
    4
    5
    6
    7
    8
    var xhr = new XMLHttpRequest();
    // 前端开关:浏览器是否读写cookie
    xhr.withCredentials = true;
    // 访问http-proxy-middleware代理服务器
    xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
    xhr.send();

    代理服务器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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...');

    WebSocket协议跨域

    详细可以看webSocket那一章吧~~
    这里推荐使用Socket.io??

  • 相关阅读:
    忍者x3,SDCMS2.0发布模块,使用说明
    多一度评论 发布模块,如何使用?
    端口站群,泛站,批量,垃圾站,还是x3的营销插件,如何选择?
    忍者X3,今天又更新了很多内容
    准备做淘宝客相关的生成工具。
    怎么样可以方便的获取到网站的ico图标呢?
    ECShop网店系统273发布模块 如何使用?
    站群还是非常火爆的,你看这力度。
    如果对文章的质量进一步把关,发布之前的批量过一遍
    有人做过FLASH验证码识别吗?
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12376407.html
Copyright © 2020-2023  润新知