身份认证是安全体系的第一道关卡。从传统的用户名密码到现代 OAuth 2.0 / OIDC 协议,再到多因素认证和零信任架构,身份认证技术经历了深刻的演进。本文系统讲解现代身份认证的协议原理与安全实践。
1. 认证 vs 授权
| 概念 | 定义 | 类比 |
|---|---|---|
| 认证(Authentication) | 验证"你是谁" | 出示身份证 |
| 授权(Authorization) | 决定"你能做什么" | 门禁卡权限 |
认证 ──► 登录成功 ──► 获得身份凭证(Token/Session)
│
授权 ◄── 访问资源 ◄── 校验凭证权限
2. OAuth 2.0 授权框架
2.1 为什么需要 OAuth
场景:美团 App 需要访问你的微信头像
❌ 危险方案:
用户把微信密码给美团 → 美团登录微信获取头像
问题:美团可以删好友、发朋友圈、转账...
✅ OAuth 方案:
用户同意 → 微信给美团一枚"只能读头像"的令牌
美团用令牌获取头像,但无法做其他操作
2.2 四种授权模式
| 模式 | 适用场景 | 安全等级 |
|---|---|---|
| Authorization Code | 服务端 Web 应用 | ★★★★★ 最高 |
| PKCE | 移动/原生应用(Code 的扩展) | ★★★★★ |
| Implicit | 纯前端应用(已废弃不推荐) | ★★ |
| Password | 受信任的第一方应用(即将废弃) | ★★★ |
| Client Credentials | 服务间调用 | ★★★★ |
2.3 Authorization Code 流程
1. 用户浏览器 ──► 授权服务器
GET /authorize?response_type=code
&client_id=meituan_app
&redirect_uri=https://meituan.com/callback
&scope=profile
&state=xyz123
2. 用户在微信登录、授权
3. 微信 ──► 回调美团
HTTP/1.1 302 Found
Location: https://meituan.com/callback?code=abc123&state=xyz123
4. 美团后端 ──► 微信令牌端点
POST /token
grant_type=authorization_code
&code=abc123
&redirect_uri=https://meituan.com/callback
&client_id=meituan_app
&client_secret=xxx
5. 微信返回:
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"scope": "profile"
}
2.4 PKCE(Proof Key for Code Exchange)
防止授权码拦截攻击(移动应用):
客户端生成:
code_verifier = 随机字符串(43-128字符)
code_challenge = BASE64URL(SHA256(code_verifier))
步骤1 授权请求:
GET /authorize?...&code_challenge=xxx&code_challenge_method=S256
步骤4 令牌请求:
POST /token
&code=abc123
&code_verifier=原始随机字符串 ← 证明是同一客户端
3. OpenID Connect(OIDC)
3.1 OAuth 2.0 + 身份层
OAuth 2.0:授权框架(Access Token 访问资源)
+
OpenID Connect:身份认证层(ID Token 传递身份信息)
=
现代身份认证标准
3.2 ID Token(JWT 格式)
{
"iss": "https://auth.example.com",
"sub": "user_12345",
"aud": "my_client_app",
"exp": 1704067200,
"iat": 1704063600,
"auth_time": 1704063000,
"nonce": "n-0S6_WzA2Mj",
"name": "张三",
"email": "zhangsan@example.com",
"picture": "https://example.com/avatar.jpg"
}
3.3 用户信息端点
GET /userinfo
Authorization: Bearer <access_token>
Response:
{
"sub": "user_12345",
"name": "张三",
"email": "zhangsan@example.com",
"email_verified": true
}
4. JWT 安全设计
4.1 JWT 结构
base64(header).base64(payload).signature
Header: {"alg":"RS256","typ":"JWT"}
Payload: {"sub":"user123","exp":1704067200,...}
Signature: HMAC_SHA256(base64url(header) + "." + base64url(payload), secret)
4.2 安全要点
| 要点 | 建议 | 错误示例 |
|---|---|---|
| 算法 | 使用 RS256/ES256(非对称) | HS256 共享密钥泄露风险 |
| 密钥 | 256+ bit 随机密钥 | 弱密钥 secret123 |
| 过期 | Access Token 15分钟 | 永不过期 |
| 敏感信息 | Token 中不存敏感数据 | Token 中包含密码 |
| 传输 | HTTPS only | HTTP 传输 |
| 存储 | 内存或 httpOnly Cookie | localStorage(XSS 风险) |
| 验证 | 服务端严格验证签名 | 信任客户端传来的 Token |
4.3 JWT vs Session
| 维度 | JWT | Session |
|---|---|---|
| 存储 | 客户端(Cookie/Header) | 服务端(Redis/File/DB) |
| 扩展性 | 无状态,天然分布式 | 需要 Session 共享 |
| 撤销 | 困难(需黑名单/短过期) | 随时删除 |
| 大小 | 较大(含签名) | 小(仅 Session ID) |
| 信息量 | 可含用户数据 | 仅 ID,数据服务端查 |
| 适用 | 微服务/API | 传统 Web |
推荐混合方案:短过期 JWT + Redis 黑名单(用于撤销)
5. 会话管理安全
5.1 Cookie 安全属性
Set-Cookie: sessionid=xxx;
HttpOnly; // JavaScript 无法读取,防 XSS
Secure; // 仅 HTTPS 传输
SameSite=Lax; // CSRF 防护
Path=/; // 限制路径
Domain=.example.com; // 限制域名
Max-Age=3600; // 过期时间
5.2 Session 安全策略
// Spring Session 安全配置
@Configuration
public class SessionConfig {
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
// session 固定攻击防护
http.sessionManagement(session -> session
.sessionFixation().migrateSession() // 登录后更换 Session ID
.maximumSessions(1) // 单设备登录
.maxSessionsPreventsLogin(false) // 踢掉旧会话
.expiredUrl("/login?expired")
);
// 会话超时
server.servlet.session.timeout = 30m
5.3 并发会话控制
// 实现 SessionRegistry 控制并发
@Autowired
private SessionRegistry sessionRegistry;
public void listActiveSessions(String username) {
List<Object> principals = sessionRegistry.getAllPrincipals();
for (Object principal : principals) {
List<SessionInformation> sessions =
sessionRegistry.getAllSessions(principal, false);
// 可强制过期指定会话
sessions.get(0).expireNow();
}
}
6. 单点登录(SSO)
6.1 基于 OIDC 的 SSO
┌──────────┐ ┌──────────┐ ┌──────────┐
│ App A │ │ App B │ │ IDP │
│ (SP) │ │ (SP) │ │(Keycloak)│
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
登录 │──► 重定向到 IDP │ │
│ 携带 redirect_uri│ │
│────────────────────│───────────────────►│
│ │ │
│◄───────────────────│────────────────────│
│ 返回 code,App A │ │
│ 换取 token │ │
│ │ │
访问 App B │──► 重定向到 IDP │
│ │ (已登录,自动) │
│ │◄───────────────────│
│ │ 直接返回 code │
6.2 基于 SAML 2.0 的企业 SSO
SAML 流程(企业常用,如 AD/Azure AD):
1. 用户访问企业应用(SP)
2. SP 生成 SAML Request,重定向到 IdP
3. 用户在 IdP(如 Azure AD)认证
4. IdP 生成 SAML Assertion(包含用户属性和签名)
5. POST 回 SP
6. SP 验证签名,创建本地会话
7. 多因素认证(MFA)
7.1 因素类型
| 类型 | 示例 | 安全性 |
|---|---|---|
| 所知 | 密码、PIN | 低 |
| 所有 | 手机、硬件密钥 | 中 |
| 所是 | 指纹、面容 | 高 |
7.2 TOTP(Time-based One-Time Password)
// Google Authenticator 标准
// 基于 HOTP(RFC 4226) 扩展
// 密钥:base32 编码的共享密钥
String secret = Base32.encode(randomBytes(20));
// 生成二维码供用户扫描
String otpAuth = String.format(
"otpauth://totp/Example:user@example.com?secret=%s&issuer=Example",
secret
);
// 验证用户输入
long counter = System.currentTimeMillis() / 1000 / 30; // 30 秒窗口
int expectedCode = generateTOTP(secret, counter);
boolean valid = expectedCode == userInputCode;
// 考虑时间漂移,通常校验 counter-1, counter, counter+1
7.3 FIDO2 / WebAuthn(passkeys)
// 注册安全密钥
const credential = await navigator.credentials.create({
publicKey: {
challenge: Uint8Array.from(serverChallenge, c => c.charCodeAt(0)),
rp: { name: "Example Corp", id: "example.com" },
user: { id: userId, name: "user@example.com", displayName: "User" },
pubKeyCredParams: [{ alg: -7, type: "public-key" }], // ES256
authenticatorSelection: { authenticatorAttachment: "platform" }
}
});
// 支持生物识别(指纹/面容)、硬件密钥(YubiKey)、移动设备
// 无密码登录,抗钓鱼
8. 密码管理
8.1 密码存储
// ✅ 现代推荐:Argon2id
import de.mkammerer.argon2.Argon2;
import de.mkammerer.argon2.Argon2Factory;
Argon2 argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id);
String hash = argon2.hash(3, 65536, 1, password.toCharArray());
boolean valid = argon2.verify(hash, password.toCharArray());
// 参数:iterations=3, memory=64MB, parallelism=1
// ✅ 备选:BCrypt(Java 内置)
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
String hash = encoder.encode(password);
8.2 密码策略
# 现代密码策略(NIST 800-63B 建议)
password-policy:
min-length: 8 # 最小长度 8
max-length: 64 # 最大长度 64(防止 DoS)
blocklist: true # 检查常见密码字典
complexity: false # 不要求大小写+数字+符号(增加用户痛苦,不增加安全)
rotation: false # 不强制定期更换(除非泄露)
mfa-required: true # 敏感操作要求 MFA
breach-check: true # 检查 Have I Been Pwned
9. 总结
现代身份认证体系的分层设计:
┌─────────────────────────────────────────────────────────┐
│ 认证方式选择 │
├─────────────────────────────────────────────────────────┤
│ B2C 应用 │ OAuth 2.0 + OIDC │
│ B2B 应用 │ SAML 2.0 │
│ 内部系统 │ LDAP/AD + Kerberos │
│ 微服务 │ mTLS + JWT │
├─────────────────────────────────────────────────────────┤
│ 安全等级 │
├─────────────────────────────────────────────────────────┤
│ 基础 │ 密码 + Session/Cookie │
│ 增强 │ 密码 + TOTP(手机验证码) │
│ 高安全 │ 密码 + FIDO2/WebAuthn(无密码) │
│ 零信任 │ 持续认证 + 上下文感知 + 动态授权 │
└─────────────────────────────────────────────────────────┘
身份认证领域正从"密码为中心"向"无密码(passwordless)“演进,而 OAuth 2.0 / OIDC 已经成为互联网身份互联的事实标准。正确实现这些协议,是构建安全、可扩展认证系统的基础。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。