从 0 到 1 学习 NestJS (五) - 身份验证与授权 (Passport.js + JWT)
前言
在前面的文章中,我们学习了 NestJS 的核心概念、数据库集成以及如何使用 TypeORM。本篇将深入探讨如何在 NestJS 应用中实现身份验证和授权,我们将使用 Passport.js 作为身份验证中间件,并使用 JWT (JSON Web Tokens) 来管理用户会话。
1. 安装必要的依赖
首先,我们需要安装 Passport.js、JWT 和相关依赖:
1 2 3
| npm install @nestjs/passport passport passport-jwt jsonwebtoken bcrypt @types/passport-jwt
yarn add @nestjs/passport passport passport-jwt jsonwebtoken bcrypt @types/passport-jwt
|
@nestjs/passport: NestJS 官方提供的 Passport.js 集成模块。
passport: Passport.js 本身。
passport-jwt: Passport.js 的 JWT 策略。
jsonwebtoken: 用于生成和验证 JWT 的库。
bcrypt: 用于密码哈希。
@types/passport-jwt: Passport-JWT 的 TypeScript 类型定义。
创建身份验证模块
创建 auth 模块
使用 Nest CLI 创建一个新的模块:
创建 auth.service.ts
在 src/auth 目录下创建一个 auth.service.ts 文件,并添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import { Injectable, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { UsersService } from '../users/users.service'; import * as bcrypt from 'bcrypt';
@Injectable() export class AuthService { constructor( private usersService: UsersService, private jwtService: JwtService, ) {}
async validateUser(username: string, pass: string): Promise<any> { const user = await this.usersService.findOneByUsername(username); if (user && await bcrypt.compare(pass, user.password)) { const { password, ...result } = user; return result; } return null; }
async login(user: any) { const payload = { username: user.username, sub: user.id }; return { access_token: this.jwtService.sign(payload), }; } }
|
validateUser(): 验证用户名和密码,成功返回用户信息。
login(): 生成 JWT。
创建 jwt.strategy.ts
在 src/auth 目录下创建一个 jwt.strategy.ts 文件,并添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 }; } }
|
JwtStrategy: 定义 JWT 验证策略。
secretOrKey: 用于验证 JWT 的密钥,请替换为更安全的密钥。
validate(): 验证 JWT 并返回用户信息。
创建 auth.module.ts
在 src/auth 目录下创建一个 auth.module.ts 文件,并添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { UsersModule } from '../users/users.module'; import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt.strategy';
@Module({ imports: [ UsersModule, PassportModule, JwtModule.register({ secret: 'your_secret_key', signOptions: { expiresIn: '60s' }, }), ], providers: [AuthService, JwtStrategy], exports: [AuthService], }) export class AuthModule {}
|
JwtModule.register(): 配置 JWT 模块,包括密钥和过期时间。
providers: 注册 AuthService 和 JwtStrategy。
exports: 导出 AuthService,以便其他模块可以使用。
在 UsersModule 中添加用户注册和登录
修改 users.service.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity'; import * as bcrypt from 'bcrypt';
@Injectable() export class UsersService { constructor( @InjectRepository(User) private usersRepository: Repository<User>, ) {}
async create(user: Partial<User>): Promise<User> { const hashedPassword = await bcrypt.hash(user.password, 10); const newUser = this.usersRepository.create({...user, password: hashedPassword}); return await this.usersRepository.save(newUser); }
async findOneByUsername(username: string): Promise<User | undefined> { return await this.usersRepository.findOne({ where: { username } }); } }
|
添加 findOneByUsername() 方法用于查找用户。
使用 bcrypt 对密码进行哈希。
修改 users.controller.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import { Controller, Post, Body, UseGuards, Get, Request } from '@nestjs/common'; import { UsersService } from './users.service'; import { CreateUserDto } from './dto/create-user.dto'; import { AuthService } from '../auth/auth.service'; import { JwtAuthGuard } from '../auth/jwt-auth.guard';
@Controller('users') export class UsersController { constructor( private readonly usersService: UsersService, private authService: AuthService ) {}
@Post('register') async register(@Body() createUserDto: CreateUserDto) { return await this.usersService.create(createUserDto); }
@Post('login') async login(@Body() user: any) { return await this.authService.login(user); }
@UseGuards(JwtAuthGuard) @Get('profile') getProfile(@Request() req) { return req.user; } }
|
添加 register 路由用于用户注册。
添加 login 路由用于用户登录。
添加 profile 路由,使用 JwtAuthGuard 保护,需要 JWT 验证。
创建 jwt-auth.guard.ts
在 src/auth 目录下创建一个 jwt-auth.guard.ts 文件,并添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport';
@Injectable() export class JwtAuthGuard extends AuthGuard('jwt') { canActivate(context: ExecutionContext) { return super.canActivate(context); }
handleRequest(err, user, info) { if (err || !user) { throw err || new UnauthorizedException(); } return user; } }
|
4. 测试
现在,你可以运行 npm run start:dev 或 yarn start:dev,然后使用 Postman 或其他工具测试 API:
POST /users/register: 注册一个新用户,请求体为:{"username": "testuser", "password": "testpassword", "email": "test@example.com"}。如果注册成功,你应该会收到一个包含新用户信息的响应。
POST /users/login: 使用注册的用户名和密码登录,请求体为:{"username": "testuser", "password": "testpassword"}。如果登录成功,你应该会收到一个包含 access_token 的 JSON 响应。
GET /users/profile: 使用上一步获取的 access_token 发起请求。在 Postman 中,将 Authorization 请求头设置为 Bearer <access_token>(将 <access_token> 替换为实际的 token)。如果验证成功,你应该会收到包含用户信息的响应。
总结
在本篇文章中,我们学习了如何在 NestJS 应用中实现身份验证和授权,使用 Passport.js 和 JWT。我们了解了如何创建 Passport 策略、生成和验证 JWT,以及如何使用 Guard 来保护路由。
通过本篇的学习,你已经掌握了在 NestJS 中实现基本的身份验证和授权的方法,可以为你的应用添加安全机制。