资讯专栏INFORMATION COLUMN

同源策略与跨域

xavier / 1378人阅读

摘要:简单地理解就是因为同源策略的限制,它是浏览器为了安全性考虑一种非常重要的策略,域名下的无法操作或是域名下的对象。同源策略会限制以下三种行为和无法读取。例如中可以引用等资源,此类操作不受同源策略限制。

同源策略

同源策略same origin policy中的重要内容就是URL(uniform resource locator),统一资源定位符,俗称网址。URL中的resource资源就是css,js,html,img等内容。

origin

源包括当前页面的域名、协议、端口号。http协议默认端口是80,https协议默认端口是443。同源策略是浏览器的一个安全机制功能,Same Origin Policy,同源就是当协议、域名、端口号一致时就是同源。不同源的客户端脚本在没有明确授权下,不能读写对方的资源。简单地理解就是因为同源策略的限制,它是浏览器为了安全性考虑一种非常重要的策略,a.com 域名下的js无法操作b.com或是c.a.com域名下的对象。更详细的说明可以看下表:

同源策略的意义

浏览器基于用户的隐私安全目的,防止恶意网站窃取数据(只是浏览器有这个同源策略设置,但是用命令行curl请求某个跨域地址时能得到相应结果),不允许不同域名的网站之间互相调用ajax XHR对象,只是不允许XHR对象,对其他的图片、js脚本、css脚本还是可以通过标签跨域调用。所以css/js/img可以跨域请求(即引用),AJAX不能请求跨域的资源。
curl http://www.abc.com 用这个命令获得了http://www.abc.com指向的页面,同样,如果这里的URL指向的是一个文件或者一幅图都可以直接下载到本地。如果下载的是HTML文档,那么缺省的将不显示文件头部,即HTML文档的header。要全部显示,请加参数 -i,要只显示头部,用参数 -I。任何时候,可以使用 -v 命令看curl是怎样工作的,它向服务器发送的所有命令都会显示出来。为了断点续传,可以使用-r参数来指定传输范围。

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

【1】 Cookie、LocalStorage 和 IndexedDB 无法读取。Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。
【2】 DOM 无法获得。
【3】 AJAX 请求无效(可以发送,但浏览器会拒绝接受响应)。

主域名和子域名

主域名 abc.com
www.abc.com //www是子域名
bbs.abc.com //bbs是子域名
beijing.bbs.abc.com //beijing.bbs是子域名
haidian.beijing.bbs.abc.com //haidian.beijing.bbs是子域名
主域名是不带www的域名,例如a.com,主域名前面带前缀的通常都为二级域名或多级域名,例如www.a.com其实是二级域名。

跨域

解决跨域问题的两个前提特别注意:
第一,如果是协议和端口造成的跨域问题“前端”无法解决。
第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。比如在host文件中可将两个不同域名绑定到同一个IP地址上形成跨域。
“URL的首部”可在console.log控制台中用window.location.方法名获取

跨域访问的四种方式

跨域就是用某种方法突破同源策略的限制,实现获取其他域中的资源。实现跨域一般有四种方法:

1.JSONP实现跨域

JSONP(json with padding)方式, 通过script标签请求资源,允许用户在scr地址中传递一个callback参数(callback=abc)即将预先定义好的回调函数名以查询字符串的形式传递给服务端,服务端收到请求后会将要返回的数据用这个callback参数(abc)包裹住再返回,即将请求传入的参数abc作为函数名来包裹住要返回的JSON数据,比如abc(JSON),这样客户端在收到服务端返回的abc(JSON)文件后默认用JS解析执行。通过JSONP就可以随意定制自己的函数来自动处理返回数据了。

jsonp的原理:虽然浏览器默认禁止了跨域访问,但并不禁止在页面中script标签引用其他域下的JS文件,比如线上jquery库,并可以自由执行引入的JS文件中的function(包括操作cookie、Dom等等)。根据这一点,可以方便地通过动态创建script节点的方法来实现完全跨域的通信。例如a.com/index.html中可以引用b.com/main.js、b.com/style.css、b.com/logo.png等资源,此类操作不受同源策略限制。实际操作中如果在a.com下用ajax去请求(读写)b.com下的内容会被同源策略阻止,但a.com里如果引用了b.com/main.js,虽然可以引用,但当这个引用的js文件(在a.com下引用)去读写(ajax)b.com的资源时一样会提示ajax错误。注意读写和引用有本质区别的,受同源策略限制ajax不能(POST写/GET读)请求跨域内容,但可以通过script引用的方式获取目标域上js文件,如果在这个被引用的JS文件内存放数据,这样就能从目标域获取到数据了,这就是JSONP实现的原理。
JSONP与JSON没有关系。JSON是规定语法的一种字符串的写法。JSONP(json with padding),这里的padding就是被请求的目标域B域返回的数据其外层包裹的A域预先定义好的函数+括号。JSONP就是动态的script,即A域前台传什么callback=abc给B域名后台,B域就生成对应的abc方法,这个方法的执行过程是A域预先定义好的
jsonp的缺点:
1.安全问题,src引用是开放的,所以jsonp的资源都被所有人访问到。解决方法是用jsonp中的token参数,通过A域和B域共用同一套cookie来验证A的身份。
2.只能用GET方式不能用POST方式获取数据即只能读不能写,因为是基于scr引用的,引用是get请求。
3.可被注入恶意代码如?callback=alert(1); 这问题只能用正则过滤字符串的方法解决,过滤callback后的内容不能有括号之类的条件

