zoukankan      html  css  js  c++  java
  • js跨域及解决方案

     原文链接:https://github.com/FIGHTING-TOP/FE-knowlodge-base

    什么是跨域请求

    当前发起请求的域与该请求指向的资源(该请求所在的页面)所在的域不一样。

    此时该请求就会受到浏览器SOP(同源策略)的限制,这样做可以降低被CSRF(跨站请求伪造)攻击的可能,但是同源策略并不能避免CSRF。对CSRF感兴趣的可以戳这里

    这里的域指的是这样的一个概念:我们认为若协议 + 域名 + 端口号均相同,那么就是同域。

    如何解决ajax跨域

    • JSONP
    • CORS
    • 代理请求
    • 图片Ping
    • websocket

    (一)JSONP方式解决跨域问题

    jsonp解决跨域问题是一个比较古老的方案(实际中不推荐使用),这里做简单介绍(实际项目中如果要使用JSONP,一般会使用JQ等对JSONP进行了封装的类库来进行ajax请求)

    实现原理

    JSONP之所以能够用来解决跨域方案,主要是因为 <script> 脚本拥有跨域能力,而JSONP正是利用这一点来实现。

    当我们用一个script的src来请求一个接口时,这个接口返回了一个函数名和一个或一些参数,由于是script引入的,所以浏览器就直接给我们执行了,注意在这之前我们必须先注册好接口返回的方法。

    实现流程

    JSONP的实现步骤大致如下(参考了来源中的文章)

    • 客户端网页网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制

      function addScriptTag(src) {
        var script = document.createElement('script');
        script.setAttribute("type","text/javascript");
        script.src = src;
        document.body.appendChild(script);
      }
      
      window.onload = function () {
        addScriptTag('http://example.com/ip?callback=foo');
      }
      
      function foo(data) {
        console.log('response data: ' + JSON.stringify(data));
      };     
      

      请求时,接口地址是作为构建出的脚本标签的src的,这样,当脚本标签构建出来时,最终的src是接口返回的内容

    • 服务端对应的接口在返回参数外面添加函数包裹层
    foo({
      "test": "testData"
    });  
    

      

    • 由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。

    注意,一般的JSONP接口和普通接口返回数据是有区别的,所以接口如果要做JSONP兼容,需要进行判断是否有对应callback关键字参数,如果有则是JSONP请求,返回JSONP数据,否则返回普通数据

    使用注意

    基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求,所以遇到那种情况,就得参考下面的CORS解决跨域了

    (二)CORS解决跨域问题

    CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应。

    同源安全策略 默认阻止“跨域”获取资源。但是 CORS 给了web服务器这样的权限,即服务器可以选择,允许跨域请求访问到它们的资源。

    他允许我们设置一些请求头信息来解决跨域:

    Access-Control-Allow-Origin // 指示请求的资源能共享给哪些域。
    
    Access-Control-Allow-Credentials // 指示当请求的凭证标记为 true 时,是否响应该请求。
    
    Access-Control-Allow-Headers // 用在对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头。
    
    Access-Control-Allow-Methods // 指定对预请求的响应中,哪些 HTTP 方法允许访问请求的资源。
    
    Access-Control-Expose-Headers // 指示哪些 HTTP 头的名称能在响应中列出。
    
    Access-Control-Max-Age // 指示预请求的结果能被缓存多久。
    
    Access-Control-Request-Headers // 用于发起一个预请求,告知服务器正式请求会使用那些 HTTP 头。
    
    Access-Control-Request-Method // 用于发起一个预请求,告知服务器正式请求会使用哪一种 HTTP 请求方法。

    Origin // 指示获取资源的请求是从什么域发起的。

      

    另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求

    在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。

    CORS请求失败会产生错误,但是为了安全,在JavaScript代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。

    然而并非所有的请求都会有预检请求,详细资料可以阅读MDN文档介绍的 跨源资源共享(CORS)

    (三)代理请求方式解决接口跨域问题

    注意,由于接口代理是有代价的,所以这个一般仅是开发过程中使用。

    与前面的方法不同,前面CORS是后端解决,而这个主要是前端对接口进行代理,也就是:

    • 前端ajax请求的是本地接口
    • 本地接口接收到请求后向实际的接口请求数据,然后再将信息返回给前端
    • 一般用node.js即可代理

    关于如何实现代理,方法有很多,那么我们开发一般会用webpack-dev-server

    假如我们服务在 localhost:3000 上,你可以这样启用代理:

    webpack.config.js

    module.exports = {
      //...
      devServer: {
        proxy: {
          '/api': {
            target: 'http://localhost:3000',
            pathRewrite: {'^/' : ''}
          }
        }
      }
    };
    

      

    请求到 /api/users 现在会被代理到请求 http://localhost:3000/api/users

    如果你不想始终传递 /api ,则需要重写路径:

    webpack.config.js

    module.exports = {
      //...
      devServer: {
        proxy: {
          '/api': {
            target: 'http://localhost:3000',
            pathRewrite: {'^/api' : ''}
          }
        }
      }
    };
    

      

    这种代理的方式呢,其实是用http-proxy-middleware来实现的, 有兴趣的可以看下源码了解下,有机会可以专门聊下这个

    (四)图片Ping

    我们知道,一个网页可以从任何网页加载图像,不用担心跨域不跨域,所以,我们就可以利用图片不受“同源限制”这一点进行跨域通信。 

    我们利用JS创建一个新的Image对象,并把src属性设置为指向请求的地址,通过监听onload和onerror事件来确定是否接受到了响应。响应的数据可以是任意内容,但通常是像素图或304响应。

    需要注意的是,新图像元素只要设置了src属性就会开始下载,所以我们这里的事件一定要在指定src属性之前绑定,这也是为什么我们这里不需要把img标签插入DOM 的原因。

    <body>
        <button id="Ping">图像Ping发送请求</button>
        <script>
            var btn=document.getElementById('Ping');
            btn.onclick=function () {
                var img=new Image();
                img.onload=img.onerror=function () {
                    alert("Done");
                };
                img.src="http://localhost:8081/img?name=Joy";
            }
        </script>
    </body>
    //服务器代码
    app.get('/img',function (req,res) {
       res.send("我是一张图片");
    });
    

      

    这种方式优点是很明显的:兼容性非常好,缺点就是:只能发生GET请求,而且无法获取响应文本。

    (五)WebSocket

     WebSockets 是一种先进的技术。它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此API,您可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。

    它默认是可以跨域的。

    // Create WebSocket connection.
    const socket = new WebSocket('ws://localhost:8080');
    
    // Connection opened
    socket.addEventListener('open', function (event) {
        socket.send('Hello Server!');
    });
    
    // Listen for messages
    socket.addEventListener('message', function (event) {
        console.log('Message from server ', event.data);
    });
    

      

    对于前端页面之间的跨域

    • 如果是iframe可以使用 location.hash 或 window.name 进行信息交流
    • 使用 postMessage
    • document.domain(仅限于一级域名和二级域名之间)

    Reference:

     

  • 相关阅读:
    [SpriteKit] 制作瓦片地图小游戏
    TouchID 指纹解锁
    iOS-启动动态页跳过设计思路
    iOS-高仿通讯录之商品索引排序搜索
    Python: PS 滤镜--马赛克
    Tensorflow学习笔记——占位符和feed_dict(二)
    Tensorflow学习笔记——张量、图、常量、变量(一)
    IDEA 单元测试
    sublime —— 强大的插件
    window环境下在anconda中安装opencv
  • 原文地址:https://www.cnblogs.com/mingweiyard/p/8320145.html
Copyright © 2011-2022 走看看