从零搭建类Google的集中式认证与密钥管理微服务架构
集中式认证授权+密钥管理的微服务架构搭建指南
一、项目结构规划
推荐方案:Monorepo + Docker Compose
适合中小规模微服务项目,便于统一维护和部署:
project-root/ ├── docker-compose.yml # 所有服务的编排配置 ├── .env # 全局非敏感配置 ├── gateway/ # API网关服务 │ ├── src/ │ │ ├── middleware/ # JWT验证、路由转发中间件 │ │ ├── routes/ # 路由映射配置 │ │ └── app.js │ ├── Dockerfile │ └── .env # 网关专属配置 ├── auth-service/ # 集中式认证授权服务 │ ├── src/ │ │ ├── controllers/ # 登录、注册、Token刷新接口 │ │ ├── models/ # User、Role、Permission数据模型 │ │ ├── services/ # JWT生成、权限校验逻辑 │ │ └── app.js │ ├── Dockerfile │ └── .env ├── key-management-service/ # 密钥管理服务 │ ├── src/ │ │ ├── controllers/ # 密钥查询、新增、轮换接口 │ │ ├── models/ # Secret、ServiceAccount数据模型 │ │ ├── crypto/ # 加密解密工具类 │ │ └── app.js │ ├── Dockerfile │ └── .env └── data-processing-service/ # 业务微服务示例 ├── src/ ├── Dockerfile └── .env
维护要点
- 所有服务遵循统一目录规范,降低跨服务维护成本
- 通过Docker Compose配置内部网络,服务间以服务名直接通信(如
http://auth-service:3000) - 敏感配置全部通过环境变量或
env_file注入,禁止硬编码
二、集中式认证授权实现
核心流程
- 单点登录(SSO):所有React前端跳转到
auth-service的统一登录页,验证通过后返回短时效access_token(15-30分钟)和长时效refresh_token(7天) - Token验证:API网关拦截所有请求,验证
access_token的签名与有效期,失败直接返回401 - 角色权限管理:
auth-service数据库存储用户-角色-权限映射,access_token嵌入角色信息,网关或业务服务按需校验接口权限
代码片段
网关JWT验证中间件
// gateway/src/middleware/auth.js const jwt = require('jsonwebtoken'); module.exports = (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ message: '未提供有效Token' }); } const token = authHeader.split(' ')[1]; try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = { id: decoded.userId, roles: decoded.roles }; next(); } catch (err) { return res.status(401).json({ message: 'Token无效或已过期' }); } };
Auth服务登录接口
// auth-service/src/controllers/auth.js const jwt = require('jsonwebtoken'); const User = require('../models/User'); exports.login = async (req, res) => { const { username, password } = req.body; const user = await User.findOne({ username }); if (!user || !await user.verifyPassword(password)) { return res.status(401).json({ message: '用户名或密码错误' }); } // 生成Access Token const accessToken = jwt.sign( { userId: user._id, roles: user.roles }, process.env.JWT_SECRET, { expiresIn: '15m' } ); // 生成Refresh Token(存入HttpOnly Cookie) const refreshToken = jwt.sign( { userId: user._id }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d' } ); res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', maxAge: 7 * 24 * 60 * 60 * 1000 }); res.json({ accessToken, roles: user.roles }); };
三、密钥管理系统搭建
核心设计
- 加密存储:用AES-256-GCM加密敏感密钥(数据库密码、API密钥等),主加密密钥从服务器环境变量读取(禁止提交到代码仓库)
- 服务账号机制:每个微服务对应唯一服务账号,仅认证通过的账号可查询自身权限范围内的密钥
- 密钥轮换:定期轮换主加密密钥与业务密钥,留存轮换日志便于审计
代码片段:加密工具类
// key-management-service/src/crypto/encryptor.js const crypto = require('crypto'); const ENCRYPTION_ALGORITHM = 'aes-256-gcm'; const MAIN_KEY = process.env.MAIN_ENCRYPTION_KEY; // 32位十六进制字符串,从环境变量获取 exports.encrypt = (plaintext) => { const iv = crypto.randomBytes(12); const cipher = crypto.createCipheriv(ENCRYPTION_ALGORITHM, Buffer.from(MAIN_KEY, 'hex'), iv); let encrypted = cipher.update(plaintext, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag().toString('hex'); return `${iv.toString('hex')}:${authTag}:${encrypted}`; }; exports.decrypt = (ciphertext) => { const [ivHex, authTagHex, encryptedHex] = ciphertext.split(':'); const iv = Buffer.from(ivHex, 'hex'); const authTag = Buffer.from(authTagHex, 'hex'); const encrypted = Buffer.from(encryptedHex, 'hex'); const decipher = crypto.createDecipheriv(ENCRYPTION_ALGORITHM, Buffer.from(MAIN_KEY, 'hex'), iv); decipher.setAuthTag(authTag); let plaintext = decipher.update(encrypted, 'hex', 'utf8'); plaintext += decipher.final('utf8'); return plaintext; };
四、服务间通信安全
方案选择:服务账号JWT认证
适合内部微服务通信,简单易落地:
- 微服务启动时,向
key-management-service申请专属服务JWT - 调用其他服务时,在请求头携带
Authorization: Bearer <service-jwt> - 被调用服务验证JWT合法性,确保请求来自授权内部服务
代码片段:服务间调用示例
// data-processing-service/src/services/auth-client.js const axios = require('axios'); async function getServiceAccountToken() { const response = await axios.post('http://key-management-service:3000/api/service-token', { serviceId: process.env.SERVICE_ID, serviceSecret: process.env.SERVICE_SECRET // 启动时从密钥服务注入 }); return response.data.accessToken; } async function callOtherService() { const token = await getServiceAccountToken(); const response = await axios.get('http://another-service:3000/api/data', { headers: { Authorization: `Bearer ${token}` } }); return response.data; }
五、最佳实践
- Token安全:
access_token存前端内存避免XSS,refresh_token存入HttpOnly、Secure Cookie避免CSRF - 网关职责:仅负责路由转发、Token验证、限流,不处理业务逻辑
- 密钥安全:主加密密钥定期轮换,密钥服务数据库开启加密存储,限制访问IP
- 监控与日志:用ELK Stack集中管理日志,用Prometheus+Grafana监控服务状态与Token使用情况
- 权限最小化:服务账号与用户角色均遵循最小权限原则,仅授予完成业务所需的必要权限
内容的提问来源于stack exchange,提问作者Radioactive Boy
相关产品推荐
相关产品推荐

