• 前端跨域问题解决方案


    背景:

    同源策略:NetSpace公司引入,基于浏览器安全,防止浏览器收到XSS、CSFR等攻击。同源,即协议+域名+端口完全一致。
    同源策略:为保障用户信息安全,防止恶意网站窃取数据的一种安全策略。


    “同源”:协议相同、域名相同、端口号相同

    同源策略限制的行为:

    • Cookie、LocalStorage和IndexDB无法读取
    • DOM和JS对象无法获取
    • Ajax请求不能发送

    解决方案:

    方案一:JSONP

    原理:通过script标签引入的js不受同源策略的限制,而XmlHttpRequest对象受到同源策略的影响。可以加载跨域服务器上的脚本,用JSONP获取的不是JSON数据,而是可以直接运行的JS脚本。

    eg1:jquery
    
    function jsonpCallback(data) {
        console.log("jsonpCallback: " + data.name)
    }
    
    $.ajax({
        url:"http://www.nanhuaqiushui.com:8080/login",
        type:"get",
        dataType:"jsonp",
        data:{
            name: $("#name").val(),
            id: $("#id").val()
        },
        cache: false,
        timeout: 5000,
        jsonp: "callback",          //jsonp字段含义为服务器通过什么字段获取回调函数的名称
        jsonpCallback:"jsonpCallback",    //自定义回调函数名
        success:function(data){
            console.log("ajax success callback: " + data.name);
        },
        error: function(jqXHR, textStatus, errorThrown){
            console.log(textStatus + ' ' + errorThrown);
        }
    })
    
    eg2:vue
    this.$http.jsonp("http://www.nanhuaqiushui.com:8080/login",{
        params:{
            name: $("#name").val(),
            id: $("#id").val()
        },
        jsonp: "jsonpCallback"
    }).then((res) => {
        console.log(res);
    });
    
    服务器端处理:
    
    app.get("/login", function(req,res){
        console.log("server accept: " + req.query.name, req.query.id);
        var data = "{" + "name:" + req.query.name + "- server 3001 process" + "," + "id:" + req.query.id + "- server 3001 process" + "}";
        var callback = req.query.callback;
        var jsonp = callback + "(" + data + ")";
        console.log(jsonp);
        res.send(jsonp);
        res.end();
    });
    

    注意:data中字符串拼接,不能直接将JSON格式的data直接传给回调函数,否则会发生编译错误。
    本质:

    <script src = 'http://www.nanhuaqiushui.com:8080/login?callback=jsonpCallback&name=lidachui&id=3001&_=1473164876032'></script>
    
    

    不足:
    1.只能使用GET请求(即时POST也会被转换成GET请求)。
    2.JSONP本质上是通过script标签的src属性实现跨域请求的,而非ajax,因此不是通过XMLHttpRequest进行传输的,所以无法注册success、error等事件监听函数。

    方案二:CORS(跨资源共享)实现跨域调用

    原理:使用自定义的HTTP头部让浏览器与服务器沟通,从而决定请求或响应是否成功。
    Cross-Origin Resource Sharing(CORS)跨域资源共享是一份浏览器技术的规范,提供了Web服务从不同域传来沙盒脚本的方法,以避开浏览器的同源策略。(现代浏览器都支持CORS)
    优点:采用XMLHttpRequest对象传递,同时支持GET/POST等多种请求方式,方便调试。是JSONP模式的现代版。

    eg:
    app.post('/cors',function(req,res){
        res.header("Access-Control-Allow-Origin","*");  //设置请求来源不受限制
        res.header("Access-Control-Allow-Headers","X-Requested-With");
        res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
        res.header("Content-Type", "application/json;charset=utf-8");
        var data = {
            name: req.body.name + ' - server 3001 cors process',
            id: req.body.id + ' - server 3001 cors process'
        };
        res.send(data);
        res.end();
    })
    
    

    优点:只需服务端设置Access-Control-Allow-Origin即可,前端无需设置。
    withCredentials: true // 前端设置是否带cookie

    方案三:window.name + iframe

    原理:window对象的name属性很特别,name值在不同的页面(甚至不同域名)加载后依旧存在,而且可以支持非常长的name值(2MB)。
    Iframe元素可以在当前网页之中,嵌入其他网页。每个iframe元素形成自己的窗口,即有自己的window对象。iframe之中的脚本,可以获得父窗口和子窗口。但是在同源的情况下,父窗口和子窗口才能通信;如果跨域,就无法拿到对方的DOM。

    var proxy = function(url, callback){
        var state = 0;
        var iframe = document.createElement("iframe");
        //加载跨域页面
        iframe.src = url;
        iframe.onload = function(){
            if(state == 0){
                //第一次onload(跨域页)成功后,切换到同域代理页面
                iframe.contentWindow.location = "http://www.nanhuaqiushui.com";
                state = 1;
            }else if(state == 1){
                //第二次onload(同域proxy页)成功后,读取同域window.name中数据
                callback(iframe.contentWindow.name);
                destroyFrame();
            }
        }
    }
    
    documetn.body.appendChild(iframe);
    
    //获取数据后销毁这个iframe,释放内存
    
    
    function destroyFrame(){
        iframe.contentWindow.document.write("");
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
    
    //请求跨域b页面数据
    
    proxy("http://www.domain2.com/b.html",function(data){
        alert(data);
    });
    

    方案四:postMessage跨域(XDM)

    postMessage是H5 中新的api,可解决以下问题:
    页面和其打开的新窗口的数据传递;
    多窗口之间消息传递;
    页面与嵌套的iframe消息传递;
    上面三个场景的跨域数据传递。

    XDM

    跨文本消息传递(cross-document messaging),简称XDM,指来自不同域的页面间传递消息。
    postMessage(),参数1表示消息值,参数2表示接收方是来自哪个域的字符串。

        var iframeWindow = document.getElementById("rayframe").contentWindow,
        iframeWindow.postMessage("A secret","http://www.wrox.com");
    

    接收XDM消息时,会触发window对象的message事件。这个事件以异步形式触发,因此从发送消息到接收消息可能经过一段时间的延迟。触发message事件后,传递给onmessage事件对象包含一下三方面重要信息:

    1.data:作为postMessage()第一个参数传入的字符串数据
    2.origin:发送消息的文档所在的域
    3.source:发送消息的文档的window对象的代理。这个代理对象的主要用于在发送上一条消息的窗口中调用postMessage()方法。
    用法:postMessage(data,origin)

        父窗口:
        <iframe src="http://www.xiaokeai.com"></iframe>
        window.onmessage = function(e){
            if(e.origin == "http://www.wrox.com"){
                //处理接收到的数据
                processMessage(e.data);
                //可选,向来源窗口发送回执
                e.source.postMessage("Received","http://p2p.wrox.com")
            }
        }
    
        子窗口:
        if(window.parent !== window.self){
            window.parent.postMessage("xiaohuochai","http://fatherxiaokeai.com");
        }
    

    方法五:document.domain(两个iframe之间)

    背景:同源策略认为域和子域隶属于不同的域,因此会被浏览器拦截。
    方法:如果两个窗口一级域名相同,只是二级域名不同,可以通过设置document.domain来使其通信。
    通过设置document.domain只能获取DOM,而Cookie、LocalStorage和IndexedDB无法获取。
    问题:安全性,当一个站点被攻击后,另一个站点也会引起安全漏洞;
    如果一个页面中引入多个iframe,要想跨域访问,就要都设置为相同的domain值

    方法六:ngix代理跨域

    1.ngix配置解决iconfont跨域
    背景:浏览器跨域访问jscssimg等常规静态资源被同源策略许可,但是iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时需在ngix的静态资源服务器中加入配置:;
    location / {
    add_header Access-Control-Allow-Origin * ;
    }
    2.ngix反向代理接口跨域
    原理:同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨域问题。
    实现思路:通过ngix配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前与cookie写入,实现跨域登录。

    方案七:WebSocket协议跨域

    WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

    前端:
    <div>user input:<input type="text"></div>
    <script src="./socket.io.js"></script>
    
    node后台:
    
    var http = require('http');
    var socket = require('socket.io');
    
    // 启http服务
    var server = http.createServer(function(req, res) {
        res.writeHead(200, {
            'Content-type': 'text/html'
        });
        res.end();
    });
    
    server.listen('8080');
    console.log('Server is running at port 8080...');
    
    // 监听socket连接
    socket.listen(server).on('connection', function(client) {
        // 接收信息
        client.on('message', function(msg) {
            client.send('hello:' + msg);
            console.log('data from client: ---> ' + msg);
        });
    
        // 断开处理
        client.on('disconnect', function() {
            console.log('Client socket has closed.'); 
        });
    });
    

    方案八:锚点值

    又称为片段标识符,指的是URL的#后面的部分。如果只是改变片段标识符,页面不会重新刷新。
    父窗口可以把信息,写入子窗口的锚点值

    var src = originURL + "#" + data;
    document.getElementById("myIframe").src = src;
    

    子窗口通过监听hashchange事件得到通知

        window.onhashchange = checkMessage;
        function checkMessage(){
            var message = window.location.hash;
            ...
        }
    
  • 相关阅读:
    Weblogic学习笔记
    Shiro权限使用
    Shiro权限框架使用总结
    支付宝接口文档说明
    代码模拟实现十六进制转二进制
    代码模拟实现十六进制转换十进制
    四种内部类详细解释和代码示例
    Struts2_struts.xml写法和用法例子
    spring一些方法和用法例子
    Hibernate一些_方法_@注解_代码示例
  • 原文地址:https://www.cnblogs.com/nanhuaqiushui/p/10493478.html
Copyright © 2020-2023  润新知