浏览器:同源策略

285 阅读5分钟

同源策略是一种浏览器安全相关的策略,首先,同源指的是域名,协议,端口都相同,主要在URL里面能体现出来,比如:http://www.baidu.comhttps://www.baidu.com就是不同源的,因为他们采用的http协议不同。不同源的客户端脚本在没有明确授权的情况下不能读写对方资源。下面看看所有可能不同源的情况

image.png

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

  • Cookie、LocalStorage和IndexDB无法读取
  • DOM无法获取
  • AJAX请求无效(可以请求,但浏览器拒收响应)

跨域访问

跨域访问就是突破同源策略的限制,一般有以下四种方式可以实现跨域访问

JSONP

JSONP是JSON with padding的缩写,它的原理其实不是什么特别开发的技术,个人感觉更像是利用了浏览器的BUG,它主要是利用了script标签请求资源不受同源策略的限制,来获取服务器的数据,来看看一个简易的流程

首先在script的scr属性中设置要访问资源的位置,不过为了能用回调函数去接收资源,这里src要加个callback属性,告诉服务器通过这个回调函数获取数据,比如这样:<script src = "http://127.0.0.1/getNews?callback=showNews">,这样写就是要去请求127.0.0.1/getNews并携带了call参数;然后服务器要有对应的处理get请求的接口,通过将js代码写入返回的body中,来进行数据传输,具体是这样的:

	data = {name:"John"}//要返回的数据对象
	dataString = JSON.stringify(data);//改成字符串
	ctx.body = ctx.query.callback + "(" + dataString + ")"
	//设置返回body的内容 在这里其实就变成了showNews(data)

然后当script标签去请求服务器的时候,服务器返回一个这样的字符串给script标签:showNews({"name":"John"}),那既然是返回给script的,所以script就会把这个字符串当成js代码去加载到本地的网页中对吧,所以如果你本地的代码里,有一个这样的函数showNews,那么script请求到资源后,这个函数就相当于一个回调函数自动的被调用了,并且能接收传入的参数。

所以最终我们可以总结一下JSONP的技术要点,首先是利用了script标签的不受同源策略的限制去请求后台,接着后台通过设置一个js调用的代码并加入需要的数据转成字符串后返回给前端,然后前端通过对应的回调函数去接收这个返回的数据,至此完成跨域访问。 但实际上我们一般是动态的去请求数据,而不是一开始就把所有的数据请求完,所以我们也需要动态的创建script标签,最终可以封装成一个JSONP函数,如下

//前端代码
function jsonp(url,callback){//封装好的JSONP
	var scriptNode=document.createElement("script")
    scriptNode.setAttribute("type","text/javascript")
    scriptNode.setAttribute("src",`${url}?callback=${callback}`)
    document.head.appendChild(scriptNode)
}

function getRes(res){//回调函数
	console.log(res)
}

//后台代码
function handleResquest(){
	//...
	var data=[{"a":1,"b":2}]
	var cb=req.query.callback;
	if(cb){
	    res.send(`${cb}(JSON.stringify(${data}))`)
	}else{
	    res.send(`${data}`)
	}
}

//前端当想要请求数据时,调用jsonp即可
jsonp("http://127.0.0.1:7001/getdata","getRes")


CORS

Crossing-Origin Resource Shargin 跨域资源共享,它允许浏览器向跨源服务器发送XMLHttpRequest请求,从而克服了AJAX只能同源使用的规则。

目前所有的浏览器都支持CORS,服务器则需要实现CORS接口,一旦两边都支持了CORS,就能进行跨源通信。浏览器目前将CORS请求分为两类:简单请求和非简单请求,这里主要讲一下简单请求。

只要同时满足以下两个条件的请求就是简单请求:

  • 为get、post、head请求方法之一
  • HTTP头不超过以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type

凡是不满足以上条件的就是非简单请求

简单请求

这里简单的讲一下简单请求的流程,首先浏览器发现这次的AJAX跨源请求是简单请求,就在头部自动加上一个Origin字段,这个字段用来说明这次请求是来自来自哪个源(协议+域名+端口),就是附上一个url。然后传到服务器那,服务器就要根据Origin字段决定是否同意这个请求

如果Origin字段值不在许可范围内,服务器就会返回一个正常的HTTP回应,然后浏览器如果发现这个回应没有Access-Control-Allow-Origin字段,就会在XMLHttpRequest回调函数里面捕获错误。

如果Origin指定的域名在许可范围内,服务器返回的响应会多出几个头信息字段

表示接收这个域名的请求,如果这里值为*号,这代表接收任意域名的请求

  • Access-Control-Allow-Credentials: true

可选,布尔值,表示是否允许发送cookie,不过默认情况下,cookie一般都不会包含在CORS请求中,所以这个值设为true即可

  • Access-Control-Expose-Headers: FooBar

可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

上面讲到,CORS请求一般不会携带cookie,那如果我们要把cookie发送到服务器,首先要服务器指定Access-Control-Allow-Credentials:true,其次,开发者需要把AJAX请求的withCredentials属性打开,如下:

	let xhr = new XMLHttpRequest()
	xhr.withCredentials = true

这样就能在cors请求发送cookie了,服务器也能设置cookie返回给浏览器,这里要注意的是,Access-Control-Allow-Origin属性不能设置为*,必须指定与网页一致的域名,同时Cookie依旧遵循同源策略,只有用服务器域名设置的Cookie才会上传,其他域名设置的不会上传,且跨源原网页代码的document.cookie也无法读取到服务器域名下的cookie