资讯专栏INFORMATION COLUMN

那些年曾谈起的跨域

galois / 2602人阅读

摘要:在中,在不同的域名下面进行数据交互,就会遇到跨域问题,说到跨域首先要从同源说起,浏览器为了提供一种安全的运行环境,各个浏览器厂商协定使用同源策略。在上面说过是不受同源策略限制的,但是出于安全原因,浏览器限制从脚本内发起的跨源请求。

对于前端开发来说跨域应该是最不陌生的问题了,无论是开发过程中还是在面试过程中都是一个经常遇到的一个问题,在开发过程中遇到这个问题的话一般都是找后端同学去解决,以至于很多人都忽略了对跨域的认识。为什么会导致跨域?遇到跨域又怎么去解决呢?本文会对这些问题一一的介绍。

JavaScript中,在不同的域名下面进行数据交互,就会遇到跨域问题,说到跨域首先要从同源说起,浏览器为了提供一种安全的运行环境,各个浏览器厂商协定使用同源策略。

什么同源策略

同源策略:同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSSCSRF等攻击。所谓同源是指协议+域名+端口三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

Url组成部分

了解同源策略以后,同样需要对url的组成部分也顺带了解一下吧,只有了解url之后当出现跨域的时候才知道哪里出了问题,这样才能和后端、运维开怼,怼天怼地对空气。O(∩_∩)O哈哈~其实不是啦,当然是为了能够和其他同事能做到良好的沟通,说的的有理有据,以理服人嘛,这才是王道。

从上图中能够清晰的看出url中每个部分的含义:

protocol:协议常用的协议是http

auth:验证,因为明文传输用户名和密码,非HTTPS环境下很不安全,一般用的非常少。

hostname:主机地址,可以是域名,也可以是IP地址

port:端口http协议默认端口是:80端口,如果不写默认就是:80端口

pathname:路径网络资源在服务器中的指定路径

serarch:查询字符串如果需要从服务器那里查询内容,在这里编辑

hash:哈希网页中可能会分为不同的片段,如果想访问网页后直接到达指定位置,可以在这部分设置

项目过程过程中经常会用到一些缓存,浏览器为了网页的安全在缓存的时候,由于同源策略的问题对其缓存内容进行了限制,其实想想也是对的,如果不使用同源策略的话,很容易冲掉缓存的东西。

CookieLocalStorageIndexDB等无法读取。

DOM无法获得。

AJAX请求不能发送。

当然浏览器也没有把所有的东西都限制了,比如图片、互联网资源等还是允许跨域请求的。允许跨域请求都是使用标签,只有三个标签是允许跨域加载资源:

http://localhost:7000/b.html










http://localhost:6000/c.html










a.html中有一个隐藏的iframe,该iframe指向异域http://localhost:7000/b.htmlb.html,且传递hash值给b.html`b.html获取hash值,生成data值,然后动态创建iframe,该iframedata值传给与a.html同域的c.html 因为c.htmla.html`同域,可以传值固然也就解决了跨域问题。

window.name

window.name这个属性不是一个简单的全局属性只要在一个window下,无论url怎么变化,只要设置好了window.name,那么后续就一直都不会改变,同理,在iframe中,即使url在变化,iframe中的window.name也是一个固定的值,利用这个,我们就可以实现跨域了。

http://localhost:6000/a.html


http://localhost:7000/b.html

var person = {
  name: "Aaron",
  age: 18
}
window.name = JSON.stringify(person)

http://localhost:6000/proxy.html





proxy


这是proxy页面

http://localhost:6000下有一个a.html,在http://localhost:7000下有一个b.html,在http://localhost:6000/a.html中创建了一个iframe标签并把地址指向了http://localhost:7000/b.html,在b.html中的window.name赋值保存了一段数据,但是现在还获取不了,因为是跨域的,所以,我们可以把src设置为当前域的http://localhost:6000/proxy.html,虽然域名改变了但是window.name是没有改变的。这样就可以拿到我们想要的数据了。

postMessage(HTML5)

可能很多不知道postMessage整个API,在HTML5中新增了postMessage方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递,postMessage在很多浏览器中都已经得到了良好的支持,所以可放心的使用。该方法可以通过绑定windowmessage事件来监听发送跨文档消息传输内容。

postMessage()方法接受两个参数

data:要传递的数据,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。

origin:字符串参数,指明目标窗口的源,协议+主机+端口号+URLURL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

