技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTBR

前端登录,这一篇就够了

所属馆藏
Browser
文件格式
Markdown
原始路径
Browser/21-前端登录,这一篇就够了
本文目录12 个章节
  1. 一、HTTP 无状态,但不代表每次请求都重新建连接
  2. 二、Cookie + Session 登录
  3. 三、Token 登录
  4. 四、JWT 到底是什么
  5. 五、SSO 单点登录
  6. 六、OAuth 第三方登录与 OIDC
  7. 七、密码和登录接口的安全底线
  8. 八、Passkey 和多因素认证
  9. 九、常见错误
  10. 十、方案选择
  11. 总结
  12. 参考资料

前端登录,这一篇就够了

Category(分类): Browser, Security, Authentication Status: 已更新

原文介绍 Cookie + Session、Token、JWT、SSO 和 OAuth 第三方登录,并配有登录流程图。本文保留这些主线和流程图,但修正“HTTP 每次都重新建立连接”“Cookie + Session 无法避免 CSRF”“Token 放在哪里都安全”“JWT 的 HMAC 使用私钥”等错误,补充 HttpOnly Cookie、刷新令牌、OAuth 2.0 + PKCE、OpenID Connect、Passkey 和现代会话安全。

登录系统要同时解决:

  1. 认证(Authentication):确认账号对应的主体是谁;
  2. 会话(Session):在后续请求中识别主体,并管理过期、撤销和登出;
  3. 授权(Authorization):主体能访问哪些资源,详见前端如何来做权限管理

一、HTTP 无状态,但不代表每次请求都重新建连接

HTTP 的请求本身通常不携带服务器端会话状态,服务端需要借助 Cookie、Session、Token 或其他机制识别用户。原文把“无状态”解释成每次请求都要重新建立并断开连接,这不准确:HTTP/1.1 Keep-Alive、HTTP/2 多路复用和 HTTP/3/QUIC 都可以复用连接。

无状态指的是协议不会自动替应用保存“这是哪个用户”的业务状态,而不是网络连接一定短命。

二、Cookie + Session 登录

Cookie 是浏览器保存并按域、路径、过期时间和安全属性自动发送的一小段数据。现代浏览器还会考虑 SameSite、第三方 Cookie 策略、分区存储和用户隐私设置。

1. 首次登录流程

Cookie + Session 首次登录流程

  1. 用户通过 HTTPS 提交账号和密码;
  2. 服务端校验密码哈希、账号状态、验证码和登录风险;
  3. 服务端创建一个高熵、不可预测的 Session ID,并把会话数据存到 Redis、数据库或其他共享存储;
  4. 服务端通过 Set-Cookie 把 Session ID 写入浏览器;
  5. 浏览器后续向匹配域名和路径发送请求时自动带上 Cookie;
  6. 服务端根据 Session ID 查找会话,再执行授权判断。

一个偏安全的 Cookie 响应示例:

Set-Cookie: __Host-session=RANDOM_HIGH_ENTROPY_ID; Path=/; Max-Age=1800; HttpOnly; Secure; SameSite=Lax

__Host- 前缀要求使用 SecurePath=/ 且不能设置 Domain,可以减少错误的跨子域覆盖。实际属性要结合是否跨站、是否 iframe、是否需要跨子域等业务场景决定。

2. 后续请求

Cookie + Session 后续请求流程

  1. 浏览器访问受保护资源;
  2. 浏览器按 Cookie 规则自动发送 Session ID;
  3. 服务端查找并验证会话是否存在、是否过期、是否被撤销;
  4. 服务端根据用户、资源、动作和租户执行授权;
  5. 允许则返回资源,不允许则返回 401403

3. Session 存放在哪里?

可以存放在:

  • 单机内存:简单,但重启丢失且不适合多实例;
  • Redis 等共享缓存:适合多实例和快速过期;
  • 数据库:便于持久化、审计和查询,但要关注访问成本;
  • 加密/签名 Cookie:把少量会话状态放在客户端,但仍需要密钥轮换、大小控制、撤销策略和隐私评估。

“服务器存放大量 Session 就一定压力过大”也不准确。通过共享存储、过期淘汰、合理 TTL 和横向扩展,Session 仍然是大量生产系统的常见选择。集群不一定要把 Session 复制到每台应用机器,通常使用共享 Session Store 或粘性会话等方案。

