NestJS JWT认证 #
JWT简介 #
JWT(JSON Web Token)是一种开放标准,用于在各方之间安全地传输信息。JWT由三部分组成:Header、Payload和Signature。
text
Header.Payload.Signature
安装依赖 #
bash
npm install @nestjs/jwt passport-jwt
npm install -D @types/passport-jwt
JWT模块配置 #
基本配置 #
typescript
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
JwtModule.register({
secret: 'YOUR_SECRET_KEY',
signOptions: { expiresIn: '1h' },
}),
],
})
export class AuthModule {}
异步配置 #
typescript
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET'),
signOptions: {
expiresIn: configService.get<string>('JWT_EXPIRES_IN', '1h'),
},
}),
inject: [ConfigService],
}),
],
})
export class AuthModule {}
JWT策略 #
创建JWT策略 #
typescript
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get<string>('JWT_SECRET'),
});
}
async validate(payload: JwtPayload) {
return {
userId: payload.sub,
email: payload.email,
roles: payload.roles,
};
}
}
interface JwtPayload {
sub: number;
email: string;
roles: string[];
iat?: number;
exp?: number;
}
JWT Guard #
typescript
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
Token生成 #
登录生成Token #
typescript
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
@Injectable()
export class AuthService {
constructor(
private jwtService: JwtService,
private usersService: UsersService,
) {}
async login(email: string, password: string) {
const user = await this.validateUser(email, password);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
const payload = {
sub: user.id,
email: user.email,
roles: user.roles,
};
return {
access_token: this.jwtService.sign(payload),
user: {
id: user.id,
email: user.email,
name: user.name,
},
};
}
private async validateUser(email: string, password: string) {
const user = await this.usersService.findByEmail(email);
if (user && await this.comparePassword(password, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
}
自定义Payload #
typescript
const payload = {
sub: user.id,
email: user.email,
roles: user.roles,
permissions: user.permissions,
};
const token = this.jwtService.sign(payload, {
expiresIn: '15m',
issuer: 'my-app',
audience: 'my-app-users',
});
Token验证 #
手动验证 #
typescript
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(private jwtService: JwtService) {}
async validateToken(token: string) {
try {
const payload = this.jwtService.verify(token);
return payload;
} catch (error) {
throw new UnauthorizedException('Invalid token');
}
}
}
解码Token #
typescript
const decoded = this.jwtService.decode(token);
console.log(decoded);
刷新Token #
双Token机制 #
typescript
@Injectable()
export class AuthService {
constructor(
private jwtService: JwtService,
private usersService: UsersService,
private refreshTokenService: RefreshTokenService,
) {}
async login(user: any) {
const payload = { sub: user.id, email: user.email };
const accessToken = this.jwtService.sign(payload, { expiresIn: '15m' });
const refreshToken = this.jwtService.sign(payload, { expiresIn: '7d' });
await this.refreshTokenService.save(user.id, refreshToken);
return {
access_token: accessToken,
refresh_token: refreshToken,
};
}
async refreshToken(refreshToken: string) {
const storedToken = await this.refreshTokenService.find(refreshToken);
if (!storedToken) {
throw new UnauthorizedException('Invalid refresh token');
}
try {
const payload = this.jwtService.verify(refreshToken);
const user = await this.usersService.findOne(payload.sub);
const newAccessToken = this.jwtService.sign(
{ sub: user.id, email: user.email },
{ expiresIn: '15m' },
);
return { access_token: newAccessToken };
} catch {
throw new UnauthorizedException('Invalid refresh token');
}
}
}
刷新Token存储 #
typescript
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { RefreshToken } from './entities/refresh-token.entity';
@Injectable()
export class RefreshTokenService {
constructor(
@InjectRepository(RefreshToken)
private tokenRepository: Repository<RefreshToken>,
) {}
async save(userId: number, token: string) {
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7);
const refreshToken = this.tokenRepository.create({
userId,
token,
expiresAt,
});
return this.tokenRepository.save(refreshToken);
}
async find(token: string) {
return this.tokenRepository.findOne({
where: { token },
});
}
async revoke(token: string) {
await this.tokenRepository.delete({ token });
}
async revokeAll(userId: number) {
await this.tokenRepository.delete({ userId });
}
}
Token黑名单 #
typescript
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { BlacklistedToken } from './entities/blacklisted-token.entity';
@Injectable()
export class TokenBlacklistService {
constructor(
@InjectRepository(BlacklistedToken)
private blacklistRepository: Repository<BlacklistedToken>,
) {}
async addToBlacklist(token: string, expiresAt: Date) {
const blacklisted = this.blacklistRepository.create({
token,
expiresAt,
});
await this.blacklistRepository.save(blacklisted);
}
async isBlacklisted(token: string): Promise<boolean> {
const count = await this.blacklistRepository.count({
where: { token },
});
return count > 0;
}
async cleanupExpired() {
await this.blacklistRepository
.createQueryBuilder()
.delete()
.where('expiresAt < :now', { now: new Date() })
.execute();
}
}
修改JWT Guard:
typescript
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(
private reflector: Reflector,
private blacklistService: TokenBlacklistService,
) {
super();
}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const token = this.extractToken(request);
if (token && await this.blacklistService.isBlacklisted(token)) {
throw new UnauthorizedException('Token has been revoked');
}
return super.canActivate(context) as Promise<boolean>;
}
private extractToken(request: Request): string | undefined {
const authorization = request.headers.authorization;
if (!authorization) return undefined;
const [type, token] = authorization.split(' ');
return type === 'Bearer' ? token : undefined;
}
}
登出实现 #
typescript
@Controller('auth')
export class AuthController {
constructor(
private authService: AuthService,
private blacklistService: TokenBlacklistService,
) {}
@Post('logout')
@UseGuards(JwtAuthGuard)
async logout(@Request() req) {
const token = req.headers.authorization?.split(' ')[1];
const decoded = this.jwtService.decode(token) as any;
await this.blacklistService.addToBlacklist(
token,
new Date(decoded.exp * 1000),
);
return { message: 'Successfully logged out' };
}
}
JWT最佳实践 #
1. 安全存储密钥 #
typescript
// .env
JWT_SECRET=your-super-secret-key-at-least-32-characters-long
JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d
2. 使用强密钥 #
typescript
import * as crypto from 'crypto';
const secret = crypto.randomBytes(64).toString('hex');
3. 短过期时间 #
typescript
signOptions: {
expiresIn: '15m', // 短过期时间
}
4. HTTPS传输 #
确保生产环境使用HTTPS。
5. 不存储敏感信息 #
typescript
// 不好的做法
const payload = {
sub: user.id,
password: user.password, // 不要存储密码
creditCard: user.creditCard, // 不要存储敏感信息
};
// 好的做法
const payload = {
sub: user.id,
email: user.email,
};
总结 #
本章学习了NestJS JWT认证:
- JWT模块配置
- Token生成和验证
- 刷新Token机制
- Token黑名单
- 登出实现
- JWT最佳实践
接下来,让我们学习 权限控制。
最后更新:2026-03-28