http://localhost:6000/a.html








http://localhost:7000/b.html








Hello World!

这样我们就可以接收任何窗口传递来的消息了,为了安全起见,我们利用这时候的MessageEvent对象判断了一下消息源,MessageEvent对象,这个对象中包含很多东西。

data:顾名思义,是传递来的message

source:发送消息的窗口对象

origin:发送消息窗口的源(协议+主机+端口号)

使用postMessage方法比以上方法用起来要轻便,不必有太多的繁琐操作,可以说postMessage是对于解决跨域来说是一个比较好的解决方案,不会显得代码特别的臃肿,并且各个浏览器又有良好的支持。

跨域资源共享(CORS)

CORS:全称"跨域资源共享"(Cross-origin resource sharing)。CORS需要浏览器和服务器同时支持,才可以实现跨域请求,目前几乎所有浏览器都支持CORSIE则不能低于IE10CORS的整个过程都由浏览器自动完成,前端无需做任何设置,跟平时发送ajax请求并无差异。CORS的关键在于服务器,只要服务器实现CORS接口,就可以实现跨域通信。

跨域资源共享(CORS) 是一种机制,它使用额外的HTTP头来告诉浏览器让运行在一个origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域HTTP请求。在上面说过src是不受同源策略限制的,但是出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。例如,XMLHttpRequestFetchAPI遵循同源策略。这意味着使用这些APIWeb应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。

所有CORS相关的的头都是Access-Control为前缀的。下面是每个头的一些细节。

字段 描述
Access-Control-Allow-Methods 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求
Access-Control-Allow-Headers 如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段
Access-Control-Allow-Credentials 该字段与简单请求时的含义相同。
Access-Control-Max-Age 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。
import express from "express";
import cors from "cors";

const app = express();
const corsOptions = {
  origin: "http://example.com",
  optionsSuccessStatus: 200
}
 
app.get("/products/:id", cors(corsOptions), (req, res, next) => {
  res.json({msg: "This is CORS-enabled for only example.com."})
})
 
app.listen(80, function () {
  console.log("启用corba,端口:80")
})

使用CORS简单请求,非常容易,对于前端来说无需做任何配置,与发送普通ajax请求无异。唯一需要注意的是,需要携带cookie信息时,需要将withCredentials设置为true即可。CORS的配置,完全在后端设置,配置起来也比较容易,目前对于大部分浏览器兼容性也比较好,现在应用最多的就是CORS解决跨域了。

在开发过程中由于各个公司的差异选用的接口风格也是不同的,很多公司会采用RESTful风格去编写接口,难免就会有些骚操作,在跨域请求时自定义的请求头是不允许这样操作的,因为浏览器根据Response Headers判断请求是否允许。

跨域时默认允许的方法

GET

HEAD

POST

因为浏览器希望在网页进行跨域请求操作的时候是保证服务端的安全的,不允许任何随便进行跨域,不允许随便的方法进行跨域,以防数据被恶意篡改。所以提供这些限制之后,就可以进行一些非常有利的判断。针对如上问题需要服务端进行特殊处理才行

const http = request("http");
http.createServer(function(request,response){
    response.writeHead(200,{
        // 设置允许跨域的访问地址
        "Access-Control-Allow-Origin":"*",
        // 设置允许访问的自定义请求头
        "Access-Control-Allow-Headers":"X-Test-Cors",
        // 设置允许跨域的methods
        "Access-Control-Allow-Methods":"POST,Put,Delete",
        // 允许以上三个方式进行跨域的最长时间,1000秒内不需要发送预请求验证了
        "Access-Control-Max-Age":"1000"
    })
    response.end("123")
}).listen(3000)

这样设置,这个请求就成功了,但是可以观测到多了一个请求,这个多出来的请求就是预请求,告诉浏览器,这个自定义请求是允许的,然后再正式的发送这个请求,通过验证之后就请求成功了,浏览器为什么要做这些限制,是因为保证安全,不允许随便一个人都可以进行跨域。

WebSocket协议跨域

WebSocketHTML5新推出的一个API,通过WebSocket可以实现客户端与服务端的即时通信,如聊天室,服务数据同步渲染等等。WebSocket是点对点通信,服务端与客户端可以通过某种标识完成的。WebSocket是不受同源策略限制的所以可以利用这个特性直接与服务端进行点对点通信。

以下示例没有使用HTML5WebSocket而是使用的socket.io完成类似的功能操作。

