JWT 在微服务架构中如何使用
JWT 在微服务架构中的使用需要考虑分布式系统的特殊性,以下是完整的实现方案:微服务架构中的 JWT 挑战1. 服务间认证如何在微服务之间安全地传递 JWT如何验证来自其他服务的请求2. 密钥管理多个服务如何共享或获取公钥密钥轮换如何协调3. Token 传播如何在服务调用链中传递 JWT如何处理 Token 过期4. 权限控制如何实现细粒度的权限控制如何处理不同服务的权限需求架构设计1. 集中式认证服务┌─────────────┐│ Client │└──────┬──────┘ │ ▼┌─────────────┐│ API Gateway│└──────┬──────┘ │ ▼┌─────────────────┐│ Auth Service ││ (签发 JWT) │└─────────────────┘ │ ▼┌─────────────────┐│ JWK Set ││ (公钥分发) │└─────────────────┘2. 服务间通信流程Client → API Gateway → Auth Service → JWT ↓ Service A → Service B ↓ Service C实现方案1. 使用 JWK (JSON Web Key) 分发公钥Auth Service 实现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' }});// 生成 JWKfunction 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] });});// 签发 JWTapp.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 });});其他服务验证 JWTconst 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 集成const 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 传播const 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. 权限控制// 基于角色的访问控制(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. 密钥轮换const 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. 监控和日志const { 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 进行认证和授权。