4. Session 安全实践

  • 登录成功后轮换 Session ID,防止 Session Fixation;
  • Session ID 使用密码学安全随机数,不能使用递增 ID、用户 ID 或时间戳;
  • 设置空闲超时和绝对过期时间;
  • 登出时服务端销毁或撤销会话,同时清除 Cookie;
  • 修改密码、提升权限和高风险操作时重新认证或轮换会话;
  • 会话存储不保存明文密码;
  • 监控异常 IP、设备、并发会话和重放行为,但不要只依赖 IP 作为身份;
  • 通过 HTTPS/WSS 保护传输,Cookie 设置 Secure
  • 尽量不设置过宽的 Domain,避免低信任子域影响高权限会话。

原文说“Session ID 存放在 Cookie 中,所以无法避免 CSRF 攻击”容易误解。Cookie 自动发送确实会带来 CSRF 风险,但可以通过以下方式降低风险:

  • SameSite=LaxStrict,按跨站业务需要选择;
  • 对有副作用的请求使用 CSRF Token 或双重提交 Cookie;
  • 校验 Origin/Referer,不能只依赖它们;
  • 使用合适的 CORS allowlist,但不要把 CORS 当成 CSRF 防护;
  • 重要操作进行重新认证或二次确认。

HttpOnly 主要防止 JavaScript 读取 Cookie,不能阻止 XSS 代码借助当前页面发起已认证请求。因此仍然需要 CSP、输出编码、输入校验和安全依赖管理。

三、Token 登录

Token 是服务端发给客户端、用于后续证明会话或授权范围的凭证。它可以是不可读的随机 opaque token,也可以是 JWT。Token 的格式本身不决定安全性。

1. Token 流程

Token 首次登录流程

  1. 用户提交账号和密码;
  2. 服务端验证身份并签发短期 Access Token,必要时同时签发 Refresh Token;
  3. 客户端在后续请求中通过 Authorization: Bearer <token> 或 Cookie 携带凭证;
  4. 服务端验证令牌、过期时间、受众、签发者、权限和资源访问范围。

Token 后续请求流程

2. Token 的优缺点

优点:

  • 适合 API、移动端、微服务和跨服务调用;
  • Bearer Token 放在 Authorization 头时,不会像 Cookie 一样自动附加到所有匹配请求;
  • JWT 可以在验证签名后本地读取声明,减少部分查询。

限制:

  • 可撤销、登出、权限即时生效和刷新轮换仍然需要服务端状态或版本机制;
  • Token 泄露后,攻击者可能在有效期内冒充用户;
  • 令牌放在 localStorage 可被 XSS 读取,放在 Cookie 则要处理 CSRF;
  • “服务端完全不保存 Token,所以没有压力”不是绝对结论:服务端仍可能保存 Refresh Token、撤销列表、密钥、会话版本、审计和风控状态。

3. 浏览器中的 Token 保存位置

没有一个位置在所有场景都安全:

位置优点风险/限制
内存页面刷新后消失,降低持久泄露窗口刷新后需恢复,需配合刷新机制
localStorage使用简单、刷新后保留XSS 可直接读取,不适合高价值长期凭证
HttpOnly CookieJavaScript 不能读取,适合服务端会话/BFF自动发送,需要 CSRF、SameSite 和域策略
IndexedDB可存结构化数据仍受同源脚本访问,不能抵御 XSS

对浏览器应用,常见的稳妥路线是服务端会话 + HttpOnly Cookie,或 BFF(Backend for Frontend)代浏览器管理 OAuth Token。若业务确实需要浏览器持有 Access Token,应缩短有效期、使用 Refresh Token Rotation、避免放入 URL,并完善 CSP、XSS 防护和撤销策略。

四、JWT 到底是什么

JWT(JSON Web Token)是一个紧凑、URL-safe 的声明传输格式。常见签名 JWT(JWS)由三段组成:

base64url(header).base64url(payload).base64url(signature)

原文中的 playload 应为 payload。JWT 的 Base64URL 编码不是加密,任何拿到 JWT 的人通常都能解码 header 和 payload;签名用于检测篡改,不能用来隐藏敏感信息。

1. Header、Payload、Signature

