微服务架构中的授权:网关向服务传递Token的最佳实践
微服务中Gateway向服务传递授权Token的最佳实践
针对你用NestJS+RabbitMQ构建微服务时遇到的授权Token传递问题,以下几种方案无需将Token嵌入DTO,符合每个服务自行处理认证的需求:
1. 利用RabbitMQ消息Headers传递原始Token
NestJS微服务的消息传递支持附加元数据(Headers),可以将Token放在Headers中发送,服务端通过RPC上下文获取后自行校验。
网关端修改:发送消息时附带Token Header
// 网关的sendMicroserviceRequest方法实现 async sendMicroserviceRequest(pattern: string, payload: any, req: Request) { // 从HTTP请求头中提取Token const authHeader = req.headers.authorization; if (!authHeader) { throw new UnauthorizedException('No token provided'); } // 携带Token到微服务消息的headers中 return this.clientProxy.send(pattern, payload, { headers: { authorization: authHeader } }).toPromise(); } // 在AccountController的me方法中传入Request对象 @Get(':id') async me(@Param('id', ParseIntPipe) id: number, @Req() req: Request): Promise<any> { const dto: FindOneDTO = {id}; return this.sendMicroserviceRequest("user/me", dto, req); }
服务端修改:AuthGuard从RPC上下文读取Token校验
@Injectable() export class AuthGuard implements CanActivate { constructor(private readonly authService: AuthService) {} async canActivate(context: ExecutionContext): Promise<boolean> { // 获取RPC上下文的元数据 const rpcContext = context.switchToRpc(); const msgMetadata = rpcContext.getContext(); const authHeader = msgMetadata?.headers?.authorization; if (!authHeader) { throw new RpcException('Unauthorized: No token found'); } // 提取Token并调用Auth Service校验 const token = authHeader.split(' ')[1]; const isValid = await this.authService.validateToken(token); if (!isValid) { throw new RpcException('Unauthorized: Invalid token'); } // 将用户信息附加到上下文,供接口使用 const user = await this.authService.getUserFromToken(token); rpcContext.getData().user = user; return true; } }
2. 网关预验证Token,传递身份凭证而非原始Token
由网关先完成Token的初步验证,提取用户身份信息(如userId、角色)后传递给服务端,服务端仅需校验身份信息的合法性,无需处理原始Token。
网关端修改:预验证Token并传递身份信息
@Get(':id') async me(@Param('id', ParseIntPipe) id: number, @Req() req: Request): Promise<any> { const dto: FindOneDTO = {id}; const authHeader = req.headers.authorization; if (!authHeader) { throw new UnauthorizedException('No token provided'); } // 网关调用Auth Service验证Token并获取用户信息 const token = authHeader.split(' ')[1]; const user = await this.authService.validateTokenAndGetUser(token); // 将用户身份信息放入消息headers return this.sendMicroserviceRequest("user/me", dto, { headers: { userId: user.id, roles: user.roles.join(',') } }); }
服务端修改:AuthGuard校验身份信息
@Injectable() export class AuthGuard implements CanActivate { constructor(private readonly authService: AuthService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const rpcContext = context.switchToRpc(); const msgMetadata = rpcContext.getContext(); const userId = msgMetadata?.headers?.userId; if (!userId) { throw new RpcException('Unauthorized: No user identity provided'); } // 调用Auth Service确认用户身份合法性 const user = await this.authService.getUserById(Number(userId)); if (!user) { throw new RpcException('Unauthorized: Invalid user identity'); } rpcContext.getData().user = user; return true; } }
3. JWT无状态验证(服务端自行校验Token签名)
如果使用JWT作为授权Token,每个服务可以通过公钥直接验证Token的签名和有效期,无需调用Auth Service,实现无状态认证。
服务端AuthGuard实现JWT校验
@Injectable() export class AuthGuard implements CanActivate { constructor( @Inject(JWT_MODULE_OPTIONS) private readonly jwtOptions: JwtModuleOptions ) {} async canActivate(context: ExecutionContext): Promise<boolean> { const rpcContext = context.switchToRpc(); const msgMetadata = rpcContext.getContext(); const authHeader = msgMetadata?.headers?.authorization; if (!authHeader) { throw new RpcException('Unauthorized: No token provided'); } const token = authHeader.split(' ')[1]; try { // 直接验证JWT签名和过期时间 const payload = await verify(token, this.jwtOptions.secretOrPublicKey); rpcContext.getData().user = payload; return true; } catch (err) { throw new RpcException('Unauthorized: Invalid or expired token'); } } }
注意事项
- 确保RabbitMQ启用TLS加密,防止Token或身份信息在传输过程中被窃取。
- 所有服务的AuthGuard都要适配RPC上下文,不能依赖HTTP Request对象。
- 避免将敏感信息放入消息Payload,始终通过Headers传递身份相关数据。
内容的提问来源于stack exchange,提问作者Emir Kutlugün
相关产品推荐
相关产品推荐

