Notes

鉴权与 Web 安全:OAuth, JWT, XSS, CSRF, CSP

一句话结论:采用基于 Token 的标准鉴权流程 (如 OAuth 2.0 + JWT) 保护用户身份,并实施一套由 CSP (内容安全策略)、HttpOnly Cookies 和其他最佳实践构成的纵深防御体系,是保护现代 Web 应用免受 XSS、CSRF 等主流攻击的基石。


1. 鉴权 (Authentication) vs 授权 (Authorization)

在进入技术细节前,必须先厘清这两个概念:

  • 鉴权 (Authentication):你是谁?(Who are you?) —— 验证用户身份的过程,通常通过用户名密码、社交登录等方式。
  • 授权 (Authorization):你有什么权限?(What are you allowed to do?) —— 验证用户是否有权访问特定资源或执行特定操作的过程。

本文重点讨论前端相关的鉴权机制和通用的 Web 安全防护。


2. 基于 Token 的鉴权:OAuth 2.0 与 JWT

传统的 Session-Cookie 机制在分布式、跨域的现代应用中面临挑战。基于 Token 的无状态 (stateless) 鉴权是目前的主流方案。

A. JWT (JSON Web Token)

JWT 是一个开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息(称为 claims)。

一个 JWT 由三部分组成,由 . 分隔:

  1. Header (头部):包含 token 类型和使用的签名算法 (如 HMAC SHA256 或 RSA)。
  2. Payload (载荷):包含 claims。这是关于实体(通常是用户)和其他数据的声明。常见的 claims 有 sub (主题, 即用户ID)、exp (过期时间)、iat (签发时间)。
  3. Signature (签名):用于验证消息在传递过程中没有被篡改。计算方式是:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

核心优势自包含。服务器无需查询数据库或缓存,仅通过验证签名就能确认 token 的有效性和用户的身份。

B. 鉴权流程 (OAuth 2.0 "Implicit" or "Auth Code" Flow)

  1. 用户登录:用户在前端页面提交凭据(用户名/密码)或通过第三方登录 (Google/GitHub)。
  2. 服务器验证:服务器验证用户凭据。
  3. 签发 Token:验证成功后,服务器生成一个 Access Token (通常是 JWT) 和一个 Refresh Token
    • Access Token:生命周期短(如 15 分钟),用于访问受保护的 API 资源。
    • Refresh Token:生命周期长(如 7 天),用于在 Access Token 过期后,无感地获取一个新的 Access Token。
  4. Token 存储 (前端)
    • Access Token: 存储在内存中(例如,React Context 或 Zustand/Redux store)。这是最安全的方式,因为它不会被 XSS 攻击轻易窃取。
    • Refresh Token: 存储在 HttpOnly Cookie 中。HttpOnly 标志使该 Cookie 无法通过 JavaScript 访问,从而有效防御 XSS 攻击。
  5. API 请求:前端在发起受保护的 API 请求时,将 Access Token 附加在 Authorization 请求头中。
    Authorization: Bearer <access_token>
    
  6. Token 刷新:当 API 返回 401 Unauthorized 错误时,前端的请求拦截器会自动触发一个到特定刷新端点 (/refresh_token) 的请求。这个请求会携带 HttpOnly Cookie 中的 Refresh Token。服务器验证 Refresh Token 后,返回一个新的 Access Token,前端更新内存中的 Access Token 并重发失败的请求。

3. 常见 Web 攻击与防御

A. XSS (Cross-Site Scripting)

攻击原理:攻击者向你的网站注入恶意脚本,当其他用户浏览该页面时,这些脚本会在用户的浏览器中执行,从而窃取用户信息(如 LocalStorage 中的 token)、操作用户行为等。

防御策略

  1. 永远不要相信用户输入:对所有用户生成的内容 (UGC) 进行严格的转义 (HTML escaping) 或过滤。
  2. 框架内置防御:现代前端框架(如 React)默认会对 JSX 中的内容进行转义,能有效防御大部分 XSS。dangerouslySetInnerHTML 是一个危险信号,必须确保其内容是可信的。
  3. HttpOnly Cookies:如上所述,将敏感的 Refresh Token 存储在 HttpOnly Cookie 中,即使发生 XSS,攻击者也无法通过 JS 读取它。
  4. 内容安全策略 (CSP):终极防御。见下文。

B. CSRF (Cross-Site Request Forgery)

攻击原理:攻击者诱导已登录的用户访问一个恶意网站,该网站向你的应用发送一个伪造的请求(例如,转账、修改密码)。由于用户的浏览器会自动携带你的应用的 Cookie,这个请求会被服务器视为合法请求。

防御策略

  1. SameSite Cookies:为你的 Cookies 设置 SameSite 属性。
    • SameSite=Strict:完全禁止第三方网站携带该 Cookie 发起请求。最安全,但可能影响某些跨站链接体验。
    • SameSite=Lax:允许用户通过常规导航(如点击链接)从外部站点进入你的网站时携带 Cookie,但禁止在 POST 等“危险”请求中携带。这是目前大多数浏览器的默认值,能防御大部分 CSRF。
  2. Anti-CSRF Tokens (也称 Synchronizer Token Pattern):
    • 服务器在渲染表单或页面时,生成一个随机的、一次性的 token,并将其嵌入页面和用户的 Session/Cookie 中。
    • 当用户提交表单时,前端必须将这个 token 作为请求参数或请求头一并发送。
    • 服务器在处理请求时,会比较请求中的 token 和 Session/Cookie 中的 token 是否匹配。由于攻击者无法获取这个随机 token,伪造的请求就会失败。

C. 内容安全策略 (CSP - Content Security Policy)

CSP 是一个强大的 HTTP 响应头,它允许你定义一个“白名单”,声明哪些来源的资源(脚本、样式、图片、字体等)是可信的,从而可以被浏览器加载和执行。

核心作用:即使 XSS 攻击成功注入了恶意脚本,由于该脚本的来源不在白名单内,浏览器也会拒绝执行它,从而提供了最后一道坚固的防线。

示例 CSP 头

Content-Security-Policy:
  default-src 'self'; /* 默认只信任同源资源 */
  script-src 'self' https://apis.google.com; /* 脚本只信任同源和 googleapis.com */
  style-src 'self' 'unsafe-inline'; /* 样式信任同源和内联样式 */
  img-src 'self' https://*.my-cdn.com; /* 图片信任同源和 my-cdn.com 的所有子域名 */
  connect-src 'self' https://api.my-app.com; /* API 请求只信任同源和 api.my-app.com */
  frame-ancestors 'none'; /* 禁止页面被嵌入到 iframe 中,防御 Clickjacking */
  form-action 'self'; /* 表单提交只允许到同源 */
  base-uri 'self';
  object-src 'none'; /* 禁止加载 <object>, <embed>, <applet> */

实施建议

  • 从一个严格的策略开始 (default-src 'none'),然后逐步放开必要的来源。
  • 使用 Content-Security-Policy-Report-Only 头在部署前进行测试,它会报告违规行为但不会实际阻止。

4. 关联阅读

cd ..