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

打印 上一主题 下一主题

主题 1613|帖子 1613|积分 4839

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
在上一篇文章中,我们先容了 NestJS 的数据库操纵和 TypeORM 集成。本文将深入探究如安在 NestJS 中实现完备的认证和授权体系。
JWT 认证实现

1. 安装依赖

  1. npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
  2. npm install -D @types/passport-jwt @types/bcrypt
复制代码
2. JWT 战略配置

  1. // src/auth/strategies/jwt.strategy.ts
  2. import { Injectable } from '@nestjs/common';
  3. import { PassportStrategy } from '@nestjs/passport';
  4. import { ExtractJwt, Strategy } from 'passport-jwt';
  5. import { ConfigService } from '@nestjs/config';
  6. @Injectable()
  7. export class JwtStrategy extends PassportStrategy(Strategy) {
  8.   constructor(private configService: ConfigService) {
  9.     super({
  10.       jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  11.       ignoreExpiration: false,
  12.       secretOrKey: configService.get('JWT_SECRET'),
  13.     });
  14.   }
  15.   async validate(payload: any) {
  16.     return {
  17.       userId: payload.sub,
  18.       username: payload.username,
  19.       roles: payload.roles
  20.     };
  21.   }
  22. }
  23. // src/auth/auth.module.ts
  24. import { Module } from '@nestjs/common';
  25. import { JwtModule } from '@nestjs/jwt';
  26. import { PassportModule } from '@nestjs/passport';
  27. import { ConfigModule, ConfigService } from '@nestjs/config';
  28. import { JwtStrategy } from './strategies/jwt.strategy';
  29. import { AuthService } from './auth.service';
  30. @Module({
  31.   imports: [
  32.     PassportModule.register({ defaultStrategy: 'jwt' }),
  33.     JwtModule.registerAsync({
  34.       imports: [ConfigModule],
  35.       useFactory: async (configService: ConfigService) => ({
  36.         secret: configService.get('JWT_SECRET'),
  37.         signOptions: {
  38.           expiresIn: '1d',
  39.           issuer: 'nestjs-app'
  40.         },
  41.       }),
  42.       inject: [ConfigService],
  43.     }),
  44.   ],
  45.   providers: [AuthService, JwtStrategy],
  46.   exports: [AuthService],
  47. })
  48. export class AuthModule {}
复制代码
3. 认证服务实现

  1. // src/auth/auth.service.ts
  2. import { Injectable, UnauthorizedException } from '@nestjs/common';
  3. import { JwtService } from '@nestjs/jwt';
  4. import { UsersService } from '../users/users.service';
  5. import * as bcrypt from 'bcrypt';
  6. @Injectable()
  7. export class AuthService {
  8.   constructor(
  9.     private usersService: UsersService,
  10.     private jwtService: JwtService,
  11.   ) {}
  12.   async validateUser(username: string, password: string): Promise<any> {
  13.     const user = await this.usersService.findByUsername(username);
  14.     if (user && await bcrypt.compare(password, user.password)) {
  15.       const { password, ...result } = user;
  16.       return result;
  17.     }
  18.     return null;
  19.   }
  20.   async login(user: any) {
  21.     const payload = {
  22.       username: user.username,
  23.       sub: user.id,
  24.       roles: user.roles
  25.     };
  26.     return {
  27.       access_token: this.jwtService.sign(payload),
  28.       user: {
  29.         id: user.id,
  30.         username: user.username,
  31.         email: user.email,
  32.         roles: user.roles
  33.       }
  34.     };
  35.   }
  36.   async register(createUserDto: CreateUserDto) {
  37.     // 检查用户是否已存在
  38.     const existingUser = await this.usersService.findByUsername(
  39.       createUserDto.username
  40.     );
  41.     if (existingUser) {
  42.       throw new ConflictException('Username already exists');
  43.     }
  44.     // 加密密码
  45.     const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
  46.     // 创建新用户
  47.     const newUser = await this.usersService.create({
  48.       ...createUserDto,
  49.       password: hashedPassword,
  50.     });
  51.     // 返回用户信息和令牌
  52.     const { password, ...result } = newUser;
  53.     return this.login(result);
  54.   }
  55. }
