1. 前言
在上一篇文章中,我们实现了用户模块的基本功能。本文将介绍如何实现用户认证和授权功能,包括 JWT 认证、登录接口、权限控制等。
2. 安装依赖
首先安装认证相关的依赖:
pnpm add @nestjs/passport @nestjs/jwt passport passport-jwt
pnpm add -D @types/passport-jwt
3. 创建认证模块
使用 NestJS CLI 创建认证模块:
nest g module auth
nest g controller auth
nest g service auth
4. 实现 JWT 策略
在 src/modules/auth/strategies/jwt.strategy.ts
中创建 JWT 策略:
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('JWT_SECRET'),
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
5. 创建 JWT 守卫
在 src/modules/auth/guards/jwt-auth.guard.ts
中创建 JWT 守卫:
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
6. 实现认证服务
在 src/modules/auth/auth.service.ts
中实现认证服务:
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '../user/user.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private userService: UserService,
private jwtService: JwtService,
) {}
async validateUser(username: string, password: string): Promise<any> {
const user = await this.userService.findByUsername(username);
if (user && await bcrypt.compare(password, 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),
};
}
}
7. 实现认证控制器
在 src/modules/auth/auth.controller.ts
中实现认证控制器:
import { Controller, Post, Body, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
async login(@Body() loginDto: LoginDto) {
const user = await this.authService.validateUser(loginDto.username, loginDto.password);
if (!user) {
throw new UnauthorizedException('用户名或密码错误');
}
return this.authService.login(user);
}
}
8. 创建 DTO
在 src/modules/auth/dto
目录下创建 DTO 文件:
// login.dto.ts
import { IsString, MinLength } from 'class-validator';
export class LoginDto {
@IsString()
username: string;
@IsString()
@MinLength(6)
password: string;
}
9. 更新认证模块
在 src/modules/auth/auth.module.ts
中更新模块配置:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './strategies/jwt.strategy';
import { UserModule } from '../user/user.module';
@Module({
imports: [
UserModule,
PassportModule,
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('JWT_SECRET'),
signOptions: { expiresIn: '1d' },
}),
inject: [ConfigService],
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
10. 更新环境变量
在 .env
文件中添加 JWT 密钥:
JWT_SECRET=your-secret-key
11. 使用认证守卫
现在可以在需要保护的控制器方法上使用 @UseGuards(JwtAuthGuard)
装饰器:
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
@Controller('profile')
@UseGuards(JwtAuthGuard)
export class ProfileController {
@Get()
getProfile() {
return 'This is a protected route';
}
}
12. 测试认证功能
使用 Postman 测试认证功能:
- 登录获取 token
POST http://localhost:3000/auth/login
Content-Type: application/json
{
"username": "test",
"password": "123456"
}
- 使用 token 访问受保护的接口
GET http://localhost:3000/profile
Authorization: Bearer your-token
13. 实现角色权限控制
- 创建角色枚举
// src/modules/auth/enums/role.enum.ts
export enum Role {
User = 'user',
Admin = 'admin',
}
- 创建角色装饰器
// src/modules/auth/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { Role } from '../enums/role.enum';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
- 创建角色守卫
// src/modules/auth/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role } from '../enums/role.enum';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
- 使用角色守卫
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/decorators/roles.decorator';
import { Role } from '../auth/enums/role.enum';
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
@Get()
@Roles(Role.Admin)
getAdminData() {
return 'This is admin data';
}
}
14. 总结
本文介绍了如何:
- 实现 JWT 认证
- 创建登录接口
- 保护 API 路由
- 实现角色权限控制
15. 常见问题
-
JWT 令牌无效
- 检查令牌是否正确传递
- 验证 JWT_SECRET 是否正确配置
- 检查令牌是否过期
-
权限控制不生效
- 确保正确使用装饰器
- 检查用户角色是否正确设置
- 验证守卫是否正确配置
-
密码验证失败
- 检查密码加密方式
- 验证密码比较逻辑
- 确保数据库中的密码是加密的
16. 下一步
在下一篇文章中,我们将:
- 实现文件上传功能
- 实现文件下载功能
- 配置文件存储
- 实现文件管理接口