为什么表单提交不会跨域

同源策略与跨域概述

在Web安全领域,同源策略(Same-Origin Policy)是一种关键的安全机制,它限制了一个源(协议+域名+端口)的文档或脚本如何与另一个源的资源进行交互。这个策略的目的是为了防止恶意网站读取另一个网站的敏感数据,从而保护用户信息安全。

同源策略主要表现在三个层面:

  1. DOM层面:限制了不同源的JavaScript脚本对当前DOM对象的读写操作
  2. 数据层面:限制了不同源的站点读取当前站点的Cookie、IndexDB、LocalStorage等数据
  3. 网络层面:限制了通过XMLHttpRequest或Fetch等API将站点的数据发送给不同源的站点

表单提交与同源策略的关系

虽然同源策略限制了JavaScript通过XMLHttpRequest或Fetch API进行跨域请求,但有趣的是,HTML表单(form)提交到不同源的服务器却不受此限制。这看似是同源策略的一个”漏洞”,但实际上是Web平台设计的一部分。

为什么表单可以跨域提交?

  1. 历史原因:表单提交是早期Web的基础功能,在同源策略制定前就已存在,为了保持向后兼容性而被保留

  2. 安全模型差异:同源策略主要关注的是读取操作而非写入操作

    • 表单提交是一种”写入”操作,它向服务器发送数据,但不能读取响应内容
    • 当表单提交到跨域服务器时,浏览器会导航到新页面或刷新当前页面,原页面的JavaScript无法读取响应内容
  3. 用户交互:传统上,表单提交通常需要用户主动触发(如点击提交按钮),这被认为是一种明确的用户意图表达

表单提交与XMLHttpRequest/Fetch的区别

特性表单提交XMLHttpRequest/Fetch
跨域能力可以直接跨域提交默认受同源策略限制,需要CORS支持
响应处理无法直接获取响应内容可以读取响应内容并进行处理
页面影响默认会导致页面刷新或跳转不会导致页面刷新(异步)
请求控制有限的请求方法和头部控制可以精细控制请求方法、头部等
数据格式主要支持表单编码数据支持多种数据格式(JSON、XML等)

表单跨域提交的安全风险

虽然表单可以跨域提交,但这也带来了安全风险,最典型的就是CSRF(跨站请求伪造)攻击:

CSRF攻击原理

  1. 用户登录了目标网站A并获得了认证Cookie
  2. 用户在不登出A的情况下访问了恶意网站B
  3. 恶意网站B包含一个自动提交到网站A的表单
  4. 当表单提交时,浏览器会自动携带网站A的Cookie
  5. 网站A的服务器收到请求后,验证Cookie有效,执行了操作
<!-- 恶意网站上的隐藏表单示例 -->
<form id="evil-form" action="https://bank.com/transfer" method="POST" style="display:none">
  <input type="hidden" name="to" value="hacker-account" />
  <input type="hidden" name="amount" value="10000" />
</form>
<script>
  document.getElementById("evil-form").submit(); // 自动提交表单
</script>

防护措施

为了防止表单跨域提交带来的安全风险,可以采取以下措施:

1. CSRF Token

最常用的防护措施是使用CSRF Token:

<form action="/transfer" method="post">
  <input type="hidden" name="csrf_token" value="随机生成的令牌" />
  <!-- 其他表单字段 -->
</form>

服务器验证这个令牌是否有效,由于同源策略限制,恶意网站无法读取到这个令牌,因此无法构造有效的请求。

2. SameSite Cookie属性

现代浏览器支持为Cookie设置SameSite属性,限制Cookie在跨站请求中的发送:

Set-Cookie: session=abc123; SameSite=Strict
  • Strict:完全禁止第三方网站发送该Cookie
  • Lax:允许导航到目标网站的GET请求发送Cookie,但禁止其他跨站请求发送
  • None:允许跨站请求发送Cookie(需要同时设置Secure属性)

3. 验证请求来源

服务器可以检查HTTP请求头中的OriginReferer字段,验证请求是否来自可信来源:

// 服务器端验证示例(伪代码)
const origin = request.headers.origin;
const allowedOrigins = ['https://example.com', 'https://sub.example.com'];
 
if (!allowedOrigins.includes(origin)) {
  return response.status(403).send('Forbidden');
}

4. 要求用户再次验证敏感操作

对于敏感操作(如转账、修改密码等),可以要求用户再次输入密码或进行其他形式的身份验证。

总结

表单提交不受跨域限制主要是因为:

  1. 历史兼容性考虑
  2. 同源策略主要限制的是”读取”而非”写入”操作
  3. 表单提交后无法直接获取响应内容

虽然这种设计为Web应用提供了便利,但也带来了安全风险。开发者需要采取适当的防护措施,如CSRF Token、SameSite Cookie等,来保护Web应用免受跨站请求伪造等攻击。

理解表单提交与同源策略的关系,对于前端开发者来说是非常重要的,它有助于我们构建更安全的Web应用。