Nestjs教程使用JWT进行无状态认证
在使用NestJS进行JWT无状态认证时遇到几个问题:
- 如何在NestJS中正确配置JWT模块?官方文档的示例感觉不够详细
- 生成Token时应该包含哪些标准字段?除了sub和exp还需要加什么?
- 前端收到Token后应该存在哪里?LocalStorage和Cookie哪种方式更安全?
- 如何优雅地处理Token过期情况?目前只会强制跳转到登录页
- 刷新Token的机制应该如何实现?看到有说用双Token方案的但不太理解具体流程
- 为什么我的守卫有时候会跳过验证?是不是和路由顺序有关?
- 在生产环境部署时需要注意哪些JWT相关的安全事项?
3 回复
在NestJS中使用JWT实现无状态认证是一种常见的方式。以下是基本步骤:
- 安装必要的依赖:
npm install @nestjs/jwt @nestjs/passport passport passport-jwt jsonwebtoken
- 创建一个AuthModule,在该模块中配置JWT策略:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret: 'your-secret-key', // 设置密钥
signOptions: { expiresIn: '60m' }, // 设置过期时间
}),
],
})
export class AuthModule {}
- 创建一个JWT策略文件,例如 jwt.strategy.ts:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'your-secret-key',
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
- 在控制器中实现登录和验证逻辑,生成JWT令牌并返回给客户端。
通过这种方式,你可以实现基于JWT的无状态认证,客户端每次请求时只需携带JWT令牌即可完成身份验证。
-
安装依赖:首先安装
[@nestjs](/user/nestjs)/jwt
和[@nestjs](/user/nestjs)/passport
等相关包。npm install [@nestjs](/user/nestjs)/jwt [@nestjs](/user/nestjs)/passport passport passport-jwt --save
-
配置Passport:创建一个服务用于验证 JWT,例如
auth.service.ts
。import { Injectable } from '[@nestjs](/user/nestjs)/common'; import * as jwt from 'jsonwebtoken'; [@Injectable](/user/Injectable)() export class AuthService { private readonly jwtSecret = 'your_jwt_secret'; async signToken(payload: any): Promise<string> { return jwt.sign(payload, this.jwtSecret, { expiresIn: '1h' }); } async validateUser(payload: any): Promise<any> { // 验证用户逻辑 return payload; } }
-
创建策略:创建一个 JWT 策略文件
jwt.strategy.ts
。import { Injectable } from '[@nestjs](/user/nestjs)/common'; import { PassportStrategy } from '[@nestjs](/user/nestjs)/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; [@Injectable](/user/Injectable)() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: 'your_jwt_secret', }); } async validate(payload: any) { return { userId: payload.sub, username: payload.username }; } }
-
设置路由和控制器:在控制器中使用 JWT 进行登录和验证。
import { Controller, Post, Body, Request, UseGuards } from '[@nestjs](/user/nestjs)/common'; import { AuthService } from './auth.service'; import { AuthGuard } from '[@nestjs](/user/nestjs)/passport'; [@Controller](/user/Controller)('auth') export class AuthController { constructor(private authService: AuthService) {} @Post('login') async login(@Body() body: any) { const user = await this.authService.validateUser(body); if (!user) throw new Error('Invalid credentials'); const token = await this.authService.signToken(user); return { access_token: token }; } @Post('protected') @UseGuards(AuthGuard('jwt')) getProfile(@Request() req) { return req.user; } }
-
测试:通过登录接口获取 token,并在请求头中携带
Authorization: Bearer <token>
测试受保护的接口。
NestJS中使用JWT进行无状态认证
基本配置
- 首先安装必要的依赖包:
npm install @nestjs/jwt passport passport-jwt
npm install @types/passport-jwt --save-dev
- 创建JWT模块和策略:
// 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 './jwt.strategy';
@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret: 'your-secret-key', // 在实际应用中应使用环境变量
signOptions: { expiresIn: '1h' },
}),
],
providers: [AuthService, JwtStrategy],
exports: [AuthService, JwtStrategy],
})
export class AuthModule {}
JWT策略实现
// jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'your-secret-key',
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
认证服务
// auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(private readonly jwtService: JwtService) {}
async login(user: any) {
const payload = { username: user.username, sub: user.userId };
return {
access_token: this.jwtService.sign(payload),
};
}
}
保护路由
// your.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller('profile')
export class ProfileController {
@UseGuards(AuthGuard('jwt'))
@Get()
getProfile() {
return { message: 'Protected route' };
}
}
注意事项
- 在实际应用中,应该使用环境变量来存储JWT密钥
- 适当设置JWT过期时间
- 考虑使用Refresh Token机制
- 在生产环境中应使用HTTPS传输JWT
以上是NestJS中使用JWT进行无状态认证的基本实现。根据实际需求可以进一步扩展和完善。