跳到正文

认证与鉴权 ​

一句话:认证回答「你是谁」,鉴权回答「你能做什么」;OAuth2 解决授权委托,OIDC 在它上面补身份层,JWT 只是一种令牌格式。

适用:OAuth 2.1 草案、OpenID Connect 1.0、RFC 7636(PKCE)、RFC 7517(JWKS);JWT 校验以所用身份提供方文档为准。

为什么需要 ​

单体应用可以用 Session 在服务端保存登录态。服务拆多、端变多之后,每个服务都去共享 Session 会把认证中心变成强依赖,跨域和移动端也不方便。

另一种做法是把身份信息放进签名令牌,让每个服务本地验签。它换来无状态和横向扩展,代价是令牌签发后难以撤销、密钥和声明校验一旦写错就会全线失守。

核心机制 ​

先分清四个概念 ​

  • 认证(Authentication):确认主体身份,例如登录。
  • 鉴权(Authorization):判断主体能否执行某个操作。
  • OAuth2:授权框架,让第三方在用户同意下获得有限权限,不规定「用户是谁」。
  • OIDC:构建在 OAuth2 上的身份层,用 ID Token 告诉客户端「用户是谁」。

把 OAuth2 当登录协议是常见错误:拿到 access token 只说明客户端被授权,不等于完成了用户认证。需要登录态时用 OIDC。

JWT 的结构与校验清单 ​

JWT 是 Header.Payload.Signature 三段 Base64URL。Header 里有 alg 和 kid,Payload 是声明,Signature 保护前两段。

Payload 只是编码,不是加密,任何人都能解出内容。不要放密码、身份证号、完整手机号等敏感信息。

资源服务校验一个 JWT,至少要按顺序做:

  1. 解析 Header,算法必须在服务端白名单内(如 RS256/ES256),拒绝 none。
  2. 用 kid 从可信 JWKS 取公钥;不要信任 token 里的 jku/x5u 指向的任意 URL。
  3. 验签。
  4. 校验 iss 等于预期签发者。
  5. 校验 aud 包含当前服务自己的 audience;不能只验签不验受众,否则给 A 服务的令牌可以拿去调 B 服务。
  6. 校验 exp、nbf、iat,允许少量时钟偏差(常见 60 秒)。
  7. 需要时校验 scope、azp、nonce(OIDC ID Token)。

OAuth2 的授权码模式与 PKCE ​

授权码模式:客户端把用户导向授权服务器,用户登录并同意后,授权服务器回调一个一次性 code;客户端后端用 code + client_secret 换 token。

公共客户端(SPA、移动 App)没有能力安全保存 client_secret,必须使用 PKCE:

text
1. 客户端生成随机 code_verifier(43–128 字符)
2. code_challenge = BASE64URL(SHA256(code_verifier))
3. 授权请求带 code_challenge 和 code_challenge_method=S256
4. 换 token 时带上原始 code_verifier
5. 授权服务器重新计算并比对

PKCE 防的是授权码被截获后直接兑换。OAuth 2.1 已移除隐式模式和密码模式,授权码流程要求 PKCE;公共客户端没有 PKCE 不应上线。

客户端凭证模式用于服务间调用,没有用户参与;不要把它当成用户登录。

OIDC:身份层补在哪里 ​

OIDC 在 OAuth2 上增加:

  • scope=openid 时返回 ID Token(JWT),包含 sub、iss、aud、exp、iat、nonce 等。
  • /.well-known/openid-configuration 发现文档,给出 authorization_endpoint、token_endpoint、jwks_uri。
  • jwks_uri 提供公钥集合,按 kid 轮换。
  • UserInfo 端点返回用户资料。

ID Token 是给客户端确认登录身份的,不是用来调用资源服务的;调用 API 用 access token。两者受众不同,校验规则也不同。

SSO:CAS 与 OIDC 的差别 ​

CAS 的流程是:用户访问应用 A,未登录被重定向到 SSO;SSO 登录后发 TGT Cookie 和一次性 ST;应用 A 后端拿 ST 去 SSO 校验,通过后建立自己的局部会话。再访问应用 B 时,SSO 已有 TGT,直接发新 ST,不用重新登录。

OIDC SSO 则是统一 IdP 签发 ID Token/access token,各应用本地验签或调 UserInfo。前者需要后端回源校验,后者可以无状态,但撤销仍然要靠短 TTL 或中心化检查。

