谈及 CORS 之前,必须先了解浏览器的同源策略(SOP)。
如果两个 URL 的 协议、主机名和端口号 都相同,那么这两个 URL 就是同源的。
同源策略是 浏览器的 核心安全功能,用于限制一个源的脚本如何与另一个源的资源进行交互。其主要目的是阻止来自一个源(如 a.com
)的脚本读取对另一个源(如 b.com
)发出的 HTTP 请求的响应。
这种从一个域的脚本访问另一个域的资源的行为称为 跨域请求。SOP 并不阻止这些请求的发送,但它限制了发送请求的源(a.com
)对响应数据的访问。
如果 b.com
的服务器收到来自 a.com
的请求(请求的 Origin
头为 a.com
)并且不允许 a.com
的跨域请求,那么它的响应中将不会包含 Access-Control-Allow-Origin
头,或者这个头的值不匹配请求的源。浏览器将拒绝访问这个响应,并显示一个跨域错误。
重要的是要理解,这并不意味着服务器 「拒绝」 了请求;实际上,服务器已经处理并响应了请求,只是浏览器出于安全考虑,阻止了脚本访问这些数据。 服务器与服务器之间的通信不受同源策略的限制,只有浏览器与服务器之间的通信才受到这种限制。 对于服务器来说,他收到一个请求,如果身份验证通过(比如有正确的 cookie),对他来说他就只需要完成请求的任务,返回响应。我们说 SOP 是 浏览器的安全策略 就体现在服务器会配合浏览器的要求,把允许的域放在响应头中并返回。浏览器如果发现这个请求是跨域,会拦截这个响应,不让脚本访问这个响应。
所以只要用户使用的是安全的浏览器,就可以保证这个策略是有效的。
考虑这样一个场景,来体验 Same-Origin Policy (SOP) 是如何提高网站安全性的:
场景描述
假设 Alice 登录到她的银行账户,网站地址为
https://www.alicebank.com
。在同一浏览器中,Alice 打开了一个新的标签页,访问了一个看似无害的社交媒体网站https://www.friendface.com
。然而,这个社交媒体网站被恶意用户 Bob 植入了恶意脚本。恶意行动
Bob 的脚本旨在从所有访问者那里窃取银行信息。脚本试图通过以下方式执行:
在后台向https://www.alicebank.com
发送请求,比如尝试获取 Alice 的账户余额或进行转账。SOP 的作用
当 Bob 的恶意脚本试图从
https://www.friendface.com
发送请求到https://www.alicebank.com
并读取响应时,浏览器的 Same-Origin Policy 介入:阻止读取响应:
虽然脚本可能能发送请求(如向 Alice 的银行发起转账请求),但由于 SOP 的限制,该脚本无法读取任何来自https://www.alicebank.com
的响应数据。因此,Bob 的脚本无法验证其操作是否成功,也无法获取 Alice 的敏感信息。结果
保护用户数据:Alice 的敏感银行数据和操作细节被保护,因为即使请求被发送,恶意脚本也无法访问响应内容。
限制恶意行为:通过限制不同源之间的数据交互,SOP 减少了跨站脚本攻击(XSS)的风险,有效防止了数据泄露。
浏览器会有一个 「预检请求」,即在发送真正的请求之前,先发送一个 OPTIONS 请求,以确定服务器是否允许跨域请求。这个请求中会包含一个 Access-Control-Request-Method
头,用于告知服务器真正请求的方法。服务器收到这个请求后,如果允许跨域请求,会在响应中包含 Access-Control-Allow-Methods
头,告知浏览器允许的跨域请求方法。
刚才也提到了服务器可以控制允许一些跨域请求,CORS 是一种策略,允许网站绕过 SOP 的限制,但前提是得到服务器的明确允许。当从 a.com
向 b.com
发送 AJAX 请求时,浏览器会自动在 HTTP 头信息中添加 Origin
头,其值为请求发起的源(即 a.com
)。如果 b.com
的服务器配置为允许来自 a.com
的请求,它将在响应中包含 Access-Control-Allow-Origin
头,其值设置为 a.com
或通配符 *
,表示接受任何域的请求。这样,浏览器就可以正常处理响应了。如果 b.com
不允许来自 a.com
的请求,浏览器将拒绝访问响应,并显示跨域错误。
通过在服务器端设置代理,可以绕过 SOP 限制。这个代理会向其他域的资源发起请求,并在响应中将 Access-Control-Allow-Origin
设置为 *
,再返回给浏览器,从而允许浏览器访问这个响应。
代理服务器部署的好处包括:
服务器端通过设置 CORS 响应头允许跨域请求,是最推荐的解决方案。例如:
// 允许来自特定域的跨域请求 res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000'); // 允许跨域请求携带的头 res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); // 允许的跨域请求方法 res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
JSONP 是一项比较老的技术,是利用 <script>
标签 src
属性的无跨域限制特性,通过动态创建 <script>
标签请求 JSON 数据的技术。服务器返回的数据作为回调函数的参数,客户端通过定义回调函数处理返回的数据。
尽管 SOP 提供了强大的安全保护,但它并不能完全防止跨站攻击,特别是 跨站请求伪造(CSRF)。CSRF 攻击主要通过利用网站的认证机制,在用户不知情的情况下代表用户发起请求。
SOP 限制了不同源的网站访问或修改彼此的数据,但并未阻止向其他源发送携带用户凭证(如 cookies)的请求。因此,恶意网站可以诱导用户的浏览器向目标网站发送请求,目标网站可能误认为这些请求是用户自愿发起的。
简单来说就是 SOP 虽然阻止了
a.com
直接访问b.com
的响应内容,但是并没有阻止a.com
向b.com
发送请求。因此,如果用户已经在b.com
登录,浏览器会自动携带b.com
的 cookie 随请求发送,因为 cookie 是自动随每个请求发送到对应域名的,而这恰恰是 CSRF 攻击所利用的。
为了防御 CSRF 攻击,需要在应用层面采取措施,例如:
Anti-CSRF Token
:服务器向客户端提供一个唯一的、随机的 token,该 token 不会被第三方网站知道。客户端每次提交请求时都需要包含这个 token,服务器验证 token 的有效性。因为恶意网站无法获取这个 token,所以这种方法可以有效防止 CSRF 攻击。SameSite
cookie 属性:设置 cookie 的 SameSite
属性可以限制 cookie 随跨站请求发送。例如,设置为 Strict
或 Lax
,可以减少 CSRF 攻击的风险。Referer
和 Origin
头部:通过验证 HTTP 请求的 Referer
或 Origin
头部信息,来确保请求是从受信任的源发起的。简单总结就是在浏览器自动发送请求携带的内容里加入一些额外的验证信息,这样就不会因为浏览器自动携带对于域的 cookie 而导致 CSRF 攻击。
在大型网站中,使用 Session 存储 CSRF Token 会带来很大的压力。访问单台服务器 Session 是同一个。但是现在的大型网站中,我们的服务器通常不止一台,可能是几十台甚至几百台之多,甚至多个机房都可能在不同的省份,用户发起的 HTTP 请求通常要经过像 Nginx 之类的负载均衡器之后,再路由到具体的服务器上,由于 Session 默认存储在单机服务器内存中,因此在分布式环境下同一个用户发送的多次 HTTP 请求可能会先后落到不同的服务器上,导致后面发起的 HTTP 请求无法拿到之前的 HTTP 请求存储在服务器中的 Session 数据,从而使得 Session 机制在分布式环境下失效,因此在分布式集群中 CSRF Token 需要存储在 Redis 之类的公共存储空间。
https://tech.meituan.com/2018/10/11/fe-security-csrf.html
SOP 可以防止恶意网站读取其他网站的数据,而 CSP 可以让网站所有者控制自己绿色网站的允许加载的内容。
CSP 是一种安全策略,server 通过设置 HTTP 响应头部 Content-Security-Policy
字段,指定浏览器只允许加载指定来源的资源,从而有效防止 XSS 和数据注入攻击。
CSP 需要浏览器支持,目前主流浏览器都支持 CSP。不支持的默认情况下会忽略 CSP 头部,降级为同源策略。
SOP 同源策略意味着来自同一源的脚本只能读取和修改同一源中的数据,而不能访问其他源的数据。限制了恶意脚本对其他源数据的访问。而 CSP 是一种通过 HTTP 头部由网站管理员明确设置的策略,可以自定义规则来提高安全性。
CSP 与 SOP 的区别: