NestJS 认证与授权:JWT、OAuth 和 RBAC 实现

server/2024/12/29 2:58:48/

在上一篇文章中,我们介绍了 NestJS 的数据库操作和 TypeORM 集成。本文将深入探讨如何在 NestJS 中实现完整的认证和授权系统。

JWT 认证实现

1. 安装依赖

npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
npm install -D @types/passport-jwt @types/bcrypt

2. JWT 策略配置

// src/auth/strategies/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
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,roles: payload.roles };}
}// src/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 { JwtStrategy } from './strategies/jwt.strategy';
import { AuthService } from './auth.service';@Module({imports: [PassportModule.register({ defaultStrategy: 'jwt' }),JwtModule.registerAsync({imports: [ConfigModule],useFactory: async (configService: ConfigService) => ({secret: configService.get('JWT_SECRET'),signOptions: { expiresIn: '1d',issuer: 'nestjs-app'},}),inject: [ConfigService],}),],providers: [AuthService, JwtStrategy],exports: [AuthService],
})
export class AuthModule {}

3. 认证服务实现

// src/auth/auth.service.ts
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, password: string): Promise<any> {const user = await this.usersService.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,roles: user.roles };return {access_token: this.jwtService.sign(payload),user: {id: user.id,username: user.username,email: user.email,roles: user.roles}};}async register(createUserDto: CreateUserDto) {// 检查用户是否已存在const existingUser = await this.usersService.findByUsername(createUserDto.username);if (existingUser) {throw new ConflictException('Username already exists');}// 加密密码const hashedPassword = await bcrypt.hash(createUserDto.password, 10);// 创建新用户const newUser = await this.usersService.create({...createUserDto,password: hashedPassword,});// 返回用户信息和令牌const { password, ...result } = newUser;return this.login(result);}
}

4. 认证控制器

// src/auth/auth.controller.ts
import { Controller, Post, Body, UseGuards, Get } from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
import { GetUser } from './decorators/get-user.decorator';@Controller('auth')
export class AuthController {constructor(private authService: AuthService) {}@Post('login')async login(@Body() loginDto: LoginDto) {const user = await this.authService.validateUser(loginDto.username,loginDto.password);if (!user) {throw new UnauthorizedException('Invalid credentials');}return this.authService.login(user);}@Post('register')async register(@Body() createUserDto: CreateUserDto) {return this.authService.register(createUserDto);}@UseGuards(JwtAuthGuard)@Get('profile')getProfile(@GetUser() user: any) {return user;}
}

OAuth2.0 集成

1. Google OAuth2 实现

// src/auth/strategies/google.strategy.ts
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {constructor(private configService: ConfigService) {super({clientID: configService.get('GOOGLE_CLIENT_ID'),clientSecret: configService.get('GOOGLE_CLIENT_SECRET'),callbackURL: 'http://localhost:3000/auth/google/callback',scope: ['email', 'profile'],});}async validate(accessToken: string,refreshToken: string,profile: any,done: VerifyCallback,): Promise<any> {const { name, emails, photos } = profile;const user = {email: emails[0].value,firstName: name.givenName,lastName: name.familyName,picture: photos[0].value,accessToken,};done(null, user);}
}// src/auth/controllers/oauth.controller.ts
import { Controller, Get, UseGuards, Req } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';@Controller('auth/google')
export class OAuthController {@Get()@UseGuards(AuthGuard('google'))async googleAuth(@Req() req) {}@Get('callback')@UseGuards(AuthGuard('google'))async googleAuthRedirect(@Req() req) {// 处理 Google 认证回调return this.authService.googleLogin(req.user);}
}

2. GitHub OAuth2 实现

// src/auth/strategies/github.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-github2';
import { ConfigService } from '@nestjs/config';@Injectable()
export class GithubStrategy extends PassportStrategy(Strategy, 'github') {constructor(private configService: ConfigService) {super({clientID: configService.get('GITHUB_CLIENT_ID'),clientSecret: configService.get('GITHUB_CLIENT_SECRET'),callbackURL: 'http://localhost:3000/auth/github/callback',scope: ['user:email'],});}async validate(accessToken: string,refreshToken: string,profile: any,done: Function,) {const user = {githubId: profile.id,username: profile.username,email: profile.emails[0].value,accessToken,};done(null, user);}
}

RBAC 权限控制

1. 角色定义

// src/auth/enums/role.enum.ts
export enum Role {USER = 'user',ADMIN = 'admin',MODERATOR = 'moderator',
}// src/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);

2. 角色守卫

// src/auth/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role } from '../enums/role.enum';
import { ROLES_KEY } from '../decorators/roles.decorator';@Injectable()
export class RolesGuard implements CanActivate {constructor(private reflector: Reflector) {}canActivate(context: ExecutionContext): boolean {const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [context.getHandler(),context.getClass(),]);if (!requiredRoles) {return true;}const { user } = context.switchToHttp().getRequest();return requiredRoles.some((role) => user.roles?.includes(role));}
}