{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "iss": "https://id.example.com",
  "sub": "user-42",
  "aud": "api.example.com",
  "exp": 1735689600,
  "iat": 1735686000,
  "nbf": 1735686000,
  "jti": "unique-token-id",
  "scope": "article:read"
}
  • iss:签发者;
  • sub:主体标识;
  • aud:受众;
  • exp:过期时间;
  • iat:签发时间;
  • nbf:不早于此时间使用;
  • jti:令牌 ID,可用于重放检测或撤销;
  • 自定义 claims 要控制大小和敏感性。

2. HMAC 和非对称签名不能混淆

原文说“输入服务器端私钥、unsignedToken,使用 HMAC-SHA256”,这是错误的:

  • HS256 是 HMAC-SHA256,签发和验证使用同一个共享密钥;验证方也拥有生成合法 Token 的能力;
  • RS256PS256ES256 等非对称算法使用私钥签发、公钥验证;
  • 服务端必须配置允许的算法集合,不能盲目信任 Token header 中的 alg
  • JWT 验证时应检查签名、算法、issaud、时间 claims、权限范围和密钥版本。

3. 服务端验证示意

不要手写生产级 JWT 解析和签名验证。以 Node.js jose 为例:

import { jwtVerify } from 'jose'

const secret = new TextEncoder().encode(process.env.JWT_SECRET)

const verifyAccessToken = async token => {
  const result = await jwtVerify(token, secret, {
    algorithms: ['HS256'],
    issuer: 'https://id.example.com',
    audience: 'api.example.com'
  })

  return result.payload
}

如果使用 RS256/ES256,服务端通过受信任的 JWKS 获取公钥,并处理密钥轮换、缓存、kid 和失败回退。不要把 JWT 直接当作用户权限的永久事实:角色撤销、租户变化和高风险授权仍应查询服务端策略或使用版本号。

4. JWT 常见错误

  • 把 Base64URL 当加密;
  • 把 payload 中的 role: admin 当成无需服务端确认的授权;
  • 不检查 expissaud 和算法;
  • 接受 none 或非预期算法;
  • 把 Access Token 放在 URL、日志、Referer 或错误页面;
  • 使用一个永不轮换的弱密钥;
  • 把 JWT 当成“天然可撤销”的 Session;
  • 在浏览器端保存包含密码、身份证号等敏感数据的 claims。

五、SSO 单点登录

SSO(Single Sign-On)通常由一个身份提供商(IdP)为多个业务系统提供认证。它解决的是“多个系统共享登录体验”,不意味着所有系统共享同一个业务权限表。

1. 首次访问业务系统

SSO 首次访问流程

典型流程:

  1. 用户访问 app-a.example,发现没有本地会话;
  2. 业务系统将浏览器重定向到 IdP,并携带明确注册的 redirect_uristate,OIDC 通常还带 nonce
  3. 用户在 IdP 完成认证和授权;
  4. IdP 通过浏览器回调传回一次性授权码,而不是把长期 Access Token 放在 URL;
  5. 业务后端在服务端与 IdP 交换授权码,验证客户端、PKCE、签发者、受众和 nonce;
  6. 业务系统创建自己的本地会话,并重定向到原始页面。

state 用于防 CSRF 和关联请求,redirect_uri 必须精确匹配注册值,回调必须使用 HTTPS。不要把任意 return_uri 原样反射,避免开放重定向。

2. 继续访问同一业务系统

SSO 访问已登录业务系统

业务系统使用自己的会话 Cookie 识别用户。IdP 的 Cookie 一般不会被业务系统直接读取,跨域共享 Cookie 也不是 SSO 的正确实现方式。

3. 访问另一个业务系统

SSO 访问另一个业务系统

  1. 用户访问 app-b.example,没有 app-b 本地会话;
  2. app-b 把浏览器重定向到 IdP;
  3. IdP 发现用户已有 IdP 会话,因此不要求重复输入密码;
  4. IdP 为 app-b 生成新的、短期、一次性的授权码;
  5. app-b 后端交换授权码并创建自己的本地会话。

每个业务系统仍然要独立执行自己的授权。SSO 只证明身份,不自动授予所有业务权限。

4. 单点退出

