NestJS简明教程——安全

登录和注册(代码示例)

需要安装的依赖

bash 复制代码
pnpm add @nestjs/jwt @nestjs/throttler helmet  

auth.service.ts

ts 复制代码
import {
  ConflictException,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcrypt';
import { SignUpDto } from './dto/sign-up.dto';
import { SignInDto } from './dto/sign-in.dto';
import { NotFoundError } from '@mikro-orm/core';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(
    private readonly usersService: UsersService,
    private readonly jwtService: JwtService,
  ) {}

  async generateToken(user: any): Promise<{ access_token: string }> {
    const payload = { email: user.email, sub: user.id };
    return {
      access_token: await this.jwtService.signAsync(payload),
    };
  }

  async signUp(signUpDto: SignUpDto): Promise<any> {
    const { email, name, password } = signUpDto;

    const user = await this.usersService.findOne(email);
    if (user) {
      throw new ConflictException('User already exists');
    }

    const hashedPassword = await bcrypt.hash(password, 10);

    await this.usersService.create({ email, name, password: hashedPassword });

    return await this.usersService.findOne(email);
  }

  async signIn(signInDto: SignInDto): Promise<any> {
    const { email, password } = signInDto;

    const user = await this.usersService.findOne(email);
    if (!user) {
      throw new NotFoundError('User not found');
    }

    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      throw new UnauthorizedException('Invalid password');
    }

    const token = await this.generateToken(user);

    return { user, token };
  }
}

auth.controller.ts

ts 复制代码
import { Controller } from '@nestjs/common';
import { AuthService } from './auth.service';
import { Post, Body } from '@nestjs/common';
import { SignUpDto } from './dto/sign-up.dto';
import { SignInDto } from './dto/sign-in.dto';
import { Public } from './decorator/public.decorator';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('register')
  @Public()
  async signUp(@Body() signUpDto: SignUpDto): Promise<any> {
    return this.authService.signUp(signUpDto);
  }

  @Post('login')
  @Public()
  async signIn(@Body() signInDto: SignInDto): Promise<any> {
    return this.authService.signIn(signInDto);
  }

}

auth.module.ts

ts 复制代码
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module';
import { JwtModule } from '@nestjs/jwt';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';

@Module({
  imports: [
    UsersModule,
    JwtModule.register({
      secret: process.env.JWT_SECRET,
      signOptions: { expiresIn: '1h', algorithm: 'HS256' },
    }),
  ],
  providers: [
    AuthService,
    {
      provide: APP_GUARD,
      useClass: AuthGuard,
    },
  ],
  controllers: [AuthController],
})
export class AuthModule {}

JWT

auth.guard.ts

bash 复制代码
nest g guard auth/auth --no-spec --flat
ts 复制代码
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from './decorator/public.decorator';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private readonly jwtService: JwtService,
    private reflector: Reflector,
  ) {}

  private extractTokenFromHeader(request: any): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }

  async canActivate(context: ExecutionContext): Promise<boolean> {

    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
    ]);
    if (isPublic) {
      // 💡 See this condition
      return true;
    }

    // get the request object from the execution context
    const request = context.switchToHttp().getRequest();
    const token = this.extractTokenFromHeader(request);

    if (!token) {
      throw new UnauthorizedException('No token provided');
    }

    // verify the token using the JwtService
    try {
      const payload = await this.jwtService.verifyAsync(token);
      request['user'] = payload;
    } catch (error) {
      throw new UnauthorizedException('Invalid token');
    }

    return true;
  }
}

public.decorator.ts

bash 复制代码
nest g decorator auth/decorator/public --flat
ts 复制代码
import { SetMetadata } from '@nestjs/common';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

限流

app.module.ts

ts 复制代码
import { ThrottlerModule } from '@nestjs/throttler';
import throttlerConfig from './common/config/c';
ThrottlerModule.forRoot(throttlerConfig)

throttler.config.ts

复制代码
export default [
  {
    name: 'short',
    ttl: 1000,
    limit: 3,
  },
  {
    name: 'medium',
    ttl: 10000,
    limit: 20,
  },
  {
    name: 'long',
    ttl: 60000,
    limit: 100,
  },
];

其他

helmet

main.ts

ts 复制代码
// 加入 Helmet 中间件以增强安全性
  app.use(helmet());
相关推荐
程序员大阳1 小时前
HTML+CSS+JS对话式实战(5)-活动管理页之查看报名名单的实现
javascript·css·html·查看报名名单
新*4 小时前
从一段 WebSocket 代码聊开:bytes 不是 zlib、binaryType 决定一切,15MB 大消息的零拷贝优化
javascript·python·websocket·性能优化·blob·zlib·arraybuffer
David猪大卫5 小时前
【C++修炼】map 和 set 的使用
c语言·开发语言·数据结构·c++·经验分享·笔记·学习
OpsEye5 小时前
别再挨个充会员了:多模型编码,最麻烦的从来不是选模型
javascript·ai编程
fengci.6 小时前
CTF2MISC中等部分
开发语言·前端·学习
学以智用6 小时前
jsencrypt.js 完整详解
javascript
浪遏7 小时前
07|网页上看着正常,导出的 PDF 为什么还会变样
typescript
浪遏7 小时前
03|自动保存真正难的部分,是保存期间又发生了修改
typescript
浪遏7 小时前
04|同一篇资料,怎样同时交给编辑器、搜索和 AI
typescript
浪遏7 小时前
09|让 Mermaid 和 HTML 成为可编辑内容块
typescript