3. 权限实体设计

// src/auth/entities/permission.entity.ts
import { Entity, Column, ManyToMany } from 'typeorm';
import { BaseEntity } from '../../common/entities/base.entity';
import { Role } from '../enums/role.enum';@Entity('permissions')
export class Permission extends BaseEntity {@Column()name: string;@Column()description: string;@Column('simple-array')allowedRoles: Role[];
}// src/users/entities/user.entity.ts
import { Entity, Column, ManyToMany, JoinTable } from 'typeorm';
import { Role } from '../../auth/enums/role.enum';
import { Permission } from '../../auth/entities/permission.entity';@Entity('users')
export class User extends BaseEntity {// ... 其他字段@Column('simple-array')roles: Role[];@ManyToMany(() => Permission)@JoinTable({name: 'user_permissions',joinColumn: { name: 'user_id' },inverseJoinColumn: { name: 'permission_id' },})permissions: Permission[];
}

4. 权限检查服务

// src/auth/services/permission.service.ts
import { Injectable, ForbiddenException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Permission } from '../entities/permission.entity';
import { User } from '../../users/entities/user.entity';@Injectable()
export class PermissionService {constructor(@InjectRepository(Permission)private permissionRepository: Repository<Permission>,) {}async checkPermission(user: User, permissionName: string): Promise<boolean> {const permission = await this.permissionRepository.findOne({where: { name: permissionName },});if (!permission) {throw new ForbiddenException('Permission not found');}// 检查用户角色是否有权限return permission.allowedRoles.some(role => user.roles.includes(role));}async grantPermission(user: User, permissionName: string): Promise<void> {const permission = await this.permissionRepository.findOne({where: { name: permissionName },});if (!permission) {throw new ForbiddenException('Permission not found');}user.permissions = [...user.permissions, permission];await this.userRepository.save(user);}
}

5. 使用示例

// src/posts/posts.controller.ts
import { Controller, Post, Body, 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';
import { GetUser } from '../auth/decorators/get-user.decorator';
import { User } from '../users/entities/user.entity';
import { PostsService } from './posts.service';
import { CreatePostDto } from './dto/create-post.dto';@Controller('posts')
@UseGuards(JwtAuthGuard, RolesGuard)
export class PostsController {constructor(private postsService: PostsService,private permissionService: PermissionService,) {}@Post()@Roles(Role.USER)async createPost(@GetUser() user: User,@Body() createPostDto: CreatePostDto,) {// 检查用户是否有创建文章的权限const hasPermission = await this.permissionService.checkPermission(user,'create_post');if (!hasPermission) {throw new ForbiddenException('You do not have permission to create posts');}return this.postsService.create(user, createPostDto);}@Post('publish')@Roles(Role.MODERATOR, Role.ADMIN)async publishPost(@GetUser() user: User,@Body('postId') postId: string,) {return this.postsService.publish(user, postId);}
}

安全最佳实践

1. 密码加密

// src/common/utils/crypto.util.ts
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';export class CryptoUtil {static async hashPassword(password: string): Promise<string> {const salt = await bcrypt.genSalt(10);return bcrypt.hash(password, salt);}static async comparePasswords(password: string,hashedPassword: string,): Promise<boolean> {return bcrypt.compare(password, hashedPassword);}static generateRandomToken(length: number = 32): string {return crypto.randomBytes(length).toString('hex');}
}

2. 请求限流

// src/common/guards/throttle.guard.ts
import { Injectable } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {protected getTracker(req: Record<string, any>): string {return req.ips.length ? req.ips[0] : req.ip;}
}// src/app.module.ts
import { Module } from '@nestjs/common';
import { ThrottlerModule } from '@nestjs/throttler';@Module({imports: [ThrottlerModule.forRoot({ttl: 60,limit: 10,}),],
})
export class AppModule {}

3. 安全头部配置

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as helmet from 'helmet';async function bootstrap() {const app = await NestFactory.create(AppModule);// 安全头部app.use(helmet());// CORS 配置app.enableCors({origin: process.env.ALLOWED_ORIGINS.split(','),methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],credentials: true,});await app.listen(3000);
}
bootstrap();

写在最后

本文详细介绍了 NestJS 中的认证与授权实现:

  1. JWT 认证的完整实现
  2. OAuth2.0 社交登录集成
  3. RBAC 权限控制系统
  4. 安全最佳实践

在下一篇文章中,我们将探讨 NestJS 的中间件与拦截器实现。

如果觉得这篇文章对你有帮助,别忘了点个赞 👍


http://www.ppmy.cn/server/154081.html

相关文章

JAVA没有搞头了吗?

前言 今年的Java程序员群体似乎承受着前所未有的焦虑。投递简历无人问津&#xff0c;难得的面试机会也难以把握&#xff0c;即便成功入职&#xff0c;也往往难以长久。于是&#xff0c;不少程序员感叹&#xff1a;互联网的寒冬似乎又一次卷土重来&#xff0c;环境如此恶劣&…

探秘 Chrome 隐藏配置项:chrome://net-internals

Chrome浏览器中的chrome://net-internals/页面是一个强大的内置工具&#xff0c;提供了监视和调试网络请求与事件的详细功能。 一、chrome://net-internals/#events 地址&#xff1a;chrome://net-internals/#events 这个页面用于监视和调试网络请求和事件。通过它&#xff…

华为仓颉编程语言的应用案例分析

一、华为仓颉语言简介 1.1 仓颉语言的设计背景 华为仓颉&#xff08;Cangjie&#xff09;语言是华为自主研发的新一代编程语言&#xff0c;旨在满足分布式系统和高并发场景下的编程需求。其设计初衷是解决传统开发模式中难以处理复杂分布式任务、效率低下以及协作困难的问题。…

Windows电脑异地SSH远程连接苹果MacOS小主机Mac mini详细教程

文章目录 前言1. macOS打开远程登录2. 局域网内测试ssh远程3. 公网ssh远程连接macOS3.1 macOS安装配置cpolar3.2 获取ssh隧道公网地址3.3 测试公网ssh远程连接macOS 4. 配置公网固定TCP地址4.1 保留一个固定TCP端口地址4.2 配置固定TCP端口地址 5. 使用固定TCP端口地址ssh远程 …

国标GB28181-2022平台EasyGBS:安防监控中P2P的穿透方法

在安防监控领域&#xff0c;P2P技术因其去中心化的特性而受到关注&#xff0c;尤其是在远程视频监控和数据传输方面。P2P技术允许设备之间直接通信&#xff0c;无需通过中央服务器&#xff0c;这在提高效率和降低成本方面具有明显优势。然而&#xff0c;P2P技术在实际应用中也面…

再生核希尔伯特空间(RKHS)上的分位回归

1. 基本定义和理论基础 1.1 再生核希尔伯特空间(RKHS) 给定一个非空集合 X \mathcal{X} X&#xff0c;一个希尔伯特空间 H \mathcal{H} H 称为再生核希尔伯特空间&#xff0c;如果存在一个函数 K : X X → R K: \mathcal{X} \times \mathcal{X} \rightarrow \mathbb{R} K…

JVM简介—3.JVM的执行子系统

大纲 1.Class文件结构 2.Class文件格式概述 3.Class文件格式详解 4.字节码指令 5.类的生命周期和初始化 6.类加载的全过程 7.类加载器 8.双亲委派模型 9.栈桢详解 11.方法调用详解 12.基于栈的字节码解释执行引擎 1.Class文件结构 (1)Java跨平台的基础 字节码是各…

【每日学点鸿蒙知识】混淆配置、主线程处理大量数据、客户端拖拽效果、三方网站加载样式、List警告问题

1、HarmonyOS API升级之后缺少混淆配置文件&#xff1f; 可参考以下文档&#xff1a; 混淆配置&#xff1a;https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-build-obfuscation-V5 混淆规则&#xff1a;https://gitee.com/openharmony/arkcompiler_ets…