JSONP的实现代码如下:
1.定义数据处理函数

appendHtml(){
    xxxxx
}

2.创建script标签,src的地址执行后端接口,最后加个参数callback=appendHtml.如:

var script=document.createElement("script")
script.src="http://127.0.0.1/getNews?callback=appendHtml"

3.服务端在收到请求后,解析参数,计算返还数据,输出 appendHtml(data) 字符串。
4.前台页面收到服务端返回的appendHtml(data)字符串所构建的script标签,页面加载这个script标签时做为js执行。此时会调用appendHtml()函数,将data做为参数。
注意:JSONP实现的前提是后端必须有JSONP的API接口,即后端有将前端传入的参数作为函数名包裹数据返回js文件的逻辑。如:

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

需后台配合,代码如下图

封装一个JSONP函数
    function jsonp(url,_callback){
        var scriptNode=document.createElement("script")
        scriptNode.setAttribute("type","text/javascript")
        scriptNode.setAttribute("src",`${url}?callback=${_callback}`)
        document.head.appendChild(scriptNode)
    }
            
    function getMusic(content){ /*定义数据返回时所执行的回调函数*/
        console.log(content.song[0])
        var comeMusic=content.song[0],
            musicTitle=comeMusic.title,
            musicArtist=comeMusic.artist,
            imgSrc=comeMusic.picture,
            _src=imgSrc.match(/.[^@]*/g)[0] /*正则匹配返回的图片地址*/
            console.log(_src)
    
            imgNode.src=_src
            h3Node.innerText=musicTitle
            h6Node.innerText=musicArtist
    }

