2、身份认证机制:基于密码的认证、Kerberos协议、OAuth 2.0与OpenID Connect

身份认证,说白了就是回答一个问题:「你凭什么说你是你?」

在分布式系统里,这个问题比单机环境复杂得多。用户可能从手机、浏览器、IoT设备同时发起请求,服务之间也要互相证明身份。我做了十几年安全架构,见过太多因为认证没做好而翻车的案例。今天咱们就把四种主流认证机制掰开揉碎了讲清楚。

2.1 基于密码的认证:最古老也最危险

密码认证是最基础的方式。用户输入用户名和密码,服务端验证。听起来简单,但坑特别多。

⚠️ 核心风险: 密码在传输和存储过程中极易泄露。

我个人习惯把密码存储分为三个等级:

  • 明文存储 —— 绝对禁止。我在项目中遇到过某初创公司直接把密码记在数据库里,结果被拖库后用户数据全裸奔。
  • 哈希存储 —— 常用做法。但别用MD5、SHA-1,它们已经被破解了。建议用bcrypt、scrypt或Argon2。
  • 加盐哈希 —— 这才是正确姿势。每个用户随机生成一个盐值,和密码拼接后再哈希。这样就算两个用户密码相同,存储的哈希值也不一样。
// 伪代码示例:加盐哈希
String salt = generateRandomSalt();  // 每个用户独立
String hash = bcrypt(salt + password);
store(salt, hash);
💡 避坑指南: 我曾经见过一个系统,盐值是固定的「salt123」。这跟没加盐有什么区别?盐值必须随机且每个用户不同。

传输过程也要加密。千万别用HTTP明文传密码。我建议至少用HTTPS,如果条件允许,前端先做一次哈希再传输,服务端再哈希一次。这叫「双重哈希」,能防止传输过程中被中间人截获原始密码。

2.2 Kerberos协议:企业级认证的经典方案

Kerberos是MIT开发的认证协议,在Windows域控和很多企业内部系统里广泛使用。它的核心思想是:不直接传输密码,而是用票据(Ticket)来证明身份

你想想看,如果每次登录都要把密码发出去,风险太大了。Kerberos引入了一个可信第三方——KDC(密钥分发中心)。

流程大致是这样的:

  1. 用户向KDC发送认证请求,包含自己的身份信息。
  2. KDC验证用户身份(通常通过密码哈希),返回一个TGT(票据授权票据)和会话密钥。
  3. 用户拿着TGT去申请访问某个服务的票据。
  4. KDC验证TGT有效后,发放服务票据。
  5. 用户用服务票据访问目标服务,服务验证票据后放行。
🔑 关键点: 密码只在第一步使用,后续所有通信都靠票据和会话密钥。票据有有效期,过期后必须重新申请。

我记得有一次帮一家银行做架构评审,他们内部系统用了Kerberos,但票据有效期设成了24小时。结果有员工离职后,他的票据还能用大半天。嗯,这里要注意:票据有效期一定要短,一般建议8小时以内,敏感系统可以设到1小时。

Kerberos的缺点也很明显:

  • 所有节点必须时间同步(误差通常不超过5分钟)。
  • KDC是单点故障,挂了整个系统都认证不了。
  • 不适合互联网场景,因为客户端和服务端需要预先共享密钥。

2.3 OAuth 2.0:授权协议的工业标准

OAuth 2.0其实不是认证协议,是授权协议。它解决的是「用户允许第三方应用访问自己在某服务上的资源」这个问题。比如你用微信登录某个网站,网站请求获取你的微信昵称和头像,这就是OAuth 2.0的典型场景。

OAuth 2.0定义了四种授权模式:

模式 适用场景 安全等级
授权码模式 有后端的Web应用
隐式模式 纯前端应用(已不推荐)
密码模式 高度信任的客户端
客户端凭证模式 服务间通信

我个人最常用的是授权码模式。流程是这样的:

  1. 用户点击「微信登录」,浏览器跳转到微信授权页面。
  2. 用户确认授权后,微信返回一个授权码(code)到你的后端。
  3. 后端用授权码向微信服务器换取access_token。
  4. 后端用access_token调用微信API获取用户信息。
⚠️ 常见错误: 授权码必须通过后端交换,不能在前端完成。我曾经见过一个项目,前端直接拿着授权码去换token,结果授权码被截获后攻击者也能拿到token。
// 后端交换授权码的示例
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=授权码
&redirect_uri=回调地址
&client_id=应用ID
&client_secret=应用密钥
💡 实践经验: access_token不要设太长的有效期,一般15-30分钟就够了。配合refresh_token使用,refresh_token可以设长一些,但一定要存储在安全的地方。

2.4 OpenID Connect:在OAuth 2.0之上做认证

OAuth 2.0只管授权,不管认证。它不知道「用户是谁」,只知道「用户授权了哪些权限」。OpenID Connect(OIDC)就是在OAuth 2.0之上加了一层身份认证层。

OIDC引入了一个新的token类型——ID Token。这是一个JWT(JSON Web Token),里面包含了用户的身份信息,比如用户ID、姓名、邮箱等。

ID Token的结构是这样的:

{
  "iss": "https://accounts.google.com",  // 签发者
  "sub": "1234567890",                   // 用户唯一标识
  "aud": "your-app-id",                  // 接收方
  "exp": 1516239022,                     // 过期时间
  "iat": 1516239022,                     // 签发时间
  "name": "张三",
  "email": "zhangsan@example.com"
}

你想想看,有了ID Token,应用不需要再额外调用API去查用户信息了。token本身就已经告诉了你「用户是谁」。而且JWT是经过签名的,应用可以验证它是否被篡改过。

我建议在微服务架构中优先使用OIDC。每个服务只需要验证JWT的签名,不需要每次都去认证中心查询。这样能大幅降低认证中心的压力。

🎯 最佳实践:
  • 使用RS256(RSA签名)而不是HS256(HMAC签名)。RS256用公钥验证,私钥只有认证中心持有,更安全。
  • JWT的payload不要放敏感信息,因为payload只是base64编码,不是加密的。
  • 一定要验证JWT的签名、过期时间、签发者和接收方。

嗯,最后总结一下我的个人看法:

  • 内部系统用Kerberos,稳定可靠。
  • 互联网应用用OAuth 2.0 + OIDC,灵活且标准。
  • 密码认证作为兜底方案,但一定要做好加盐哈希和传输加密。
  • 不管用哪种方案,永远不要信任客户端。所有认证逻辑必须在服务端完成。

我曾经见过一个团队,为了省事把JWT的密钥写在前端代码里。结果被逆向工程后,攻击者可以伪造任意用户的token。这种低级错误,真的不应该犯。

下一章我们会聊授权机制,讲清楚「你是谁之后,你能做什么」。到时候见。