JWT 在微服务架构中的使用需要考虑分布式系统的特殊性,以下是完整的实现方案:
微服务架构中的 JWT 挑战
1. 服务间认证
- 如何在微服务之间安全地传递 JWT
- 如何验证来自其他服务的请求
2. 密钥管理
- 多个服务如何共享或获取公钥
- 密钥轮换如何协调
3. Token 传播
- 如何在服务调用链中传递 JWT
- 如何处理 Token 过期
4. 权限控制
- 如何实现细粒度的权限控制
- 如何处理不同服务的权限需求
架构设计
1. 集中式认证服务
shell┌─────────────┐ │ Client │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ API Gateway│ └──────┬──────┘ │ ▼ ┌─────────────────┐ │ Auth Service │ │ (签发 JWT) │ └─────────────────┘ │ ▼ ┌─────────────────┐ │ JWK Set │ │ (公钥分发) │ └─────────────────┘
2. 服务间通信流程
shellClient → API Gateway → Auth Service → JWT ↓ Service A → Service B ↓ Service C
实现方案
1. 使用 JWK (JSON Web Key) 分发公钥
Auth Service 实现
javascriptconst jwt = require('jsonwebtoken'); const { generateKeyPairSync } = require('crypto'); // 生成密钥对 const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }); // 生成 JWK function generateJWK(publicKey, kid = 'key1') { const jwk = { kty: 'RSA', kid: kid, use: 'sig', alg: 'RS256', n: publicKey.match(/Modulus:\s*([^\n]+)/)?.[1], e: publicKey.match(/Exponent:\s*([^\n]+)/)?.[1] }; return jwk; } // JWK Set 端点 app.get('/.well-known/jwks.json', (req, res) => { const jwk = generateJWK(publicKey); res.json({ keys: [jwk] }); }); // 签发 JWT app.post('/auth/login', (req, res) => { const { username, password } = req.body; const user = validateUser(username, password); const token = jwt.sign( { userId: user.id, role: user.role, permissions: user.permissions }, privateKey, { algorithm: 'RS256', keyid: 'key1', expiresIn: '1h', issuer: 'auth-service', audience: 'microservices' } ); res.json({ token }); });
其他服务验证 JWT
javascriptconst jose = require('node-jose'); const jwt = require('jsonwebtoken'); let cachedPublicKey = null; let cacheExpiry = 0; async function getPublicKey() { if (cachedPublicKey && Date.now() < cacheExpiry) { return cachedPublicKey; } const response = await fetch('https://auth-service/.well-known/jwks.json'); const jwks = await response.json(); const keystore = await jose.JWK.createKeyStore(); await keystore.add(jwks.keys[0]); cachedPublicKey = keystore.get(jwks.keys[0].kid).toPEM(false); cacheExpiry = Date.now() + (5 * 60 * 1000); // 缓存5分钟 return cachedPublicKey; } async function verifyToken(token) { const publicKey = await getPublicKey(); try { const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'], issuer: 'auth-service', audience: 'microservices' }); return decoded; } catch (error) { throw new Error('Invalid token'); } }
2. API Gateway 集成
javascriptconst express = require('express'); const { createProxyMiddleware } = require('http-proxy-middleware'); const app = express(); // 认证中间件 app.use(async (req, res, next) => { const authHeader = req.headers['authorization']; if (!authHeader) { return res.status(401).json({ error: 'No token provided' }); } const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader; try { const decoded = await verifyToken(token); req.user = decoded; next(); } catch (error) { res.status(401).json({ error: 'Invalid token' }); } }); // 路由到不同服务 app.use('/api/users', createProxyMiddleware({ target: 'http://user-service:3001', changeOrigin: true })); app.use('/api/orders', createProxyMiddleware({ target: 'http://order-service:3002', changeOrigin: true })); app.use('/api/products', createProxyMiddleware({ target: 'http://product-service:3003', changeOrigin: true }));
3. 服务间 Token 传播
javascriptconst axios = require('axios'); class ServiceClient { constructor(baseURL) { this.client = axios.create({ baseURL, timeout: 5000 }); this.client.interceptors.request.use(config => { const token = getCurrentToken(); // 从上下文获取当前 token if (token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }); } async get(url, config) { return this.client.get(url, config); } async post(url, data, config) { return this.client.post(url, data, config); } } // 使用示例 const userService = new ServiceClient('http://user-service:3001'); const orderService = new ServiceClient('http://order-service:3002'); async function getUserOrders(userId) { const user = await userService.get(`/users/${userId}`); const orders = await orderService.get(`/orders?userId=${userId}`); return { user: user.data, orders: orders.data }; }
4. 权限控制
javascript// 基于角色的访问控制(RBAC) function requireRole(...roles) { return (req, res, next) => { if (!req.user) { return res.status(401).json({ error: 'Unauthorized' }); } if (!roles.includes(req.user.role)) { return res.status(403).json({ error: 'Forbidden' }); } next(); }; } // 基于权限的访问控制 function requirePermission(...permissions) { return (req, res, next) => { if (!req.user) { return res.status(401).json({ error: 'Unauthorized' }); } const userPermissions = req.user.permissions || []; const hasPermission = permissions.every(p => userPermissions.includes(p)); if (!hasPermission) { return res.status(403).json({ error: 'Forbidden' }); } next(); }; } // 使用示例 app.get('/admin/users', requireRole('admin'), (req, res) => { res.json({ users: [] }); }); app.post('/orders', requirePermission('order:create'), (req, res) => { res.json({ success: true }); });
5. 密钥轮换
javascriptconst keyStore = { keys: new Map(), currentKeyId: null }; // 添加新密钥 function addNewKey() { const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }); const kid = `key${Date.now()}`; keyStore.keys.set(kid, { publicKey, privateKey, createdAt: Date.now() }); keyStore.currentKeyId = kid; return kid; } // 获取当前密钥 function getCurrentKey() { return keyStore.keys.get(keyStore.currentKeyId); } // 获取所有公钥(用于验证) function getAllPublicKeys() { const keys = []; for (const [kid, key] of keyStore.keys.entries()) { keys.push({ kid, publicKey: key.publicKey, createdAt: key.createdAt }); } return keys; } // 签发 JWT(使用当前密钥) function signToken(payload) { const currentKey = getCurrentKey(); return jwt.sign(payload, currentKey.privateKey, { algorithm: 'RS256', keyid: keyStore.currentKeyId, expiresIn: '1h' }); } // 验证 JWT(尝试所有密钥) function verifyToken(token) { const decoded = jwt.decode(token, { complete: true }); const kid = decoded.header.kid; const key = keyStore.keys.get(kid); if (!key) { throw new Error('Key not found'); } return jwt.verify(token, key.publicKey, { algorithms: ['RS256'] }); } // 定期轮换密钥 setInterval(() => { addNewKey(); // 清理旧密钥(保留最近7天) const sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000); for (const [kid, key] of keyStore.keys.entries()) { if (key.createdAt < sevenDaysAgo && kid !== keyStore.currentKeyId) { keyStore.keys.delete(kid); } } }, 30 * 24 * 60 * 60 * 1000); // 每30天轮换一次
6. 监控和日志
javascriptconst { createLogger, format, transports } = require('winston'); const logger = createLogger({ format: format.combine( format.timestamp(), format.json() ), transports: [ new transports.Console(), new transports.File({ filename: 'auth.log' }) ] }); // 认证中间件添加日志 app.use(async (req, res, next) => { const startTime = Date.now(); try { const token = req.headers['authorization']?.replace('Bearer ', ''); if (token) { const decoded = await verifyToken(token); req.user = decoded; logger.info({ event: 'auth_success', userId: decoded.userId, path: req.path, method: req.method, ip: req.ip }); } next(); } catch (error) { const duration = Date.now() - startTime; logger.error({ event: 'auth_failure', error: error.message, path: req.path, method: req.method, ip: req.ip, duration }); res.status(401).json({ error: 'Unauthorized' }); } }); // 指标收集 const authMetrics = { totalRequests: 0, successfulAuth: 0, failedAuth: 0, avgAuthTime: 0 }; function updateMetrics(duration, success) { authMetrics.totalRequests++; if (success) { authMetrics.successfulAuth++; } else { authMetrics.failedAuth++; } authMetrics.avgAuthTime = (authMetrics.avgAuthTime * (authMetrics.totalRequests - 1) + duration) / authMetrics.totalRequests; } // 指标端点 app.get('/metrics', (req, res) => { res.json(authMetrics); });
最佳实践
- 使用非对称加密(RS256)而非对称加密(HS256)
- 实现 JWK 端点用于公钥分发
- 缓存公钥减少网络请求
- 实现密钥轮换机制
- 使用 API Gateway统一认证
- 实现细粒度权限控制
- 添加监控和日志
- 处理 Token 过期和刷新
- 使用 HTTPS传输
- 实现速率限制防止滥用
通过以上方案,可以在微服务架构中安全、高效地使用 JWT 进行认证和授权。