若若的说一句:其实我一直以为WebSocketAjax一样是受同源策略限制的,经过学习才发现不是的。真是学到老活到老(关谷口音)。O(∩_∩)O

服务端:

var io = require("socket.io")(1234);
io.sockets.on("connection", (client) => {
    client.on("message", function (msg) { //监听到信息处理
        client.send("服务器收到了信息:" + msg);
    });
    client.on("disconnect", function () { //断开处理
        console.log("client has disconnected");
    });
});
console.log("listen 1234...");

客户端:

$(function () {
    var iosocket = io.connect("http://localhost:1234/");
    var $ul = $("ul");
    var $input = $("input");
    iosocket.on("connect", function () {  //接通处理
        $ul.append($("
  • 连上啦
  • ")); iosocket.on("message", function (message) { //收到信息处理 $ul.append($("
  • ").text(message)); }); iosocket.on("disconnect", function () { //断开处理 $ul.append("
  • Disconnected
  • "); }); }); $input.keypress(function (event) { if (event.which == 13) { //回车 event.preventDefault(); console.log("send : " + $input.val()); iosocket.send($input.val()); $input.val(""); } }); });

    Websocket既然能支持跨域方法,那就是说,一个开放给公网的Websocket服务任何人都能访问,这样的话会使数据变得很不安全,所以可以通过对连接域名进行认证即可。

    服务器反代

    学习路程首先了解了一下什么是反代,在计算机网络中,反向代理是代理服务器的一种。服务器根据客户端的请求,从其关联的一组或多组后端服务器(如Web服务器)上获取资源,然后再将这些资源返回给客户端,客户端只会得知反向代理的IP地址,而不知道在代理服务器后面的服务器簇的存在。 -- 节选自百度百科

    反向代理服务器:就nginxhttp请求转发到另一个或者一些服务器上。从而轻松实现跨域访问。比如服务器中分别部署了N个服务器,当客户端发起请求时不用直接请求服务器中N个节点上的服务,只需要访问我们的代理服务器就行了,代理服务器根据请求内容分发到不同服务器节点。这仅是一种使用场景,当然还可以做负载均衡等。

    反向代理理解起来不是特别的难,平时生活中最常见的例子,当我们拨打人工客服的时候,并不是直接拨打客服的某一个电话号码,而是拨打总机号码,当我们拨打然后由总机进行处理,然后再分发给不同的客服人员。r然而当服务人员需要让你挂断电话等待回拨的时候,也不是直接拨打到你的电话,同样是也通过总机之后再转发到你的电话。其实这个总机也就相当于反代服务器。虽然这个例子不太贴切但是多多少少就是这个意思。

    由于不太懂Nginx不知道该如何处理这个部分,只是对反向代理做了一个简单的了解,等以后学习了Nginx会补上相关代码。

    Nodejs代理跨域

    使用Nodejs进行跨域在我看来,就是使用Node服务做了一个中间代理转发,其原理和反向代理差不多,当访问某一个URL时需要通过服务器分发到另一个服务器URL地址中。这里就不过多的赘述了,直接看代码吧。

    示例代码入下:

    main.js

    import http from "http";
    import httpProxy from "http-proxy";
      
    // 新建一个代理 Proxy Server 对象  
    const proxy = httpProxy.createProxyServer({});  
      
    // 捕获异常  
    proxy.on("error", function (err, req, res) {  
      res.writeHead(500, {  
        "Content-Type": "text/plain"  
      });  
      res.end("error");  
    });  
        
    // 在每次请求中,调用 proxy.web(req, res config) 方法进行请求分发  
    const server = http.createServer((req, res) => {  
      // 在这里可以自定义你的路由分发  
      let host = req.headers.host, ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;  
      switch(host){  
        case "www.a.com":   
            proxy.web(req, res, { target: "http://localhost:3000" });  
            break;  
        case "www.b.com":  
            proxy.web(req, res, { target: "http://localhost:4000" });  
            break;
        default:  
            res.writeHead(200, {  
                "Content-Type": "text/plain"  
            });  
            res.end("Hello Aaron!");  
      }  
    });  
    server.listen(8080);

    如代码所示,当访问www.a.com的时候,请求就被转发到了3000接口上,访问www.b.com时就被转发到了4000这个接口上。这样就简单的完成了一个反向代理的工作。

    在使用vue开发的时候难免也会遇到跨域问题,或许你根本就没有遇到,可能你们正好处于同一个域里面,还有一种可能就是,后端同学或者运维同学已经处理好有关跨域的相关操作。但是当在开发过程中遇到跨域的时候,什么前端应该有对应的解决办法。vue-cli是基于Node服务的,所以我们可以利用这个服务来做代理工作,暂时解决开发中的跨域问题。

    build/webpack.config.js

    module.exports = {
        devServer: {
            historyApiFallback: true,
            proxy: [{
                context: "/login",  //  url以/login为开头时启用代理
                target: "http://www.a.com:8080",  // 代理跨域目标接口
                changeOrigin: true,
                secure: false,  // 当代理某些https服务报错时用
                cookieDomainRewrite: "www.b.com"  // 可以为false,表示不修改
            }],
            noInfo: true
        }
    }

    在开发过程中遇到的可以通过这种方式解决,但是到达生产环境时到底使用什么方法还是需要斟酌的,毕竟要使服务数据变得更加的安全才是最好的。

    总结

    以上讲了很多有关跨域的解决方案,有利也有弊,对于我而言可能更加的倾向于后端粑粑或者运维粑粑去解决跨域问题,毕竟前端处理起来毕竟不是很安全,而且后端或者运维处理起来也不是那么的麻烦。

    很感谢大家利用这么长时间来读这篇文章,文章中若有错误请在下方留言,会尽快做出修改。

    文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/104870.html

    相关文章

    • 【Geek议题】当年那些风骚跨域操作

      摘要:同源策略年,同源政策由公司引入浏览器。标签不受同源策略限制,但只能发起请求。这一行为使得不同域的特定文档可以读取该属性值,因此可以绕过同源策略并使跨域消息通信成为可能。 前言 现在cross-origin resource sharing(跨域资源共享,下简称CORS)已经十分普及,算上IE8的不标准兼容(XDomainRequest),各大浏览器基本都已支持,当年为了前后端分离、if...

      mengera88 评论0 收藏0
    • 【Geek议题】当年那些风骚跨域操作

      摘要:同源策略年,同源政策由公司引入浏览器。标签不受同源策略限制,但只能发起请求。这一行为使得不同域的特定文档可以读取该属性值,因此可以绕过同源策略并使跨域消息通信成为可能。 前言 现在cross-origin resource sharing(跨域资源共享,下简称CORS)已经十分普及,算上IE8的不标准兼容(XDomainRequest),各大浏览器基本都已支持,当年为了前后端分离、if...

      Worktile 评论0 收藏0
    • 老生常谈跨域问题JSONP解决方式

      摘要:解决方案跨域问题可以说在前端方面不可避免,但同源策略毕竟在保护网络信息安全方面起到很大的作用。 起因 说起来源...今天去茶水间倒水时,偶然听到公司面试官在问面试者前端跨域的如何解决。我心中默默想了一想,啪啪啪瞬间想出几个关键词,iframe,cors,同源策略,jsonp...转念一想,虽然这是很常见的面试题,然而我在开发过程中,还真没有用过jsonp这种方式...就连原理也说不好。...

      asoren 评论0 收藏0
    • 老生常谈跨域问题JSONP解决方式

      摘要:解决方案跨域问题可以说在前端方面不可避免,但同源策略毕竟在保护网络信息安全方面起到很大的作用。 起因 说起来源...今天去茶水间倒水时,偶然听到公司面试官在问面试者前端跨域的如何解决。我心中默默想了一想,啪啪啪瞬间想出几个关键词,iframe,cors,同源策略,jsonp...转念一想,虽然这是很常见的面试题,然而我在开发过程中,还真没有用过jsonp这种方式...就连原理也说不好。...

      jemygraw 评论0 收藏0
    • 用 Nokitjs 解决前端开发中跨域问题

      摘要:用解决问题是一个,和等框架类似,用于开发应用或网站,这里不去比较各个框架的优劣,而是去解决跨域问题。 问题 在开发一些「单页应用」时,通常会使用 Ajax 和服务器通讯,比如 RESTful API,通常「前端」和「服务端 API」可能是有不同人员在负责,也不在同一个工程下,那么开发过程中就可能会遇到跨域的问题,比如 Chrome 会在 console 中看到这样的错误消息: XMLH...

      voidking 评论0 收藏0

    发表评论

    0条评论

    galois

    |高级讲师

    TA的文章

    阅读更多
    最新活动
    阅读需要支付1元查看
    <