继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

跨域总结--JSONP,CORS,postMessage,子域名代理

慕UI0932993
关注TA
已关注
手记 7
粉丝 25
获赞 186

什么是跨域

跨域产生的原因是浏览器同源策略-SOP的限制,当前域名下的js只能读取同域的窗口属性。当使用js获取不同域名下的数据时,就会产生跨域的问题。只有双方在协议、主机和端口号都完全匹配的情况下,可以被授权访问。

需要注意的是,在服务器端是没有跨域之说的。比如我用node请求java数据时,即使两个的地址不一样,但是这个不是跨域,跨域是浏览器做的限制,防止获其他域名的cookies或者恶意的发送ajax请求等,别弄乱了,因为现在很多项目都为了前后端分离,用node做了中间层,但是对于网页来说,他是请求同域的nodejs,nodejs再转发请求,这里并没有发生跨域。至于java怎么判断这个请求是自己人,就是另一个话题了。咨询了同事,采用的一般是IP白名单或者api签名。

常使用的跨域方法有:

  1. JSONP
  2. CORS
  3. postMessage
  4. 子域名代理

JSONP

HTML脚本元素可以规避SOP的检查。即我们请求HTML、CSS、JS文件时浏览器不会做限制。所以运用这个特点,通过加载外部js文件的方式来向其他资源发出http请求。也正是是请求文件,所以JSONP只能GET而不能POST。简单说,jsonp就仅仅相当与在HTML标签了动态创建了一个<script src="URL/data.js?callback=jsonpCallback">标签。然后服务端那边根据我们传过去的参数,处理好后返回数据,所以对于不同请求我们请求回来的js文件里的内容是不同的,都是服务端动态生成的。
例:

jsonpCallback = function(json) {
    alert(json.title)
}
var script = document.createElement('script');
script.async = true;
script.src = 'http://thirdpartyjs.com/info.js?callback=jsonpCallback?id=123'
document.body.appendChild(script);

//服务器端收到请求,将返回以下响应,然后执行这段代码
jsonpCallback({
    'title': 'thirdpartyjs'
})
局限性和安全性
  • JSONP仅适用HTTP的GET请求。
  • 此外还缺乏查无处理机制。如果没有调用成功,也不会有任何提示。意味JSONP请求遇到404、500等错误时,无法检测到具体出错的原因,所能做的也只是在超过给定时间没收到响应就认为请求失败,并执行自己的错误回调。
  • 因远程脚本可以注入任何内容,严重的安全漏洞。借助JSONP有可能进行跨站请求伪造攻击(CSRF)--恶意网站使用访问者的浏览器向服务器端发送请求并进行数据变更时被称为CSRF攻击-->由于请求会携带用户的cookie信息。攻击者唯一需要做的就是把响应中有效的回掉函数更改为其他的JS代码。

跨域资源共享(CORS)

现在另一个兴起的跨域方法还有一个CORS。

  • 通过一系列特殊的HTTP头来解决,浏览器和服务器是如何通过可控的方式进行跨域通信的。
  • 通过HTTP头信息可以允许双方判断请求应该成功还是失败。成功返回200,失败返回403

CORS请求分为简单请求和复杂请求两种情况。
简单请求只有这几种情况,其他都是复杂情况

请求的请发只有一下三种方法之一  
1. HEAD2. GET3. POST

HTTP的头信息不超出一下几个字段
 1. Accept   2. Accept-Language   3. Content-Language    4. Last-Event-ID    5. Content-Type
发送简单的请求

