5月27日 23:10

JWT 在微服务架构中如何使用

核心思路

微服务中使用 JWT 的关键是:由认证服务签发令牌,各服务通过公钥本地验证,避免集中式校验带来的性能瓶颈和单点故障

具体架构:客户端登录 → 认证服务签发 JWT → 客户端携带 JWT 请求 API Gateway → Gateway 验证后转发 → 下游服务通过 JWK 端点获取公钥自行验证,无需回调认证服务。

为什么用非对称加密(RS256)而不是对称加密(HS256)

HS256 要求所有验证方共享同一个密钥,微服务场景下密钥分发和泄露风险高。RS256 签名用私钥(仅认证服务持有),验证用公钥(可公开分发),任何服务只需拿到公钥就能验证,无需共享敏感信息。

认证服务签发 JWT

javascript
const jwt = require('jsonwebtoken'); const { generateKeyPairSync } = require('crypto'); const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }); // 签发令牌 app.post('/auth/login', (req, res) => { const user = validateUser(req.body.username, req.body.password); const token = jwt.sign( { userId: user.id, role: user.role, permissions: user.permissions }, privateKey, { algorithm: 'RS256', keyid: 'key-20260527', expiresIn: '15m', issuer: 'auth-service', audience: 'my-platform' } ); const refreshToken = jwt.sign( { userId: user.id, type: 'refresh' }, privateKey, { algorithm: 'RS256', expiresIn: '7d' } ); res.json({ token, refreshToken }); }); // JWK Set 端点,供其他服务获取公钥 app.get('/.well-known/jwks.json', (req, res) => { const jwk = jose.JWK.asKey(publicKey, { kid: 'key-20260527' }); res.json({ keys: [jwk.toJWK()] }); });

访问令牌有效期设短(15 分钟),配合 Refresh Token 续期,降低令牌泄露风险。

API Gateway 验证与转发

javascript
app.use(async (req, res, next) => { const token = req.headers['authorization']?.replace('Bearer ', ''); if (!token) return res.status(401).json({ error: 'No token' }); try { const publicKey = await getPublicKey(); // 从 JWK 端点获取,带缓存 req.user = jwt.verify(token, publicKey, { algorithms: ['RS256'], issuer: 'auth-service', audience: 'my-platform' }); // 将用户信息注入请求头,传递给下游服务 req.headers['x-user-id'] = req.user.userId; req.headers['x-user-role'] = req.user.role; next(); } catch (e) { res.status(401).json({ error: 'Invalid token' }); } });

下游服务验证与 Token 传播

下游服务有两种选择:

  • Gateway 已验证,下游信任 Gateway:只检查 x-user-id 等请求头,适用于内部可信网络
  • 下游自行验证:从 JWK 端点获取公钥,本地校验,适用于跨团队或零信任场景

服务间调用时,用拦截器自动传播 Token:

javascript
const axios = require('axios'); const serviceClient = axios.create({ timeout: 5000 }); serviceClient.interceptors.request.use(config => { const token = getCurrentToken(); // 从请求上下文获取 if (token) config.headers['Authorization'] = `Bearer ${token}`; return config; });

密钥轮换

线上必须支持密钥轮换。流程:生成新密钥对 → 新令牌用新 kid 签发 → 旧令牌仍可用旧公钥验证 → 旧密钥过期后从 JWK Set 移除。验证方根据 JWT Header 中的 kid 字段匹配对应公钥,过渡期内 JWK Set 同时包含新旧公钥。

Token 撤销的处理

JWT 本身无状态,签出后无法直接撤销。常用方案:

  • 短有效期 + Refresh Token:最主流,泄露窗口小
  • 黑名单:Redis 存已撤销的 Token ID,每次验证时查询,牺牲部分无状态优势
  • Gateway 层拦截:只对敏感操作生效,如修改密码后立即失效旧 Token

追问

Q: JWT 和 OAuth2 是什么关系? JWT 是令牌格式,OAuth2 是授权框架。OAuth2 可以用 JWT 作为 Access Token 的载体,也可以用不透明令牌。两者不在同一层面。

Q: 微服务之间用 JWT 还是 mTLS? 服务间通信(非用户请求)更推荐 mTLS(双向 TLS),双方通过证书互验身份,不依赖令牌传播。JWT 适合用户到服务的场景,mTLS 适合服务到服务。

Q: JWK 端点挂了怎么办? 验证方必须缓存公钥(TTL 5-10 分钟),JWK 端点短暂不可用时用缓存继续验证,不影响线上服务。

标签:JWT