会话与认证
涵盖 cookie、session、token、HttpOnly、SameSite、SessionStorage、JWT、OAuth 完整认证授权知识。
一、Cookie
1. 概念
Cookie 是浏览器为站点保存的一小块数据(单个 Cookie 通常约 4 KiB)。服务器可通过 Set-Cookie 设置,脚本也可写入非 HttpOnly Cookie。发送请求时,只有 Domain/主机、Path、Secure、SameSite、过期时间等条件匹配的 Cookie 才会自动放入请求头。
2. 基本用法
// 服务端设置(HTTP 响应头)
Set-Cookie: name=value; Expires=...; Max-Age=...; Path=/; Domain=.example.com; Secure; HttpOnly; SameSite=Lax// 客户端查看 / 操作
document.cookie // 'name=value; name2=value2'
// 设置(要 encodeURIComponent 防止特殊字符)
document.cookie = `username=${encodeURIComponent('张三')}; max-age=3600; path=/`
// 删除(设置过期时间为过去)
document.cookie = `username=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`
// 读取(需要自己解析)
function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'))
return match ? decodeURIComponent(match[2]) : null
}3. Cookie 属性
| 属性 | 含义 |
|---|---|
Expires / Max-Age | 过期时间。Max-Age 单位是秒 |
Path | Cookie 作用路径,默认当前路径 |
Domain | Cookie 作用域名,默认当前域名 |
Secure | 仅在 HTTPS 下发送 |
HttpOnly | JS 无法访问(防 XSS 窃取) |
SameSite | 跨站发送限制(防 CSRF) |
4. 第三方 Cookie
第一方 Cookie:当前访问域名设置的 Cookie
第三方 Cookie:由其他域名(广告、社交、CDN)设置的 Cookie
例子:访问 example.com,页面上有 ad-network.com 的广告
- example.com 设置的是第一方 Cookie
- ad-network.com 设置的是第三方 Cookie
现代浏览器(Safari、Firefox、Chrome)默认/逐步禁用第三方 Cookie
→ 影响:广告跟踪、SSO 单点登录等二、Session
1. 概念
Session 是服务器端的会话机制,服务器为每个用户创建一个 Session 对象,存储用户信息(登录态、权限等),并通过 Cookie 返回一个 Session ID 给客户端。
工作流程:
1. 用户登录 → 服务器验证 → 创建 Session 对象(内存/Redis/DB)→ 返回 Session ID
2. 浏览器收到 Set-Cookie: SESSIONID=xxx
3. 后续请求:浏览器自动带 Cookie: SESSIONID=xxx
4. 服务器根据 SESSIONID 找到对应的 Session 对象
5. 识别用户身份,返回相应内容2. Session 存储
Session 数据存储在服务端,可以是:
- 内存(重启丢失,不适用于分布式)
- 数据库(MySQL、MongoDB)
- Redis / Memcached(高性能,主流选择)
- 文件系统
分布式 Session 问题:
- 多服务器需要共享 Session
- 解决方案:
1. Session 复制(同步到所有服务器)
2. Session 粘性(同一用户始终路由到同一服务器)
3. Session 集中存储(Redis,所有服务器访问同一 Session 源)3. Session 的缺点
1. 服务器开销:每个用户都要在服务端存储
2. 扩展性:分布式需要共享 Session
3. CSRF:默认浏览器会自动带上 Session Cookie,攻击者可利用
4. 跨域限制:默认同源策略限制,跨域访问受限三、Token
1. 概念
Token 是无状态的认证方案,服务器不存储会话状态,而是把用户信息(或引用)编码到 Token 中,客户端每次请求带 Token。
工作流程:
1. 用户登录 → 服务器验证 → 生成 Token → 返回给客户端
2. 客户端存储(localStorage / cookie / memory)
3. 后续请求:在 Authorization 头中带 Token
4. 服务器验证 Token 签名/合法性 → 识别用户身份2. JWT(JSON Web Token)
JWT 结构(点分隔三段):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
┌──────────┐ ┌─────────────────────┐ ┌──────────────┐
│ Header │ │ Payload (Claims) │ │ Signature │
│ 算法+类型 │ │ 携带的数据 │ │ 签名 │
└──────────┘ └─────────────────────┘ └──────────────┘Header
{
"alg": "HS256",
"typ": "JWT"
}Payload(声明 Claims)
{
// 标准声明(Registered Claims)
"iss": "issuer",
"sub": "subject", // 用户 ID
"aud": "audience",
"exp": 1516239022, // 过期时间
"nbf": 1516239022, // 生效时间
"iat": 1516239022, // 签发时间
"jti": "token-id", // JWT ID
// 自定义声明
"name": "张三",
"role": "admin"
}Signature
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)3. JWT 的使用
// 登录获取 Token
async function login(username, password) {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
})
const { token } = await res.json()
localStorage.setItem('token', token)
}
// 后续请求带 Token
async function fetchUser() {
const token = localStorage.getItem('token')
const res = await fetch('/api/user', {
headers: { Authorization: `Bearer ${token}` }
})
return res.json()
}
// Token 失效处理(拦截器)
axios.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
// Token 过期,跳转登录
window.location.href = '/login'
}
return Promise.reject(error)
}
)4. JWT 的优缺点
优点:
- 无状态:服务端无需存储,水平扩展容易
- 跨域友好:只需前端带 Header,不像 Cookie 受同源限制
- 自包含:Payload 可以放用户信息、权限,减少数据库查询
- 标准化:RFC 7519,多语言支持
缺点:
- Token 一旦签发就有效(除非服务端有额外机制),无法主动失效
- Payload 不加密,敏感信息不能放(base64 不是加密)
- 体积较大:Token 通常有几百字节
- 无法单独续期:需要 refresh token 机制
5. 双 Token 机制
Access Token:短期(如 15 分钟),用于 API 鉴权
Refresh Token:长期(如 7 天),用于刷新 Access Token
工作流程:
1. 登录:服务器返回 accessToken + refreshToken
2. API 请求:带 accessToken
3. accessToken 过期:带 refreshToken 换新 accessToken
4. refreshToken 也过期:重新登录
存储:
- accessToken:内存可缩短令牌暴露时间;`sessionStorage` 仍可被同源恶意脚本读取,不能“避免 XSS”
- refreshToken:httpOnly Cookie(防 XSS)+ SameSite=Strict(防 CSRF)四、Cookie vs Session vs Token 对比
Cookie 是浏览器存储与 HTTP 传输机制,Session 是服务端状态管理方式,JWT 是令牌格式,三者并非严格互斥的同层方案。例如 Session ID 或 JWT 都可以放在 Cookie 中。
| 维度 | Cookie | 服务端 Session | Token / JWT |
|---|---|---|---|
| 存储位置 | 浏览器 | 数据在服务端,ID 常在客户端 | 可放内存、Cookie 或 Web Storage;由方案决定 |
| 状态 | 只是存储/传输机制 | 服务端有状态 | JWT 可自包含,但系统仍可能维护撤销或用户状态 |
| 扩展性 | 取决于携带的数据和后端设计 | 多节点通常需共享或粘性会话 | 自包含令牌可减少会话查询,但密钥轮换和撤销仍需设计 |
| 跨站/跨源 | 受 Domain、SameSite、CORS 等约束 | 取决于承载 Session ID 的方式 | 取决于令牌存储、发送方式和 CORS,不是天然“跨域灵活” |
| CSRF | 自动随请求发送时需防护 | 常借助 Cookie 标识,因此需防护 | 取决于传输位置;放在 Cookie 中仍有 CSRF 风险 |
| 撤销 | 客户端删除不等于服务端失效 | 服务端可删除会话 | 自包含 JWT 通常要短过期或配合撤销表;不透明 Token 可在服务端撤销 |
| 性能 | 匹配的 Cookie 随请求发送 | 通常要查服务端存储 | 需要验签及业务校验,也可能查撤销表/用户状态 |
| 典型场景 | 偏好、本地状态 | 传统服务端应用 | 前后端分离、SPA、微服务 |
五、HttpOnly 与 SameSite
1. HttpOnly
Set-Cookie: SESSIONID=xxx; HttpOnly- JS(document.cookie)无法访问
- 防止 XSS 攻击窃取 Session Cookie
- 注意:HttpOnly 不能防 CSRF(CSRF 不需要读 Cookie)
2. Secure
Set-Cookie: SESSIONID=xxx; Secure- 仅在 HTTPS 下发送 Cookie
- 防止 HTTP 明文传输时被窃听
3. SameSite
Set-Cookie: SESSIONID=xxx; SameSite=Strict
Set-Cookie: SESSIONID=xxx; SameSite=Lax ← 默认值
Set-Cookie: SESSIONID=xxx; SameSite=None; Secure| 值 | 行为 |
|---|---|
| Strict | 不随跨站请求发送,但同站不同源的请求仍可发送 |
| Lax | 同站请求发送;跨站时主要只在顶层、安全方法导航中发送(浏览器默认策略有兼容细节) |
| None | 允许跨站发送;现代浏览器要求同时设置 Secure |
防 CSRF:
SameSite=Lax通常会阻止跨站子资源请求和跨站 POST 携带 Cookie,但浏览器默认值及短时兼容策略可能存在差异- Lax 仍允许部分顶层安全方法导航携带 Cookie,因此服务端不能用 GET 做状态修改,敏感操作还应结合 CSRF Token 和 Origin 校验
4. Domain 与 Path
Set-Cookie: name=value; Domain=example.com; Path=/apiDomain:省略时是 host-only Cookie,仅发给设置它的主机;设为example.com时也可发送给其子域。现代规范会忽略开头的点,.example.com与example.com无本质差异Path:省略时默认路径由设置 Cookie 的请求路径推导;它控制发送范围,不是安全访问控制边界
六、SessionStorage vs LocalStorage vs Cookie
| 维度 | SessionStorage | LocalStorage | Cookie |
|---|---|---|---|
| 存储大小 | 配额由浏览器和存储分区决定,常见为数 MiB | 配额由浏览器和存储分区决定,常见为数 MiB | 单个 Cookie 通常约 4 KiB,数量也有限制 |
| 生命周期 | 标签页关闭即清 | 永久(除非主动删) | 可设置过期时间 |
| 自动发送 | ❌ 不会自动发 | ❌ 不会自动发 | 仅在 Cookie 属性与请求上下文匹配时自动发送 |
| 作用域 | 同源且按顶层浏览上下文/标签页隔离 | 同源(还可能受存储分区影响) | 由 Domain/主机、Path、SameSite 等共同决定 |
| API | sessionStorage.setItem | localStorage.setItem | document.cookie |
| 跨标签页 | ❌ 不共享 | ✅ 共享 | ✅ 共享(根据 Domain/Path) |
Token 存储选择:
- 不推荐 localStorage(XSS 风险)
- 推荐 httpOnly Cookie(防 XSS + 配合 SameSite 防 CSRF)
- SPA 可以考虑内存或 sessionStorage,但后者仍暴露给同源 JavaScript;选择取决于刷新恢复、XSS、CSRF、SSO 和后端部署约束
七、SSO 单点登录
SSO(Single Sign-On):一次登录,多系统访问
常见方案:
1. OAuth 2.0:授权框架,可实现 SSO
2. SAML:基于 XML 的 SSO 标准(企业级常用)
3. CAS:Central Authentication Service(耶鲁大学开源)
4. 自建 SSO:基于 JWT + 统一登录中心
核心思想:
1. 用户访问系统 A,发现未登录,跳转到认证中心
2. 认证中心验证用户,颁发 Token
3. 携带 Token 跳回系统 A
4. 系统 A 用 Token 向认证中心换取用户信息
5. 后续访问系统 B 时,已登录状态从认证中心同步八、面试高频问答
Q1: Cookie 和 Session 的区别?
答:
- Cookie:存储在客户端(浏览器),是服务器发送到浏览器并保存的小数据,每次请求会自动带上
- Session:存储在服务端,是服务器为每个用户维护的会话状态,通过 Session ID(通常是 Cookie)关联
关系:Session 通过 Cookie 中的 Session ID 来识别用户。Cookie 是机制,Session 是数据。
Q2: Token 和 Session 的区别?
答:
- Session:服务端存储会话状态(有状态),客户端只存 Session ID
- Token:服务端不存储会话状态(无状态),客户端持有 Token(含信息或引用)
Token 优势:水平扩展容易,跨域友好 Session 优势:服务端可主动撤销会话
现代方案:JWT + Refresh Token,结合两者优点。
Q3: JWT 的 Token 放在哪里?
答:
方案 1:Authorization Header(推荐)
fetch('/api', { headers: { Authorization: `Bearer ${token}` } })方案 2:httpOnly Cookie
- 防 XSS 窃取
- 配合 SameSite=Strict 防 CSRF
方案 3:localStorage
- ⚠️ 不推荐:XSS 可读所有 localStorage
- 仅在内部应用、可信环境可用
方案 4:内存
- 令牌不持久化且刷新页面即丢,可缩短泄露窗口,但页面发生 XSS 时仍可能被读取或被恶意脚本直接调用接口
- 是否配合 SessionStorage 取决于是否接受持久化令牌带来的 XSS 风险,并非固定搭配
Q4: 什么是 CSRF?如何防御?
答:CSRF(Cross-Site Request Forgery,跨站请求伪造):攻击者诱导用户在已登录的网站上执行非本意的操作。
防御方案:
- SameSite Cookie:设为 Strict/Lax,阻止跨站发送 Cookie
- CSRF Token:服务器生成 token,前端在请求中带上(攻击者无法获取)
- 验证 Origin / Referer:服务器检查请求来源
- 双重 Cookie:客户端写 Cookie,前端读后放在 Header
- 验证码:敏感操作加验证码
详见 03-前端安全。
Q5: HttpOnly 和 Secure 的区别?
答:
- HttpOnly:禁止 JS 访问 Cookie(防 XSS 窃取)
- Secure:只在 HTTPS 下发送 Cookie(防明文窃听)
两者正交,常一起设置: Set-Cookie: SESSIONID=xxx; HttpOnly; Secure
十、JWT 存哪里更安全?
一、几种常见存储方式对比
1️⃣ localStorage / sessionStorage(不推荐存敏感 JWT)
- ✅ 优点:API 简单、跨标签页共享(localStorage)
- ❌ 缺点:任何 JS 都能读取 → 一旦页面被 XSS,整个 JWT 泄露
- ❌ localStorage 不会过期(除非手动清理)
2️⃣ 普通 Cookie(不加 HttpOnly)
- ✅ 优点:浏览器自动管理,可设置过期时间
- ❌ 缺点:JS 也能读取 → 同样 XSS 可盗取
3️⃣ ✅ HttpOnly Cookie(推荐)
- ✅ 优点:页面脚本无法通过
document.cookie读取该 Cookie,可降低凭证被 XSS 直接窃取的风险;这不代表 HttpOnly 能防止 XSS 执行操作 - ✅ 浏览器在 Cookie 条件匹配时自动随请求发送,通常无需手动加 Authorization 头
- ❌ 缺点:有 CSRF 风险(攻击者伪造请求时 Cookie 会自动发送),需要额外防御(SameSite、CSRF Token)
Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600二、但注意:HttpOnly Cookie 也不是无敌
⚠️ CSRF 风险
攻击者虽然读不到 Cookie,但可以让浏览器自动带上(用户登录了银行网站,访问了攻击者的网站,攻击者用 <img src="https://bank.com/transfer?to=xxx"> 触发自动请求)。
三、进阶更安全方案(企业级常用)
推荐架构:双 Token 模式
Access Token(短期,存内存):
- 存于内存(Vuex/Pinia / 组件 state)
- 有效期短(如 15 分钟)
- 每次请求手动加 Authorization 头
Refresh Token(长期,存 HttpOnly Cookie):
- 仅用于刷新 Access Token
- 有效期长(如 7 天)
- HttpOnly + Secure + SameSite=Strict好处:
- ⚠️ XSS 仍能读取内存中的 Access Token,或直接以当前用户身份调用接口;短有效期只能缩短泄露窗口
- ✅ Access Token 过期后,用 Refresh Token 静默换新
- ⚠️ 刷新端点仍要校验 SameSite、CSRF Token 或 Origin;若响应可被攻击者页面读取、令牌可被滥用或刷新过程有设计缺陷,依然存在风险
四、总结一句话(你面试可以直接说)
我不会把 JWT 放 localStorage(XSS 即可盗取)。生产环境我会用 HttpOnly Cookie + SameSite + CSRF Token,或者进阶的双 Token 模式(Access 存内存,Refresh 存 HttpOnly Cookie)。
十一、关联文档
- 01-HTTPS 与 TLS 握手
- 03-前端安全(XSS、CSRF、CSP)
- 04-网络基础