原文中“认证中心遍历所有产品并调用退出 API”是一种早期自定义方案,不是所有 SSO 都采用。现代系统可以组合:

  • 本地 RP(业务系统)先销毁自己的会话;
  • 调用 IdP 的 RP-Initiated Logout 或撤销端点;
  • OIDC Back-Channel Logout 或 Front-Channel Logout(如果 IdP 和 RP 支持);
  • Refresh Token 撤销和会话版本失效;
  • 对高风险系统要求重新登录。

不同应用的本地 Cookie 不会因为删除 IdP Cookie 就自动消失,退出协议和实现能力需要在架构阶段明确。

六、OAuth 第三方登录与 OIDC

第三方授权概念示意

OAuth 2.0 是授权委托协议:它回答“客户端能否代表资源所有者访问某些资源”。如果应用要确认用户身份,应使用在 OAuth 之上的 OpenID Connect(OIDC),验证 id_token 的签名和 claims。

不要把“OAuth Access Token 一定就是用户登录凭证”写成通用结论;不同提供商的 Token、用户信息接口、scope、过期和撤销机制不同。

1. 现代 Web 应用推荐:Authorization Code + PKCE

浏览器/客户端                  IdP                 业务后端
     |                          |                     |
     |-- code_challenge ------->|                     |
     |<-- 登录/同意页面 ---------|                     |
     |<-- authorization code ----|                     |
     |-------------------------->|-- code + verifier ->|
     |                          |<-- tokens ----------|
     |<-- 业务会话/安全结果 ------|                     |

流程:

  1. 客户端生成高熵 code_verifier
  2. 使用 S256 计算 code_challenge
  3. 跳转 IdP 的授权端点,携带 client_idredirect_uriresponse_type=codescopestatecode_challenge
  4. 用户在 IdP 登录并同意授权;
  5. IdP 回调一次性授权码;
  6. 客户端或后端提交授权码和 code_verifier 到 Token Endpoint;
  7. 服务端验证 state、PKCE、redirect URI、client 身份和 Token claims;
  8. 对浏览器应用,优先建立 HttpOnly 本地会话,而不是把长期 Token 暴露给 JavaScript。

浏览器端生成 PKCE 的示意:

const toBase64Url = bytes => {
  let binary = ''
  for (const byte of bytes) binary += String.fromCharCode(byte)
  return btoa(binary)
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '')
}

const verifierBytes = crypto.getRandomValues(new Uint8Array(32))
const codeVerifier = toBase64Url(verifierBytes)
const digest = await crypto.subtle.digest(
  'SHA-256',
  new TextEncoder().encode(codeVerifier)
)
const codeChallenge = toBase64Url(new Uint8Array(digest))

代码示例展示协议步骤,真实项目应使用经过维护的 OAuth/OIDC 客户端库,并正确处理回调、错误、超时、重试、浏览器返回和多标签页。

2. 不推荐的隐式模式

早期 SPA 常见的 Implicit Flow 会把 Access Token 放在前端重定向结果中,容易通过浏览器历史、Referer、日志和脚本环境泄露。OAuth 2.0 Security Best Current Practice 推荐 Authorization Code,并使用 PKCE;不要因为旧文章仍列出 response_type=token 就在新项目中采用。

3. 以微信等提供商为例

微信等第三方登录流程示意

不同平台字段和接口地址不同,但常见流程仍是:

  1. 在开放平台注册应用,获得 client_id/appid 和客户端配置;
  2. 用户点击第三方登录,跳转提供商授权页面;
  3. 用户同意后回调一次性 code
  4. 服务端使用 code、客户端凭证(如果该客户端类型需要)和 redirect URI 换取 Token;
  5. 服务端通过提供商用户信息接口获取稳定的第三方主体 ID;
  6. 将第三方主体绑定到本站账号;
  7. 创建本站 Session 或本站 Token,并设置安全 Cookie;
  8. 后续业务权限由本站系统判断。

client_secret 不能放到浏览器。提供商具体是否需要 secret、scope 和用户信息接口,要以官方文档为准;不要把某个平台 2020 年的流程图当成所有 OAuth 提供商的统一协议。

七、密码和登录接口的安全底线

密码处理

  • 只通过 HTTPS 提交密码;
  • 服务端使用 Argon2id、bcrypt 或 scrypt 等专用密码哈希,设置合适成本和唯一 salt;
  • 永不保存明文密码、可逆加密密码或把密码放进 JWT;
  • 登录失败提示不要泄露账号是否存在;
  • 对登录、验证码、密码重置和 MFA 接口限速;
  • 密码重置链接使用短期、一次性、不可预测令牌;
  • 密码修改、邮箱变更和支付操作可以要求重新认证。

