基于NestJs的JWT双Token认证机制设计与实现

基于NestJs的JWT双Token认证机制设计与实现

在Web应用开发中,认证授权是保障系统安全的核心环节。传统单Token方案存在Token过期导致用户体验下降、频繁刷新Token增加服务压力等问题。双Token机制通过分离访问权限(Access-token)与身份刷新(Refresh-token),在安全性和用户体验间取得平衡。本文将深入探讨基于NestJs框架的双Token认证实现方案。

一、技术选型与核心概念

1.1 JWT认证原理

JWT(JSON Web Token)由Header、Payload、Signature三部分组成,采用Base64编码和加密签名确保数据完整性。其无状态特性使其成为分布式系统的理想选择,但需注意:

  • Token过期时间设置需权衡安全性与用户体验
  • 敏感信息不应直接存储在Payload中
  • 必须使用HTTPS协议传输

1.2 双Token机制优势

维度 Access-token Refresh-token
有效期 短(15-30分钟) 长(7-30天)
功能 访问受保护资源 获取新的Access-token
存储方式 HTTP-only Cookie/内存 HttpOnly Secure Cookie
撤销难度 无需撤销(短有效期) 可实现黑名单机制

二、NestJs认证模块实现

2.1 环境准备与依赖安装

  1. npm install @nestjs/passport passport passport-jwt @nestjs/jwt bcrypt jsonwebtoken
  2. npm install --save-dev @types/passport-jwt

2.2 核心模块配置

创建AuthModule并配置JWT模块:

  1. // auth.module.ts
  2. @Module({
  3. imports: [
  4. PassportModule,
  5. JwtModule.registerAsync({
  6. useFactory: async (config: ConfigService) => ({
  7. secret: config.get<string>('JWT_SECRET'),
  8. signOptions: {
  9. expiresIn: '15m', // Access-token有效期
  10. },
  11. }),
  12. inject: [ConfigService],
  13. }),
  14. ],
  15. providers: [AuthService, JwtStrategy, LocalStrategy],
  16. exports: [AuthService],
  17. })
  18. export class AuthModule {}

2.3 策略实现

2.3.1 Access-token策略

  1. // jwt-access.strategy.ts
  2. @Injectable()
  3. export class JwtAccessStrategy extends PassportStrategy(Strategy) {
  4. constructor(private readonly configService: ConfigService) {
  5. super({
  6. jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  7. secretOrKey: configService.get<string>('JWT_SECRET'),
  8. ignoreExpiration: false,
  9. });
  10. }
  11. async validate(payload: JwtPayload) {
  12. return { userId: payload.sub, username: payload.username };
  13. }
  14. }

2.3.2 Refresh-token策略

  1. // jwt-refresh.strategy.ts
  2. @Injectable()
  3. export class JwtRefreshStrategy extends PassportStrategy(Strategy, 'jwt-refresh') {
  4. constructor(private readonly configService: ConfigService) {
  5. super({
  6. jwtFromRequest: ExtractJwt.fromExtractors([
  7. (request: Request) => request.cookies?.['refresh_token'],
  8. ]),
  9. secretOrKey: configService.get<string>('JWT_REFRESH_SECRET'),
  10. passReqToCallback: true,
  11. });
  12. }
  13. async validate(req: Request, payload: JwtPayload) {
  14. const refreshToken = req.cookies?.['refresh_token'];
  15. // 实现黑名单检查逻辑
  16. return { userId: payload.sub, refreshToken };
  17. }
  18. }

三、认证流程实现

3.1 登录接口实现

  1. // auth.controller.ts
  2. @Post('login')
  3. @UsePipes(new ValidationPipe())
  4. async login(@Body() loginDto: LoginDto, @Res({ passthrough: true }) res: Response) {
  5. const { access_token, refresh_token } = await this.authService.login(loginDto);
  6. // 设置HttpOnly Secure Cookie
  7. res.cookie('refresh_token', refresh_token, {
  8. httpOnly: true,
  9. secure: true,
  10. sameSite: 'strict',
  11. maxAge: 30 * 24 * 60 * 60 * 1000, // 30天
  12. });
  13. return { access_token };
  14. }