AK/SK 与 RBAC ​

AK/SK 用于服务间或开放平台:请求方用 Secret Key 对「方法 + 路径 + 时间戳 + 请求体摘要」做 HMAC-SHA256,服务端用同一套规则重算并比对。时间戳窗口(常见 ±5 分钟)防重放,nonce 缓存防同一请求重复使用。

RBAC 是用户 → 角色 → 权限。权限判断应该放在服务端,不能只靠网关;网关能挡住大部分入口,但内部调用和异步任务也要校验。

令牌安全 ​

  • Access Token 短 TTL(如 15 分钟),Refresh Token 长 TTL(如 7 天),只放 HttpOnly + Secure + SameSite Cookie。
  • Refresh Token Rotation:每次续期都换新 RT,旧 RT 立即失效;同一家族旧 RT 被重复使用,判定为泄露,整个家族失效。
  • 注销:短 TTL + Redis 黑名单存 jti;或者用不透明 token + 内省接口,撤销更直接。
  • 常见攻击:alg=none、算法混淆(把 RS256 公钥当 HS256 密钥)、弱 HS256 密钥、伪造 kid/jku。防御核心是算法白名单和固定密钥来源。

最小上手 ​

发现 OIDC 配置并查看 JWKS:

bash
curl -s https://issuer.example.com/.well-known/openid-configuration \
  | jq '{issuer, authorization_endpoint, token_endpoint, jwks_uri}'
curl -s https://issuer.example.com/.well-known/jwks.json \
  | jq '.keys[] | {kid, kty, alg, use}'

资源服务校验伪代码(省略错误处理):

text
h = decode_header(token)
assert h.alg in ["RS256", "ES256"]
key = jwks.get(h.kid)              # 未知 kid 时刷新一次 JWKS,限频
assert verify_signature(token, key)
c = decode_claims(token)
assert c.iss == EXPECTED_ISSUER
assert MY_AUDIENCE in c.aud
assert c.exp > now - 60
assert c.nbf <= now + 60

生产动作 ​

  • JWKS 缓存:缓存 5–15 分钟;遇到未知 kid 才刷新,刷新要限频,避免被伪造 token 打成放大攻击。
  • 受众隔离:每个服务有自己的 aud;不要所有服务共用同一个 audience。
  • 时钟:允许 60 秒偏差;服务器开启 NTP,否则 exp 校验会随机失败。
  • 撤销:高安全场景用不透明 token + introspection,或者短 TTL + jti 黑名单;不要指望纯 JWT 立即失效。
  • Cookie:HttpOnly; Secure; SameSite=Lax;状态变更接口加 CSRF Token 或校验 Origin。
  • AK/SK:Secret 不落日志、不放 URL;时间戳窗口 + nonce 防重放;密钥支持轮换和双密钥过渡。
  • 排障:401 先看 iss/aud/exp 和 JWKS 的 kid 是否匹配;403 看角色和 scope,不要把所有鉴权失败都归因于 token。

边界 ​

  • JWT 不是加密容器,Payload 对持有者可见;敏感数据不要放进去。
  • OAuth2 不是认证协议;只接 OAuth2 不接 OIDC,拿不到可信的用户身份。
  • 本地验签无法立即撤销;强撤销需求要用中心化校验,代价是每次请求多一次依赖。
  • 不要把 client_secret 放进 SPA 或移动端;公共客户端用 PKCE,不用 secret。
  • SSO 只解决登录,不解决细粒度授权;RBAC/ABAC 仍要在业务侧做。

讲清楚 ​

认证和鉴权要分开。用户登录用 OIDC,底层走 OAuth2 授权码 + PKCE;OAuth2 本身只解决授权委托,不告诉你用户是谁。JWT 只是令牌格式,资源服务验它要按顺序做:算法白名单、按 kid 从可信 JWKS 取公钥、验签、校验 iss 和 aud、校验 exp/nbf。公共客户端没有 secret,必须用 PKCE。JWT 签发后难以撤销,所以 access token 要短、refresh token 要轮换,紧急封禁靠 jti 黑名单或中心化内省。服务间调用用 AK/SK 签名加时间戳防重放,权限判断放在服务端而不是只放网关。

把一门技术讲透到既懂也会用