复制代码
4. 认证控制器

  1. // src/auth/auth.controller.ts
  2. import { Controller, Post, Body, UseGuards, Get } from '@nestjs/common';
  3. import { AuthService } from './auth.service';
  4. import { JwtAuthGuard } from './guards/jwt-auth.guard';
  5. import { GetUser } from './decorators/get-user.decorator';
  6. @Controller('auth')
  7. export class AuthController {
  8.   constructor(private authService: AuthService) {}
  9.   @Post('login')
  10.   async login(@Body() loginDto: LoginDto) {
  11.     const user = await this.authService.validateUser(
  12.       loginDto.username,
  13.       loginDto.password
  14.     );
  15.     if (!user) {
  16.       throw new UnauthorizedException('Invalid credentials');
  17.     }
  18.     return this.authService.login(user);
  19.   }
  20.   @Post('register')
  21.   async register(@Body() createUserDto: CreateUserDto) {
  22.     return this.authService.register(createUserDto);
  23.   }
  24.   @UseGuards(JwtAuthGuard)
  25.   @Get('profile')
  26.   getProfile(@GetUser() user: any) {
  27.     return user;
  28.   }
  29. }
复制代码
OAuth2.0 集成

1. Google OAuth2 实现

  1. // src/auth/strategies/google.strategy.ts
  2. import { PassportStrategy } from '@nestjs/passport';
  3. import { Strategy, VerifyCallback } from 'passport-google-oauth20';
  4. import { Injectable } from '@nestjs/common';
  5. import { ConfigService } from '@nestjs/config';
  6. @Injectable()
  7. export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  8.   constructor(private configService: ConfigService) {
  9.     super({
  10.       clientID: configService.get('GOOGLE_CLIENT_ID'),
  11.       clientSecret: configService.get('GOOGLE_CLIENT_SECRET'),
  12.       callbackURL: 'http://localhost:3000/auth/google/callback',
  13.       scope: ['email', 'profile'],
  14.     });
  15.   }
  16.   async validate(
  17.     accessToken: string,
  18.     refreshToken: string,
  19.     profile: any,
  20.     done: VerifyCallback,
  21.   ): Promise<any> {
  22.     const { name, emails, photos } = profile;
  23.     const user = {
  24.       email: emails[0].value,
  25.       firstName: name.givenName,
  26.       lastName: name.familyName,
  27.       picture: photos[0].value,
  28.       accessToken,
  29.     };
  30.     done(null, user);
  31.   }
  32. }
  33. // src/auth/controllers/oauth.controller.ts
  34. import { Controller, Get, UseGuards, Req } from '@nestjs/common';
  35. import { AuthGuard } from '@nestjs/passport';
  36. @Controller('auth/google')
  37. export class OAuthController {
  38.   @Get()
  39.   @UseGuards(AuthGuard('google'))
  40.   async googleAuth(@Req() req) {}
  41.   @Get('callback')
  42.   @UseGuards(AuthGuard('google'))
  43.   async googleAuthRedirect(@Req() req) {
  44.     // 处理 Google 认证回调
  45.     return this.authService.googleLogin(req.user);
  46.   }
  47. }
复制代码
2. GitHub OAuth2 实现

  1. // src/auth/strategies/github.strategy.ts
  2. import { Injectable } from '@nestjs/common';
  3. import { PassportStrategy } from '@nestjs/passport';
  4. import { Strategy } from 'passport-github2';
  5. import { ConfigService } from '@nestjs/config';
  6. @Injectable()
  7. export class GithubStrategy extends PassportStrategy(Strategy, 'github') {
  8.   constructor(private configService: ConfigService) {
  9.     super({
  10.       clientID: configService.get('GITHUB_CLIENT_ID'),
  11.       clientSecret: configService.get('GITHUB_CLIENT_SECRET'),
  12.       callbackURL: 'http://localhost:3000/auth/github/callback',
  13.       scope: ['user:email'],
  14.     });
  15.   }
  16.   async validate(
  17.     accessToken: string,
  18.     refreshToken: string,
  19.     profile: any,
  20.     done: Function,
  21.   ) {
  22.     const user = {
  23.       githubId: profile.id,
  24.       username: profile.username,
  25.       email: profile.emails[0].value,
  26.       accessToken,
  27.     };
  28.     done(null, user);
  29.   }
  30. }
