乐闻世界logo
搜索文章和话题

How to use JWT in microservices architecture

2月21日 17:52

Using JWT in microservices architecture requires considering the special characteristics of distributed systems. Here's a complete implementation:

JWT Challenges in Microservices Architecture

1. Inter-Service Authentication

  • How to securely pass JWT between microservices
  • How to verify requests from other services

2. Key Management

  • How multiple services share or obtain public keys
  • How to coordinate key rotation

3. Token Propagation

  • How to pass JWT in service call chains
  • How to handle token expiration

4. Permission Control

  • How to implement fine-grained permission control
  • How to handle different services' permission requirements

Architecture Design

1. Centralized Authentication Service

shell
┌─────────────┐ │ Client │ └──────┬──────┘ ┌─────────────┐ │ API Gateway│ └──────┬──────┘ ┌─────────────────┐ │ Auth Service │ (Issues JWT)└─────────────────┘ ┌─────────────────┐ │ JWK Set │ (Public Key)└─────────────────┘

2. Inter-Service Communication Flow

shell
Client → API Gateway → Auth Service → JWT Service A → Service B Service C

Implementation

1. Using JWK (JSON Web Key) for Public Key Distribution

Auth Service Implementation

javascript
const jwt = require('jsonwebtoken'); const { generateKeyPairSync } = require('crypto'); // Generate key pair const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }); // Generate 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 endpoint app.get('/.well-known/jwks.json', (req, res) => { const jwk = generateJWK(publicKey); res.json({ keys: [jwk] }); }); // Issue 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 }); });

Other Services Verify JWT

javascript
const 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); // Cache for 5 minutes 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 Integration

javascript
const express = require('express'); const { createProxyMiddleware } = require('http-proxy-middleware'); const app = express(); // Authentication middleware 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' }); } }); // Route to different services 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. Inter-Service Token Propagation

javascript
const axios = require('axios'); class ServiceClient { constructor(baseURL) { this.client = axios.create({ baseURL, timeout: 5000 }); this.client.interceptors.request.use(config => { const token = getCurrentToken(); // Get current token from context 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); } } // Usage example 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. Permission Control

javascript
// Role-Based Access Control (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(); }; } // Permission-Based Access Control 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(); }; } // Usage example app.get('/admin/users', requireRole('admin'), (req, res) => { res.json({ users: [] }); }); app.post('/orders', requirePermission('order:create'), (req, res) => { res.json({ success: true }); });

5. Key Rotation

javascript
const keyStore = { keys: new Map(), currentKeyId: null }; // Add new key 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; } // Get current key function getCurrentKey() { return keyStore.keys.get(keyStore.currentKeyId); } // Get all public keys (for verification) function getAllPublicKeys() { const keys = []; for (const [kid, key] of keyStore.keys.entries()) { keys.push({ kid, publicKey: key.publicKey, createdAt: key.createdAt }); } return keys; } // Sign JWT (using current key) function signToken(payload) { const currentKey = getCurrentKey(); return jwt.sign(payload, currentKey.privateKey, { algorithm: 'RS256', keyid: keyStore.currentKeyId, expiresIn: '1h' }); } // Verify JWT (try all keys) 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'] }); } // Regularly rotate keys setInterval(() => { addNewKey(); // Clean up old keys (keep last 7 days) 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); // Rotate every 30 days

6. Monitoring and Logging

javascript
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' }) ] }); // Add logging to authentication middleware 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' }); } }); // Metrics collection 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; } // Metrics endpoint app.get('/metrics', (req, res) => { res.json(authMetrics); });

Best Practices

  1. Use asymmetric encryption (RS256) instead of symmetric encryption (HS256)
  2. Implement JWK endpoint for public key distribution
  3. Cache public keys to reduce network requests
  4. Implement key rotation mechanism
  5. Use API Gateway for unified authentication
  6. Implement fine-grained permission control
  7. Add monitoring and logging
  8. Handle token expiration and refresh
  9. Use HTTPS for transmission
  10. Implement rate limiting to prevent abuse

With these solutions, you can securely and efficiently use JWT for authentication and authorization in microservices architecture.

标签:JWT