NestJS项目实战:JWT认证和签名认证的实现方案

NestJS项目实战:JWT认证和签名认证的实现方案

前言

在后端开发中,接口安全是一个重要话题。本文结合实际项目经验,详细介绍在 NestJS 框架中如何实现和使用 JWT认证 和 签名认证 这两种主流的认证方式。

一、认证方案分析

1.1 应用场景分析

在支付系统开发中,存在两种不同的认证需求:

  1. 后台管理系统认证

    • 用户登录态管理
    • 用户身份识别
    • 权限信息携带
    • 支持登出操作
  2. 开放API认证

    • 第三方系统调用
    • 支付回调通知
    • 无需登录态
    • 请求合法性验证

1.2 认证方案对比

特性JWT认证签名认证
使用场景用户登录开放API
认证方式Token参数签名
存储需求无需存储需要密钥
可撤销性不可撤销可撤销
信息携带可携带信息不携带信息
性能开销较小较大

二、JWT认证实现

2.1 JWT基础知识

JWT(JSON Web Token)的组成部分:

  1. Header(头部)
  2. Payload(负载)
  3. Signature(签名)

标准格式:xxxxx.yyyyy.zzzzz

2.2 环境准备

bash
安装依赖包
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
安装类型定义
npm install @types/passport-jwt -D

2.3 JWT模块配置

// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategies/jwt.strategy';
@Module({
imports: [
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET || 'your-secret-key',
signOptions: {
expiresIn: '24h',
algorithm: 'HS256'
},
}),
],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}

2.4 JWT策略实现

// src/auth/strategies/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET || 'your-secret-key',
});
}
async validate(payload: any) {
const { userId, username } = payload;
if (!userId) {
throw new UnauthorizedException('无效的token');
}
return payload;
}
}

2.5 认证服务实现

// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(private readonly jwtService: JwtService) {}
async generateToken(user: any) {
const payload = {
userId: user.userId,
username: user.username,
roles: user.roles
};
return {
access_token: this.jwtService.sign(payload),
expires_in: 24 60 60
};
}
async verifyToken(token: string) {
try {
return this.jwtService.verify(token);
} catch (error) {
return null;
}
}
}

三、签名认证实现

3.1 签名认证流程

  1. 请求参数排序
  2. 参数和密钥拼接
  3. MD5加密生成签名
  4. 服务端验证签名

3.2 签名工具类

// src/utils/sign.util.ts
import { createHash } from 'crypto';
export class SignUtil {
static generateSign(params: Record<string, any>, apiKey: string): string {
// 1. 过滤空值和签名字段
const filteredParams = Object.entries(params)
.filter(([key, value]) => {
return key !== 'sign' &&
value !== '' &&
value !== undefined &&
value !== null;
})
.reduce((acc, [key, value]) => {
acc[key] = value;
return acc;
}, {});
// 2. 参数排序
const sortedParams = Object.keys(filteredParams)
.sort()
.reduce((acc, key) => {
acc[key] = filteredParams[key];
return acc;
}, {});
// 3. 拼接参数
let signStr = Object.entries(sortedParams)
.map(([key, value]) => ${key}=${value})
.join('&');
signStr += &key=${apiKey};
// 4. MD5加密
return createHash('md5')
.update(signStr)
.digest('hex')
.toUpperCase();
}
static verifySign(params: Record<string, any>, sign: string, apiKey: string): boolean {
const calculatedSign = this.generateSign(params, apiKey);
return calculatedSign === sign;
}
}

3.3 签名认证守卫

// src/auth/guards/sign-auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { SignUtil } from '../../utils/sign.util';
import { ApiKeyService } from '../services/api-key.service';
@Injectable()
export class SignAuthGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
private readonly apiKeyService: ApiKeyService
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const params = { ...request.body, ...request.query };
const sign = params.sign;
const mchNo = params.mchNo;
if (!sign || !mchNo) {
return false;
}
try {
const apiKey = await this.apiKeyService.getApiKey(mchNo);
if (!apiKey) {
return false;
}
return SignUtil.verifySign(params, sign, apiKey);
} catch (error) {
console.error('签名验证失败:', error);
return false;
}
}
}```

## 四、实际应用示例

### 4.1 用户登录接口

```typescript
@Controller('sys')
export class SysController {
constructor(
private readonly authService: AuthService,
private readonly userService: UserService,
) {}
@Public()
@Post('login')
async login(@Body() loginDto: LoginDto) {
const user = await this.userService.validateUser(
loginDto.username,
loginDto.password
);
const token = await this.authService.generateToken(user);
return {
code: 0,
msg: '登录成功',
data: {
...token,
userInfo: {
userId: user.userId,
username: user.username,
roles: user.roles
}
}
};
}
}```

### 4.2 支付回调接口
```typescript
@Controller('pay')
export class PayController {
constructor(private readonly payService: PayService) {}
@SignAuth()
@Post('notify')
async payNotify(@Body() notifyDto: NotifyDto) {
try {
await this.payService.verifyNotifyData(notifyDto);
await this.payService.handlePayResult(notifyDto);
return 'success';
} catch (error) {
console.error('支付回调处理失败:', error);
return 'fail';
}
}
}

五、项目实践要点

5.1 安全性要点

  1. JWT安全

    • 定期轮换密钥
    • 合理设置过期时间
    • 关键操作二次验证
    • 刷新token机制
  2. 签名安全

    • 商户密钥隔离
    • 时间戳校验
    • 防重放处理
    • 异常日志记录

5.2 性能优化

  1. 缓存优化

    • 商户密钥缓存
    • JWT黑名单缓存
    • 用户信息缓存
  2. 计算优化

    • 签名计算优化
    • 参数排序优化
    • 验证逻辑优化

5.3 开发规范

  1. 错误处理

    • 统一错误拦截
    • 错误日志记录
    • 标准错误响应
  2. 代码组织

    • 模块化设计
    • 统一响应格式
    • 规范代码注释

六、常见问题处理

6.1 JWT问题处理

  1. Token过期处理

    • 刷新token机制
    • 过期前续期
    • 降级处理方案
  2. Token注销处理

    • token黑名单
    • Redis存储机制
    • 定期清理策略

6.2 签名问题处理

  1. 验签失败排查

    • 参数排序检查
    • 密钥正确性验证
    • 请求日志分析
  2. 重放攻击防范

    • 时间戳验证
    • 流水号机制
    • 幂等性保证

七、总结

本文详细介绍了NestJS中JWT认证和签名认证的实现方案:

  1. JWT认证适用于用户登录场景
  2. 签名认证适用于开放API接口
  3. 两种认证方式可组合使用
  4. 通过装饰器实现认证控制

参考资料

本文首发于优快云,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值