3.2 Token刷新接口

  1. // auth.controller.ts
  2. @Post('refresh')
  3. @UseGuards(JwtRefreshGuard)
  4. async refreshTokens(
  5. @Req() req: RequestWithUser,
  6. @Res({ passthrough: true }) res: Response
  7. ) {
  8. const { user, refreshToken } = req.user;
  9. const { access_token, newRefreshToken } = await this.authService.refreshTokens(
  10. user.userId,
  11. refreshToken
  12. );
  13. // 更新Cookie
  14. res.cookie('refresh_token', newRefreshToken, {
  15. httpOnly: true,
  16. secure: true,
  17. sameSite: 'strict',
  18. maxAge: 30 * 24 * 60 * 60 * 1000,
  19. });
  20. return { access_token };
  21. }

四、安全增强措施

4.1 Refresh Token黑名单

  1. // refresh-token.service.ts
  2. @Injectable()
  3. export class RefreshTokenService {
  4. private readonly blacklist = new Set<string>();
  5. async addToBlacklist(token: string) {
  6. this.blacklist.add(token);
  7. // 可选:持久化到Redis,设置TTL
  8. }
  9. async isBlacklisted(token: string): Promise<boolean> {
  10. return this.blacklist.has(token);
  11. // 实际生产环境应查询Redis
  12. }
  13. }

4.2 防护机制实现

  1. CSRF防护

    • 使用SameSite=Strict Cookie属性
    • 结合CSP策略增强安全性
  2. 点击劫持防护

    1. # Nginx配置示例
    2. add_header X-Frame-Options "DENY";
    3. add_header Content-Security-Policy "frame-ancestors 'none'";
  3. 速率限制

    1. // auth.controller.ts
    2. @Post('refresh')
    3. @UseGuards(JwtRefreshGuard)
    4. @UseInterceptors(RateLimitInterceptor) // 自定义速率限制拦截器
    5. async refreshTokens(...) { ... }

五、最佳实践与优化建议

5.1 Token存储方案对比

存储方式 安全性 跨域支持 实现复杂度
HttpOnly Cookie 优秀
LocalStorage
Web Storage

推荐方案

  • 前端应用:HttpOnly Cookie + XSRF-TOKEN
  • 移动端:安全存储方案(如Keychain)

5.2 性能优化策略

  1. Token解析缓存

    1. // 使用LRU缓存解析结果
    2. const tokenCache = new LRU<string, JwtPayload>({
    3. max: 1000,
    4. ttl: 60000, // 1分钟
    5. });
    6. async validateToken(token: string): Promise<JwtPayload> {
    7. const cached = tokenCache.get(token);
    8. if (cached) return cached;
    9. const payload = verifyToken(token); // 实际解析逻辑
    10. tokenCache.set(token, payload);
    11. return payload;
    12. }
  2. 并发控制

    • 实现Refresh Token的原子操作
    • 使用Redis分布式锁处理并发刷新

5.3 监控与告警

  1. 关键指标监控

    • Token生成频率
    • 刷新请求成功率
    • 黑名单命中率
  2. 异常告警规则

    • 连续5次刷新失败触发告警
    • 刷新Token使用率超过80%时预警

六、完整架构示例

  1. // main.ts 完整配置示例
  2. async function bootstrap() {
  3. const app = await NestFactory.create(AppModule, {
  4. cors: {
  5. origin: ['https://your-domain.com'],
  6. credentials: true,
  7. },
  8. });
  9. // 安全中间件
  10. app.use(helmet());
  11. app.use(csrf({ cookie: true }));
  12. // 全局异常过滤器
  13. app.useGlobalFilters(new HttpExceptionFilter());
  14. // 全局管道
  15. app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  16. await app.listen(3000);
  17. }

七、常见问题解决方案

7.1 跨域问题处理

  1. // main.ts 跨域配置
  2. app.enableCors({
  3. origin: process.env.CLIENT_URL,
  4. methods: ['GET', 'POST', 'PUT', 'DELETE'],
  5. credentials: true,
  6. allowedHeaders: ['Content-Type', 'Authorization'],
  7. });

7.2 移动端适配方案

  1. 原生应用

    • 使用平台特定安全存储
    • 通过API网关传递Token
  2. 混合应用

    1. // Cordova插件示例
    2. cordova.plugin.http.setHeader('Authorization', 'Bearer ' + accessToken);

八、总结与展望

双Token认证机制通过分离权限与刷新功能,在安全性与用户体验间取得良好平衡。实际实施时需注意:

  1. 定期轮换加密密钥
  2. 建立完善的Token撤销机制
  3. 结合设备指纹技术增强安全性

未来发展方向可考虑:

  • 引入OAuth 2.0设备授权流
  • 结合FIDO2实现无密码认证
  • 探索基于SIOP的自主主权身份方案

通过合理设计认证架构,开发者能够构建出既安全又易用的认证系统,为业务发展提供坚实保障。