调用jsonp函数:
jsonp(http://www.aaa.com,getMusic)

2.CORS 跨域资源共享 Cross-Origin Resource Sharing

CORS允许浏览器向跨域服务器发出XMLHttpRequest请求,突破了AJAX只能同源使用的限制。CORS需要浏览器和服务器同时支持,目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。CORS原理:浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。只要服务器实现了CORS接口,就可以跨源通信。
以nodejs server-mock后台为例:
res.header("Accept-Control-Allow-Origin","http://www.a.com:8080")//只允许http://www.a.com:8080这个源发起的请求
res.header("Accept-Control-Allow-Origin","*")//允许所有源发起的请求

浏览器将跨域请求分为两类
简单请求(simple request),同时满足以下两个条件
【1】请求方法是HEAD/GET/POST之一
【2】HTTP请求头信息不超出以下几种字段
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

非简单请求(not-so-simple request),不满足以上两个条件的请求就是非简单请求
AJAX发起跨域请求时,如果是简单类型请求,请求头信息如下:
GET /cors HTTP/1.1
Origin: http://api.bob.com //发起此次请求的所在源(协议+域名+端口号)
Host: api.alice.com //要访问的目标域
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
如果Origin指定的源,不在服务端许可范围内,服务器会返回一个正常的HTTP回应。但这个响应头信息没有包含Access-Control-Allow-Origin字段。浏览器接收后就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
如果Origin指定的域名在许可范围内,服务器返回的响应,响应头会包含以下信息字段
Access-Control-Allow-Origin: http://api.bob.com //允许来自源http://api.bob.com的访问,如果是*则代表允许来自所有源的访问
Access-Control-Allow-Credentials: true //是否允许客户端在请求中发送Cookie
Access-Control-Expose-Headers: FooBar //允许CORS请求拿到除默认的Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma外的其他字段。这里允许拿到foobar字段信息。
Content-Type: text/html; charset=utf-8

CORS请求默认不包含Cookie信息,如果需要包含Cookie信息,一方面要服务器同意
Access-Control-Allow-Credentials: true //但Access-Control-Allow-origin 不能是*
另一方面,开发者必须在AJAX请求中打开withCredentials属性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
此时Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

AJAX发起跨域请求时,如果是非简单类型请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json,非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为“预检”请求,浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
比如js代码如下:
var url = "http://api.alice.com/cors";
var xhr = new XMLHttpRequest();
xhr.open("PUT", url, true); //方法是PUT
xhr.setRequestHeader("X-Custom-Header", "value"); //自定义请求头信息
xhr.send();
请求头信息如下:
OPTIONS /cors HTTP/1.1 //方法是OPTIONS,表示这个请求是用来询问的。
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
如果服务器否定了“预检”请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。浏览器接收后就知道出错了,被XMLHttpRequest对象的onerror回调函数捕获。
如果服务器接受了“预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。服务器发送响应头信息如下:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT //表明服务器支持的所有跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

3.HTML5跨文档通信API window.postMessage

HTML5中最酷的新功能之一就是 跨文档消息传输Cross Document Messaging。下一代浏览器都将支持这个功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。 Facebook已经使用了这个功能,用postMessage支持基于web的实时消息传递。

举例,父窗口aaa.com向子窗口bbb.com发消息,调用postMessage方法如下:

var popup = window.open("http://bbb.com", "title");
popup.postMessage("Hello World!", "http://bbb.com");

postMessage方法的第一个参数是具体的信息内容(支持任意类型),第二个参数是接收消息目标窗口的源origin(协议 + 域名 + 端口)也可以设为*,表示不限制域名,向所有窗口发送。
子窗口向父窗口发送消息的写法如下:
window.opener.postMessage("Nice to see you", "http://aaa.com");
父窗口和子窗口都可以通过message事件,监听对方的消息:

window.addEventListener("message", function(event) {
  console.log(e.data);
},false);

事件对象event的三个属性:
event.source:发送消息的窗口对象,对发送消息的窗口对象的引用;
event.origin: 发送消息的窗口的源(协议、域名、端口号)这里不是接受消息的窗口的源
event.data: 消息内容
event.origin属性可以过滤不是发给本窗口的消息,举例如下:
当bbb网站收到来自aaa网站发来的消息

window.addEventListener("message", receiveMessage);
function receiveMessage(event) {
  if (event.origin !== "http://aaa.com") return;//判断发消息的窗口的源是否是aaa网站的源,这里的event.origin和postMessage()方法中的origin不一样!!
  if (event.data === "Hello World") {
      event.source.postMessage("Hello", event.origin);//这里event.origin指向aaa网站的源即消息接收的窗口的源
  } else {
    console.log(event.data);
  }
}

otherWindow.postMessage方法中,otherWindow是其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。即只能适用于open方法打开的页面相互发送消息,或者通过iframe嵌套的页面之间发送消息。通过window.postMessage()方法还可读取其他窗口的LocalStorage
window.open("http://www.bbb.com:8881/b_index.html","title") //主页面中打开子页面
window.opener.postMessage(${msg},"http://www.aaa.com:8888") //引用打开子页面的主页面

4.降域 实现iframe窗口跨域访问 实现不同子域页面cookies共享

当两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain来实现iframe窗口相互访问或设置cookie。降域方式只适用于同一站点下不同子域名共享cookie或者iframe页面中嵌套的子域名页面之间的访问。降域不适用访问LocalStorage 和 IndexedDB,postMessage()方法可访问LocalStorage
用改写document.domain+iframe的方法来获取目标域数据。缺点是安全性差,一个页面被攻击后另一个页面的数据也会被泄露且不支持ajax方式请求数据。降域只能解决主域相同而二级域名(子域名)不同的两个页面请求数据的情况,如果把script.a.com的domian设为alibaba.com那显然是会报错。domain只能设置为主域名,不可以在b.a.com中将domain设置为c.a.com;且只能由子域名改到父域名或父父域名,不能从父父域名改到子域名(比如将b.com改成script.b.com是不行的)

举例来说,A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,子域名不同默认情况下会被同源策略阻止访问。只要将两个页面都设置相同的document.domain,两个网页就可以共享Cookie或在iframe窗口下相互访问。
document.domain = "example.com";
服务器也可以在设置 Cookie 的时候,指定 Cookie 的所属域名为一级域名以后二级域名和三级域名不用做任何设置,都可以读取这个Cookie。比如.example.com。
Set-Cookie: key=value; domain=.example.com; path=/

降域的特点:只能对主域相同子域不同的iframe页面和父页面(或者open方法打开的主域相同的页面)形式的跨域起作用,可实现的两个页面共享cookie。修改domain时top页面和iframe页面都要使用document.domain去修改成一致的域名。具体的做法是可以在http://www.a.com/a.html和http://script.a.com/b.html两个文件中分别加上document.domain = "a.com";然后通过a.html文件中创建一个iframe,去控制iframe的contentDocument,这样两个js文件之间就可以“交互”了。验证步骤如下:
1.本地文件夹中有两个文件index.html(www.a.com下的网页),b.html(script.a.com下的网页),a.com下的index.html页面中有iframe页面(src="http://script.a.com:8080/b.html")
2.由于域仅仅是通过“URL的首部”来识别,不会判断两个不同域名是否为同一IP地址。根据这点,修改本地host文件增加两行,子域名不同,主域名相同来模拟跨域。
127.0.0.1 www.a.com
127.0.0.1 script.a.com
3.用mock start命令启动服务器并分别访问
http://www.a.com:8080/index.html
http://script.a.com:8080/b.html
两个页面console.log(document.domain)时分别返回www.a.com和script.a.com,此时是跨域状态。
4.当在www.a.com下的html页面中执行以下脚本时提示
var ccc=document.getElementsByTagName("iframe")[0].contentDocument

(注:.contentDocument方法可操作iframe页面内的信息)
5.解决方案:www.a.com下的index.html页面和script.a.com下的b.html页面中都增加脚本,将两个页面的域设置成相同的主域名

再次执行var ccc=document.getElementsByTagName("iframe")[0].contentDocument
返回的就是b.html文件中的信息

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

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

相关文章

  • web安全一,同源策略跨域

    摘要:可以说同源策略在安全中扮演着及其重要的角色。我把这个领域的东西写成了一个系列,以后还会继续完善下去安全一同源策略与跨域安全二攻击安全三攻击 之所以要将同源策略与跨域写在一起,是因为存在浏览器的同源策略,才会存在跨域问题 何为同源策略 同源策略是浏览器实现的一种安全策略,它限制了不同源之间的文档和脚本交互的权限。只有同一个源的脚本才会具有操作dom、读写cookie、session 、a...

    cgspine 评论0 收藏0
  • 同源策略跨域资源共享

    摘要:由于浏览器的同源策略导致无法直接通过拿到后台数据。目前,如果非同源,共有三种行为受到限制。此处应有掌声参考关于跨域资源共享和浏览器的同源策略限制的具体讲解。 工作中,经常会遇到需要跨域请求数据的情况。由于浏览器的同源策略,导致无法直接通过ajax拿到后台数据。解决这个问题的方式之一就是JSONP。还有一种方式更高效简单——跨域资源共享(Cross-origin Resource Sha...

    darcrand 评论0 收藏0
  • 同源策略跨域资源共享的纠缠

    摘要:扯了这么多,自然不是为了吹水,而是要为了引出前端开发的一个重要的知识点同源策略什么是同源策略出于保护用户信息安全的目的,现在的浏览器都会实施同源策略这个政策,所谓同源策略指的是不同源的客户端脚本在没有明确授权情况下,不允许读写对方的资源。 导语你家的小孩带了他的朋友来你们的家里玩,你家的小孩如果要在自家屋里拿玩具玩、拿东西吃你自然是不会阻止,但是如果你家小孩的朋友人品不行,乱拿东西吃、...

    alighters 评论0 收藏0
  • 同源策略跨域资源共享的纠缠

    摘要:扯了这么多,自然不是为了吹水,而是要为了引出前端开发的一个重要的知识点同源策略什么是同源策略出于保护用户信息安全的目的,现在的浏览器都会实施同源策略这个政策,所谓同源策略指的是不同源的客户端脚本在没有明确授权情况下,不允许读写对方的资源。 导语你家的小孩带了他的朋友来你们的家里玩,你家的小孩如果要在自家屋里拿玩具玩、拿东西吃你自然是不会阻止,但是如果你家小孩的朋友人品不行,乱拿东西吃、...

    赵连江 评论0 收藏0
  • FE.B-理解浏览器的同源策略跨域方案

    摘要:方案浏览器设置一级域名。场景完全不同源的网站,需要窗口通信方案父子窗口互相写互相监听子窗口写后跳回同域父窗口读浏览器跨文档通信场景请求非同源地址方案架设服务器代理参考资料浏览器同源政策及其规避方法阮一峰前端常见跨域解决方案全跨域几种方式 同源 概念:协议,域名,端口 相同。目的:保证用户信息的安全,防止恶意的网站窃取数据。限制的行为: Cookie、LocalStorage 和 In...

    oujie 评论0 收藏0
  • FE.B-理解浏览器的同源策略跨域方案

    摘要:方案浏览器设置一级域名。场景完全不同源的网站,需要窗口通信方案父子窗口互相写互相监听子窗口写后跳回同域父窗口读浏览器跨文档通信场景请求非同源地址方案架设服务器代理参考资料浏览器同源政策及其规避方法阮一峰前端常见跨域解决方案全跨域几种方式 同源 概念:协议,域名,端口 相同。目的:保证用户信息的安全,防止恶意的网站窃取数据。限制的行为: Cookie、LocalStorage 和 In...

    ghnor 评论0 收藏0

发表评论

0条评论

xavier

|高级讲师

TA的文章

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