简单请求的情况下,浏览器不会先发一个option请求验证,而是直接发请求至服务器。如果服务器没有做校验直接返回响应的话,其实是响应已经发回到浏览器了(看抓包结果)。但是浏览器做了拦截,如果没有对得上Access-Control-Allow-Origin,浏览器就会报出这样的错误。
这里写图片描述

  • 当发起跨域HTTP请求时,支持CORS的浏览器会通过引用额外的Origin头信息来指定请求的源这个头信息的值与同源策略使用的3个部分的值相同——协议、域名、端口 Origin:http://www.example.com
  • 服务器端的工作是检查头信息一确定是否接受这个请求,如果一个请求被接受,他必须发挥一个包含Access-Control-Allow-Origin,其值与发送的源相同的响应头 Access-Control-Allow-Origin:http://www.example.com
  • 如果资源是公共的且允许任何资源都发送请求时,服务器也可以返回一个通配符 `Access-Control-Allow-Origin:`
  • 如果一个请求被接受,它必须发挥一个包含Access-Control-Allow-Origin
  • 如果请求头不包含Origin,服务器不会发回任何CORS头信息
  • 当浏览器接受到服务器的HTTP响应时,它会检查Access-Control-Allow-Origin的值,其值必须与请求头中的Origin或(*)完全匹配。如果头信息缺失或值不匹配,浏览器会禁止该请求
    使用CORS传输Cookie
  • 在默认情况下,使用CORS发送请求时,浏览器不会发送任何识别信息,如Cookie或HTTP认证头。为了指定某个请求需要发送身份信息,必须将XMLHttpRequest对象的withCredentials:true
  • 如果服务器端需要并支持识别对象,需要在相应头中返回Access-Control-Allow-Credentials:true。如果withCredentials设置为true但服务器端没有返回该头,浏览器将会拒绝该请求。
    发送预检请求

    使用CORS时,如果请求方法不是GET、POST、HEAD,或者使用了自动义的HTTP头,浏览器会发起所谓的预检请求。预检请求是一个服务器端认证机制,在执行实际请求之前判断该请求是否合法。当向服务器发送稍微复杂的请求时,浏览器会把原始请求的方法和请求头作为预检请求的信息发送给服务器。然后服务器需要决定该请求是否应该被允许并响应必要的信息。预检请求使用一种OPTIONS的特殊HTTP方法传输

  • 通知服务器即将发出的请求并请求许可时,客户端会发送以下头信息
    • Origin ——请求源
    • Access-Control-Request-Method ——请求所用的HTTP方法
    • Access-Control-Request-Headers ——以逗号隔开的请求自定义头
  • 服务器端会返回这些响应头
    • Access-Control-Allow-Origin ——允许的请求源
    • Access-Control-Allow-Methods ——以逗号隔开的允许的方法列表
    • Access-Control-Allow-Headers ——以逗号隔开的允许的头信息
    • Access-Control-Max-Age ——预检请求的缓存时间(以秒为单位)
    • Access-Control-Allow-Credentials ——指名所请求的资源是否支持认证信息
      当客户端收到服务端的响应后,会使用前面声明的HTTP方法和请求头发送真正的请求。此外,预检请求会被浏览器缓存(取决于Access-Control-Max-Age),被缓存后,后续相同类型的请求将不会再重复发起额外的预检请求。

这段都是从书上抄的,还可以看阮大大的文章了解CORS。跨域资源共享 CORS 详解

postMessage

postMessage是为了解决页面间的通信。

  1. 页面和其打开的新窗口的数据传递
  2. 多窗口之间消息传递
  3. 页面与嵌套的iframe消息传递
  4. 上面三个问题的跨域数据传递
页面和其打开的新窗口的数据传递
//同域 - a页面
<a href="./b.html" target="_blank">跳转</a>
<script>
    window.addEventListener('message', function(event) {
        if(event.origin == 'http://hwc.home.com:3000') {   //同域与不同域,都是靠origin判断
            console.log(event.data)
        }
    })
</script>

//b页面
<script>
    document.getElementById('app').onclick = function() {
        var target = 'http://hwc.home.com:3000'
        opener.postMessage('b.html send message', target)
    }
</script>
页面与嵌套的iframe消息传递
//a页面
<iframe src="http://hwc.home.com:4000/home.html"></iframe>
var iframe = document.getElementsByTagName('iframe')[0];
window.onMessage = function(event) {
    if(event.source == iframe.contentWindow) {}   //嵌套iframe除了origin外还可以比较用source
}

//iframe页面
window.parent.postMessage(data, target)
window.addEventListener('message',function(e){
    if(e.source!=window.parent) return;     //子页面可以通过此方法判断是否是父页面的消息
    window.parent.postMessage('data','*');
},false);

多窗口之间消息传递不是指我用浏览器分别打开了两个页面,就能互相通信了。需要一个otherWindow,一个其他窗口的引用 window.postMessage

语法 ——> otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow ——> 其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。
message ——> 将要发送到其他 window的数据
targetOrigin——> 通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。

我们用window.postMessage是可以获取postMessage函数体的,但是不可用

子域名代理

SOP限制除了限制不同用AJAX请求不同域的文档外,他还限制不同域的框架间是不能进行js交互操作的。比如

<script>
    var iframe = document.getElementId('iframe');   
    var win = iframe.contentWindow;   //这里能获得iframe里的window对象,但是window对象里的属性和方法基本不可用
    var doc = win.document;  //获取不到
    var name = win.name      //获取不到
</script>

如果iframe的域名是子域名的话,可以用document.domain来修改iframe的域名。要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。浏览器允许网站将主机部分更改为原始值的后缀
如果是第三方,则要求发布者配置Web服务器,将某一域名的请求都代理到你的服务器上
例:sub.example.com -> example.com
如果通信双方设置相同的域名后缀,他们就具有相同的值源,浏览器就不会再阻止他们进行通信

使用document.domain更改文档的源
<script>
    document.domain = 'example.com'   //原 sub.example.com
</script>

document.domain只有第一次设置有效,所以最好在页面的<head>标签中设置
SOP将不会再阻止托管在example.com的文档与sub.example.com的文档进行通信,但在example.com的文件也要document.domain = 'example.com',因为浏览器要求双方明确设置为相同域名后缀

其实子域名代理还有很多东西的,但因为现在用得比较少了,所以也没继续深究了

打开App,阅读手记
7人推荐
发表评论
随时随地看视频慕课网APP