登录接口

POST /api/login HTTP/1.1
Content-Type: application/json

{"email":"ada@example.com","password":"..."}

成功后可以返回:

HTTP/1.1 204 No Content
Set-Cookie: __Host-session=...; Path=/; Max-Age=1800; HttpOnly; Secure; SameSite=Lax

不要把 Session ID、Access Token 或密码放在 URL 查询参数中。错误响应、日志、分析脚本和第三方监控也不应记录完整凭证。

认证和授权状态码

  • 401:没有有效认证,需要登录或刷新认证;
  • 403:已经识别主体,但不允许执行操作;
  • 对象不存在和无权限时是否统一返回 404,应根据防止资源枚举的策略决定;
  • 前端收到 401 不应无休止地刷新 Token,避免多个标签页形成刷新风暴。

八、Passkey 和多因素认证

密码不是唯一登录方式。Passkey/WebAuthn 使用公钥凭证和设备验证器,服务端保存公钥而不是私钥,能够抵抗许多钓鱼攻击。MFA、风险登录、设备绑定和恢复码可以降低密码泄露后的风险,但恢复流程本身也必须安全。

前端只负责调用浏览器 API 和展示状态,注册、断言验证、挑战随机数、Origin/RP ID 和凭证绑定仍由服务端完成。

九、常见错误

错误 1:把 Token 放到 localStorage 就“没有 CSRF”

它可能减少 Cookie 自动发送带来的 CSRF 面,但 XSS 可以读取并外传 Token。存储位置是风险权衡,不是绝对安全结论。

错误 2:认为 JWT 不需要服务端状态

JWT 的签名验证可以不查询会话,但撤销、Refresh Token、权限版本、密钥轮换、异常检测和审计都需要服务端能力。

错误 3:把 OAuth 当作认证协议

OAuth 的核心是授权委托,身份认证使用 OIDC 或提供商明确的身份接口,并验证 issuer、audience、nonce、签名和时间 claims。

错误 4:把第三方 Token 当本站 Session

第三方 Access Token 的受众和权限属于第三方资源服务器,不能直接当作本站登录态。应绑定第三方主体并创建本站会话。

错误 5:把客户端角色当作授权依据

前端可以隐藏按钮,但用户可以直接调用 API。服务端必须检查主体、动作、资源归属和租户边界。

如果服务端 Session 仍有效、Refresh Token 未撤销或其他设备仍在线,单纯清空本地 Cookie 不能完成完整登出。退出策略应覆盖本地会话、IdP 会话、刷新令牌和设备会话。

十、方案选择

场景常见方案重点
同域 Web 应用服务端 Session + HttpOnly CookieCSRF、会话轮换、过期和撤销
浏览器前后端分离BFF + HttpOnly Cookie,或短期 Access Token避免长期 Token 暴露,处理 CORS
移动端/桌面端Authorization Code + PKCE安全回调、刷新令牌轮换
企业多系统OIDC SSO每个业务系统独立会话和授权
第三方登录OAuth/OIDC 官方流程state、nonce、PKCE、精确 redirect URI
高价值操作Session/Token + MFA/Passkey重新认证、审计和风控

总结

  1. HTTP 无状态不等于每次请求都重新建立连接;
  2. Cookie + Session 仍是浏览器 Web 应用的可靠方案,集群可用共享 Session Store;
  3. Cookie 自动发送需要 CSRF 防护,HttpOnly 不能消除 XSS;
  4. Token 不等于 JWT,JWT 的 payload 不是加密内容,HS256 共享密钥和 RS/ES 非对称密钥不能混淆;
  5. JWT 验证必须检查签名、算法、issuer、audience、过期时间和业务授权;
  6. SSO 共享的是认证体验,每个业务系统仍要有自己的会话和授权;
  7. OAuth 是授权委托,登录身份场景优先使用 OIDC,浏览器应用使用 Authorization Code + PKCE;
  8. Passkey/WebAuthn 和 MFA 可以提升高价值账号的抗钓鱼能力;
  9. 凭证存储、撤销、轮换、登出和审计要结合威胁模型设计。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS