Skip to content

会话与认证

作者:青见春山
发表于:2026-07-29
字数统计:7000 字
预计阅读24分钟

涵盖 cookie、session、token、HttpOnly、SameSite、SessionStorage、JWT、OAuth 完整认证授权知识。

一、Cookie

1. 概念

Cookie 是浏览器为站点保存的一小块数据(单个 Cookie 通常约 4 KiB)。服务器可通过 Set-Cookie 设置,脚本也可写入非 HttpOnly Cookie。发送请求时,只有 Domain/主机、Path、Secure、SameSite、过期时间等条件匹配的 Cookie 才会自动放入请求头。

2. 基本用法

JavaScript
// 服务端设置(HTTP 响应头)
Set-Cookie: name=value; Expires=...; Max-Age=...; Path=/; Domain=.example.com; Secure; HttpOnly; SameSite=Lax
JavaScript
// 客户端查看 / 操作
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
}
属性含义
Expires / Max-Age过期时间。Max-Age 单位是秒
PathCookie 作用路径,默认当前路径
DomainCookie 作用域名,默认当前域名
Secure仅在 HTTPS 下发送
HttpOnlyJS 无法访问(防 XSS 窃取)
SameSite跨站发送限制(防 CSRF)
Plain
第一方 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 给客户端。

Plain
工作流程:
1. 用户登录 → 服务器验证 → 创建 Session 对象(内存/Redis/DB)→ 返回 Session ID
2. 浏览器收到 Set-Cookie: SESSIONID=xxx
3. 后续请求:浏览器自动带 Cookie: SESSIONID=xxx
4. 服务器根据 SESSIONID 找到对应的 Session 对象
5. 识别用户身份,返回相应内容

2. Session 存储

Plain
Session 数据存储在服务端,可以是:
- 内存(重启丢失,不适用于分布式)
- 数据库(MySQL、MongoDB)
- Redis / Memcached(高性能,主流选择)
- 文件系统

分布式 Session 问题:
- 多服务器需要共享 Session
- 解决方案:
  1. Session 复制(同步到所有服务器)
  2. Session 粘性(同一用户始终路由到同一服务器)
  3. Session 集中存储(Redis,所有服务器访问同一 Session 源)

3. Session 的缺点

Plain
1. 服务器开销:每个用户都要在服务端存储
2. 扩展性:分布式需要共享 Session
3. CSRF:默认浏览器会自动带上 Session Cookie,攻击者可利用
4. 跨域限制:默认同源策略限制,跨域访问受限

三、Token

1. 概念

Token 是无状态的认证方案,服务器不存储会话状态,而是把用户信息(或引用)编码到 Token 中,客户端每次请求带 Token。

Plain
工作流程:
1. 用户登录 → 服务器验证 → 生成 Token → 返回给客户端
2. 客户端存储(localStorage / cookie / memory)
3. 后续请求:在 Authorization 头中带 Token
4. 服务器验证 Token 签名/合法性 → 识别用户身份

2. JWT(JSON Web Token)

Plain
JWT 结构(点分隔三段):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

┌──────────┐ ┌─────────────────────┐ ┌──────────────┐
│ Header   │ │ Payload (Claims)    │ │ Signature    │
│ 算法+类型 │ │ 携带的数据           │ │ 签名          │
└──────────┘ └─────────────────────┘ └──────────────┘
JSON
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload(声明 Claims)

JSON
{
  // 标准声明(Registered Claims)
  "iss": "issuer",
  "sub": "subject",       // 用户 ID
  "aud": "audience",
  "exp": 1516239022,      // 过期时间
  "nbf": 1516239022,      // 生效时间
  "iat": 1516239022,      // 签发时间
  "jti": "token-id",      // JWT ID

  // 自定义声明
  "name": "张三",
  "role": "admin"
}

Signature

Plain
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

3. JWT 的使用

JavaScript
// 登录获取 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 机制

