同源策略与跨域
同源策略
该策略的核心思想是限制来自不同源的文档或脚本对当前文档的读取和交互。
同源策略限制和允许的内容
同源策略限制的内容可以分为三个部分:网络访问、js中API的访问、数据存储的访问。
限制类型 | 具体内容 |
---|---|
网络访问 | http、img标签 |
js中的API的访问 | Window 和 Location 对象 |
数据存储的访问 | Cookie、LocalStorage、IndexedDB 等存储性内容 |
不受跨域限制的情况
简单请求
不会触发 CORS 预检请求称为简单请求,其不会受同源策略的影响跨域不会被拦截。
简单请求需满足以下条件:
- 请求方法为
GET、HEAD、POST
之一; - 请求头除了代理自动设置的字段(
User-Agent
等),只能包含:Accept
,Accept-Langugage
,Content-Langugage
,Content-Type
,Range
(只允许设置bytes=256-
等值); - 使用
Content-Type
时,值只能为text/plain
,multipart/form-data
,application/x-www-form-urlencoded
。 - 响应头只能包含:
Cache-Control,Content-Language,Content-Length,Content-Type,Expires,Last-Modified,Pragma
部分 html 标签
script、link、img、video、audio、object、embed、@font-face、iframe 等标签允许加载跨域的资源,但是例如 script 脚本里的内容无法访问外部的 DOM 元素。
防护场景:
1. 恶意脚本通过该页面的文档对象模型DOM
访问另一个网页上的敏感数据
例如:假设存在一个恶意网页malicious.com
,它试图通过iframe嵌入受害者网站victim.com
,并尝试读取其DOM中的敏感数据。
<!DOCTYPE html>
<html>
<head>
<title>Malicious Page</title>
</head>
<body>
<iframe src="https://victim.com/secret-data.html" width="0" height="0"></iframe>
<script>
// 尝试访问iframe中的DOM
var iframe = document.getElementsByTagName('iframe')[0];
if (iframe) {
var victimDocument = iframe.contentWindow.document;
// 读取敏感数据
var sensitiveData = victimDocument.getElementById('sensitive-info').innerHTML;
// 这里可以进一步将数据发送到攻击者控制的服务器
console.log(sensitiveData);
}
</script>
</body>
</html>
2. 恶意脚本获取、修改 cookie
里的敏感信息;
假设攻击者控制了一个恶意网站malicious.com
,并且用户在访问该网站时,恶意脚本尝试获取和修改用户的cookie。
<!DOCTYPE html>
<html>
<head>
<title>Malicious Page</title>
</head>
<body>
<script>
// 尝试获取cookie
var cookies = document.cookie;
console.log("User's cookies:", cookies);
// 尝试修改cookie(这通常需要服务器端的配合)
// 注意:这里的代码在客户端执行时不会有任何效果,因为HttpOnly的cookie无法被JavaScript读取或修改
document.cookie = "sensitive-cookie=new-value; path=/; expires=Fri, 31 Dec 9999 23:59:59 GMT";
</script>
</body>
</html>
跨域
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。 不同域之间相互请求资源,就算作“跨域”。由于同源策略的限制,跨域访问在默认情况下是被禁止的。
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
跨域资源访问手段:JSONP、CORS(跨源资源共享)等。
同源策略和跨域的关系
跨域是在同源策略的背景下产生的一个概念。正是因为有了同源策略的限制,开发者在进行不同源之间的资源访问时需要特别注意,这就产生了跨域的问题。同时,为了解决跨域问题,需要采用一些特殊的技术和策略。
跨域处理方法
代理服务器转发请求
此种方式属于通过代理服务器将请求转发给后端服务,由于务器不受浏览器的同源策略限制, 所以能让开发者绕过限制。例如,本地开发使用的webpack
代理配置、nginx
中的proxy
配置都属于本方式。
JSONP
- script 中src 使用 ?callback=fn 的形式,想后台传递一个函数。
- 后台接收到,往fn 中注入参数,fn({name:'test'}) 的形式返回。
- 前台接收到返回 fn 函数会立即执行,一般会返回处理过的数据。
CORS
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的 http 通信没有差别,代码完全一样。只不过浏览器发现 http 请求跨域时,会自动添加一些附加的头信息,有时还会多出一次附加的请求。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
- CORS要求浏览器(>IE10)和服务器的同时支持,是跨域的根本解决方法,由浏览器自动完成。
- 只要在服务器中设置 Access-Control-Allow-Origin 允许跨域的地址,那么浏览器检测到响应头中的地址包含当前请求的地址,就不会拦截。
//允许任何网站跨域
Access-Control-Allow-Origin:*
//也可以设置跨域的方法
Access-Control-Allow-Methods:POST,GET
允许使用 CORS 的情况:
- 由
XMLHttpRequest
或 Fetch API 发起的跨源 HTTP 请求。 - Web 字体(CSS 中通过
@font-face
使用跨源字体资源),因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。 - WebGL 贴图。
- 使用
drawImage()
将图片或视频画面绘制到 canvas。 - css shapes 。
webSocket
Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。 原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
postMessage
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。
- 父窗口监听 message 事件,获取子窗口发送的数据,获取数据时有可能会有多个子窗口往父窗口发送数据,所以父窗口要做好判断。
- 子窗口使用 postMessage 往父窗口发送数据。
// iframe的页面
window.addEventListener("message",function(e){
//判断发送方的源
if(e.origin !== 'http://www.baidu.com'){
return
}
//接收到数据之后,给外层的window发送接收成功的消息
e.source.postMessage('getMessage',e.origin)
})
//外层window
window.onload = function(){
var fra = window.iframes[0]
var obj = {from:'localhost:8080',message:'send message'}
fra.postMessage(JSON.stringfy(obj),'http://localhost:8081')
window.addEventListener('message',e=>{
console.log('81 receive message success')
})
}
修改源
满足某些限制条件的情况下,页面是可以修改它的源。脚本可以将 document.domain
的值设置为其当前域或其当前域的父域。如果将其设置为其当前域的父域,则这个较短的父域将用于后续源检查。
例如:假设 http://store.company.com/dir/other.html
文档中的一个脚本执行以下语句
document.domain = "company.com";
这条语句执行之后,页面将会成功地通过与 http://company.com/dir/page.html
的同源检测(假设http://company.com/dir/page.html
将其 document.domain
设置为“company.com
”,以表明它希望允许这样做——更多有关信息,请参阅 document.domain
)。然而,company.com
不能设置 document.domain
为 othercompany.com
,因为它不是 company.com
的父域。
端口号是由浏览器另行检查的。任何对 document.domain
的赋值操作,包括 document.domain = document.domain
都会导致端口号被覆盖为 null
。因此 company.com:8080
不能仅通过设置 document.domain = "company.com"
来与 company.com
通信。必须在它们双方中都进行赋值,以确保端口号都为 null
。
该方法的限制:
localStorage
、indexedDB
、BroadcastChannel
、SharedWorker
等Web API 无法躲过源检查。
常见跨域场景
场景 | 解决方式 |
---|---|
前后端分离开发联调 | 代理服务器,webpack等工具都支持 proxy 配置 |
前后端分离部署 | 代理服务器,例如 nginx 配置 proxy 将接口转发至后端网关 |
获取 iframe 中的元素 | 修改源,将 domain 设置为 iframe 的父级 |
图片403反盗 | 1. 通过 referrerpolicy 知道请求发起的地址 2. 设置 crossorigin="use-credentials" 携带 cookie 信息 3. cookie 设置 samesite=none |
cookie 跨站共享(二级域名一致) | 按照步骤:1. 前端请求时在request 对象中配置"withCredentials": true ;2. 服务端在response 的header 中配置"Access-Control-Allow-Origin", "http://xxx:${port}" ;3. 服务端在response 的header 中配置"Access-Control-Allow-Credentials", "true" ; 4. cookie 设置 samesite=none |
Content Sercurity Policy
CSP 可以简单理解为 白名单策略,通过设定 script、字体、frame、img、audio、video、worker等资源所允许加载的来源,来减少外部攻击。
CSP 通过Content-Security-Policy
字段来配置策略,以下是部分配置项的解释:
default-src
: 其他配置项未设定时的 fallback 配置项;font-src
: 设置@font-face
可加载的源;frame-src
:设置frame
iframe
标签可加载的源;img-src
:img
favicons
可加载的源;mdeia-src
:audio
video
可加载的源;script-src
:script
可加载的源;style-src
:stylesheets
可加载的源;
同源策略和CSP之间的关系及区别
- 目标不同:同源策略主要关注于控制不同源之间的资源访问权限,防止跨源请求和操作;而CSP则关注于限制Web页面内部的资源加载和执行,防止恶意内容的注入和执行。
- 实施方式不同:同源策略是由浏览器自动强制执行的,不需要开发者进行额外的配置;CSP则需要开发者主动在HTTP响应头或HTML元数据中声明策略。
- 灵活性:CSP提供了更高的灵活性,开发者可以根据需要自定义策略,例如允许某些特定的外部资源加载,或者限制内联脚本的执行等。同源策略则较为严格,通常不允许跨源的行为,除非通过特定的机制(如CORS)进行明确的跨源通信授权。
- 防御范围:同源策略主要防止的是跨站脚本攻击(XSS)和跨站请求伪造(CSRF)等攻击;而CSP则更广泛地防止各种类型的代码注入攻击,包括但不限于XSS。