复制代码
RBAC 权限控制

1. 角色定义

  1. // src/auth/enums/role.enum.ts
  2. export enum Role {
  3.   USER = 'user',
  4.   ADMIN = 'admin',
  5.   MODERATOR = 'moderator',
  6. }
  7. // src/auth/decorators/roles.decorator.ts
  8. import { SetMetadata } from '@nestjs/common';
  9. import { Role } from '../enums/role.enum';
  10. export const ROLES_KEY = 'roles';
  11. export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
复制代码
2. 角色守卫

  1. // src/auth/guards/roles.guard.ts
  2. import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
  3. import { Reflector } from '@nestjs/core';
  4. import { Role } from '../enums/role.enum';
  5. import { ROLES_KEY } from '../decorators/roles.decorator';
  6. @Injectable()
  7. export class RolesGuard implements CanActivate {
  8.   constructor(private reflector: Reflector) {}
  9.   canActivate(context: ExecutionContext): boolean {
  10.     const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
  11.       context.getHandler(),
  12.       context.getClass(),
  13.     ]);
  14.     if (!requiredRoles) {
  15.       return true;
  16.     }
  17.     const { user } = context.switchToHttp().getRequest();
  18.     return requiredRoles.some((role) => user.roles?.includes(role));
  19.   }
  20. }
复制代码
3. 权限实体设计

  1. // src/auth/entities/permission.entity.ts
  2. import { Entity, Column, ManyToMany } from 'typeorm';
  3. import { BaseEntity } from '../../common/entities/base.entity';
  4. import { Role } from '../enums/role.enum';
  5. @Entity('permissions')
  6. export class Permission extends BaseEntity {
  7.   @Column()
  8.   name: string;
  9.   @Column()
  10.   description: string;
  11.   @Column('simple-array')
  12.   allowedRoles: Role[];
  13. }
  14. // src/users/entities/user.entity.ts
  15. import { Entity, Column, ManyToMany, JoinTable } from 'typeorm';
  16. import { Role } from '../../auth/enums/role.enum';
  17. import { Permission } from '../../auth/entities/permission.entity';
  18. @Entity('users')
  19. export class User extends BaseEntity {
  20.   // ... 其他字段
  21.   @Column('simple-array')
  22.   roles: Role[];
  23.   @ManyToMany(() => Permission)
  24.   @JoinTable({
  25.     name: 'user_permissions',
  26.     joinColumn: { name: 'user_id' },
  27.     inverseJoinColumn: { name: 'permission_id' },
  28.   })
  29.   permissions: Permission[];
  30. }
复制代码
4. 权限查抄服务

  1. // src/auth/services/permission.service.ts
  2. import { Injectable, ForbiddenException } from '@nestjs/common';
  3. import { InjectRepository } from '@nestjs/typeorm';
  4. import { Repository } from 'typeorm';
  5. import { Permission } from '../entities/permission.entity';
  6. import { User } from '../../users/entities/user.entity';
  7. @Injectable()
  8. export class PermissionService {
  9.   constructor(
  10.     @InjectRepository(Permission)
  11.     private permissionRepository: Repository<Permission>,
  12.   ) {}
  13.   async checkPermission(user: User, permissionName: string): Promise<boolean> {
  14.     const permission = await this.permissionRepository.findOne({
  15.       where: { name: permissionName },
  16.     });
  17.     if (!permission) {
  18.       throw new ForbiddenException('Permission not found');
  19.     }
  20.     // 检查用户角色是否有权限
  21.     return permission.allowedRoles.some(role => user.roles.includes(role));
  22.   }
  23.   async grantPermission(user: User, permissionName: string): Promise<void> {
  24.     const permission = await this.permissionRepository.findOne({
  25.       where: { name: permissionName },
  26.     });
  27.     if (!permission) {
  28.       throw new ForbiddenException('Permission not found');
  29.     }
  30.     user.permissions = [...user.permissions, permission];
  31.     await this.userRepository.save(user);
  32.   }
  33. }