Plain
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服务端 SessionToken / JWT
存储位置浏览器数据在服务端,ID 常在客户端可放内存、Cookie 或 Web Storage;由方案决定
状态只是存储/传输机制服务端有状态JWT 可自包含,但系统仍可能维护撤销或用户状态
扩展性取决于携带的数据和后端设计多节点通常需共享或粘性会话自包含令牌可减少会话查询,但密钥轮换和撤销仍需设计
跨站/跨源受 Domain、SameSite、CORS 等约束取决于承载 Session ID 的方式取决于令牌存储、发送方式和 CORS,不是天然“跨域灵活”
CSRF自动随请求发送时需防护常借助 Cookie 标识,因此需防护取决于传输位置;放在 Cookie 中仍有 CSRF 风险
撤销客户端删除不等于服务端失效服务端可删除会话自包含 JWT 通常要短过期或配合撤销表;不透明 Token 可在服务端撤销
性能匹配的 Cookie 随请求发送通常要查服务端存储需要验签及业务校验,也可能查撤销表/用户状态
典型场景偏好、本地状态传统服务端应用前后端分离、SPA、微服务

五、HttpOnly 与 SameSite

1. HttpOnly

HTTP
Set-Cookie: SESSIONID=xxx; HttpOnly
  • JS(document.cookie)无法访问
  • 防止 XSS 攻击窃取 Session Cookie
  • 注意:HttpOnly 不能防 CSRF(CSRF 不需要读 Cookie)

2. Secure

HTTP
Set-Cookie: SESSIONID=xxx; Secure
  • 仅在 HTTPS 下发送 Cookie
  • 防止 HTTP 明文传输时被窃听

3. SameSite

HTTP
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

HTTP
Set-Cookie: name=value; Domain=example.com; Path=/api
  • Domain:省略时是 host-only Cookie,仅发给设置它的主机;设为 example.com 时也可发送给其子域。现代规范会忽略开头的点,.example.comexample.com 无本质差异
  • Path:省略时默认路径由设置 Cookie 的请求路径推导;它控制发送范围,不是安全访问控制边界
维度SessionStorageLocalStorageCookie
存储大小配额由浏览器和存储分区决定,常见为数 MiB配额由浏览器和存储分区决定,常见为数 MiB单个 Cookie 通常约 4 KiB,数量也有限制
生命周期标签页关闭即清永久(除非主动删)可设置过期时间
自动发送❌ 不会自动发❌ 不会自动发仅在 Cookie 属性与请求上下文匹配时自动发送
作用域同源且按顶层浏览上下文/标签页隔离同源(还可能受存储分区影响)由 Domain/主机、Path、SameSite 等共同决定
APIsessionStorage.setItemlocalStorage.setItemdocument.cookie
跨标签页❌ 不共享✅ 共享✅ 共享(根据 Domain/Path)

Token 存储选择

  • 不推荐 localStorage(XSS 风险)
  • 推荐 httpOnly Cookie(防 XSS + 配合 SameSite 防 CSRF)
  • SPA 可以考虑内存或 sessionStorage,但后者仍暴露给同源 JavaScript;选择取决于刷新恢复、XSS、CSRF、SSO 和后端部署约束

七、SSO 单点登录

Plain
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 时,已登录状态从认证中心同步

八、面试高频问答

  • 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(推荐)

JavaScript
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,跨站请求伪造):攻击者诱导用户在已登录的网站上执行非本意的操作。

防御方案

  1. SameSite Cookie:设为 Strict/Lax,阻止跨站发送 Cookie
  2. CSRF Token:服务器生成 token,前端在请求中带上(攻击者无法获取)
  3. 验证 Origin / Referer:服务器检查请求来源
  4. 双重 Cookie:客户端写 Cookie,前端读后放在 Header
  5. 验证码:敏感操作加验证码

详见 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 不会过期(除非手动清理)
  • ✅ 优点:浏览器自动管理,可设置过期时间
  • ❌ 缺点:JS 也能读取 → 同样 XSS 可盗取
  • ✅ 优点:页面脚本无法通过 document.cookie 读取该 Cookie,可降低凭证被 XSS 直接窃取的风险;这不代表 HttpOnly 能防止 XSS 执行操作
  • ✅ 浏览器在 Cookie 条件匹配时自动随请求发送,通常无需手动加 Authorization 头
  • ❌ 缺点:有 CSRF 风险(攻击者伪造请求时 Cookie 会自动发送),需要额外防御(SameSite、CSRF Token)
HTTP
Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600

⚠️ CSRF 风险

攻击者虽然读不到 Cookie,但可以让浏览器自动带上(用户登录了银行网站,访问了攻击者的网站,攻击者用 <img src="https://bank.com/transfer?to=xxx"> 触发自动请求)。

三、进阶更安全方案(企业级常用)

推荐架构:双 Token 模式

text
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)。

十一、关联文档