复制代码
5. 利用示例

  1. // src/posts/posts.controller.ts
  2. import { Controller, Post, Body, UseGuards } from '@nestjs/common';
  3. import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
  4. import { RolesGuard } from '../auth/guards/roles.guard';
  5. import { Roles } from '../auth/decorators/roles.decorator';
  6. import { Role } from '../auth/enums/role.enum';
  7. import { GetUser } from '../auth/decorators/get-user.decorator';
  8. import { User } from '../users/entities/user.entity';
  9. import { PostsService } from './posts.service';
  10. import { CreatePostDto } from './dto/create-post.dto';
  11. @Controller('posts')
  12. @UseGuards(JwtAuthGuard, RolesGuard)
  13. export class PostsController {
  14.   constructor(
  15.     private postsService: PostsService,
  16.     private permissionService: PermissionService,
  17.   ) {}
  18.   @Post()
  19.   @Roles(Role.USER)
  20.   async createPost(
  21.     @GetUser() user: User,
  22.     @Body() createPostDto: CreatePostDto,
  23.   ) {
  24.     // 检查用户是否有创建文章的权限
  25.     const hasPermission = await this.permissionService.checkPermission(
  26.       user,
  27.       'create_post'
  28.     );
  29.     if (!hasPermission) {
  30.       throw new ForbiddenException('You do not have permission to create posts');
  31.     }
  32.     return this.postsService.create(user, createPostDto);
  33.   }
  34.   @Post('publish')
  35.   @Roles(Role.MODERATOR, Role.ADMIN)
  36.   async publishPost(
  37.     @GetUser() user: User,
  38.     @Body('postId') postId: string,
  39.   ) {
  40.     return this.postsService.publish(user, postId);
  41.   }
  42. }
复制代码
安全最佳实践

1. 密码加密

  1. // src/common/utils/crypto.util.ts
  2. import * as bcrypt from 'bcrypt';
  3. import * as crypto from 'crypto';
  4. export class CryptoUtil {
  5.   static async hashPassword(password: string): Promise<string> {
  6.     const salt = await bcrypt.genSalt(10);
  7.     return bcrypt.hash(password, salt);
  8.   }
  9.   static async comparePasswords(
  10.     password: string,
  11.     hashedPassword: string,
  12.   ): Promise<boolean> {
  13.     return bcrypt.compare(password, hashedPassword);
  14.   }
  15.   static generateRandomToken(length: number = 32): string {
  16.     return crypto.randomBytes(length).toString('hex');
  17.   }
  18. }
复制代码
2. 请求限流

  1. // src/common/guards/throttle.guard.ts
  2. import { Injectable } from '@nestjs/common';
  3. import { ThrottlerGuard } from '@nestjs/throttler';
  4. @Injectable()
  5. export class CustomThrottlerGuard extends ThrottlerGuard {
  6.   protected getTracker(req: Record<string, any>): string {
  7.     return req.ips.length ? req.ips[0] : req.ip;
  8.   }
  9. }
  10. // src/app.module.ts
  11. import { Module } from '@nestjs/common';
  12. import { ThrottlerModule } from '@nestjs/throttler';
  13. @Module({
  14.   imports: [
  15.     ThrottlerModule.forRoot({
  16.       ttl: 60,
  17.       limit: 10,
  18.     }),
  19.   ],
  20. })
  21. export class AppModule {}
复制代码
3. 安全头部配置

  1. // src/main.ts
  2. import { NestFactory } from '@nestjs/core';
  3. import { AppModule } from './app.module';
  4. import * as helmet from 'helmet';
  5. async function bootstrap() {
  6.   const app = await NestFactory.create(AppModule);
  7.   // 安全头部
  8.   app.use(helmet());
  9.   // CORS 配置
  10.   app.enableCors({
  11.     origin: process.env.ALLOWED_ORIGINS.split(','),
  12.     methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  13.     credentials: true,
  14.   });
  15.   await app.listen(3000);
  16. }
  17. bootstrap();
复制代码
写在最后

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

  • JWT 认证的完备实现
  • OAuth2.0 社交登录集成
  • RBAC 权限控制体系
  • 安全最佳实践
在下一篇文章中,我们将探究 NestJS 的中间件与拦截器实现。
假如以为这篇文章对你有资助,别忘了点个赞
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

缠丝猫

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表