乐闻世界logo
搜索文章和话题

NestJS 安全和认证如何实现?

2月17日 22:41

安全性是任何应用程序的关键组成部分。NestJS 提供了多种安全机制,包括身份验证、授权、数据加密、防护措施等,帮助开发者构建安全可靠的应用程序。

身份验证(Authentication)

JWT 认证

安装依赖

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

创建 JWT 策略

typescript
import { Injectable, UnauthorizedException } 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 }; } }

创建认证服务

typescript
import { Injectable } 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.userId }; return { access_token: this.jwtService.sign(payload), }; } async register(username: string, password: string) { const hashedPassword = await bcrypt.hash(password, 10); return this.usersService.create({ username, password: hashedPassword }); } }

创建认证控制器

typescript
import { Controller, Post, Body, UseGuards, Request } from '@nestjs/common'; import { AuthService } from './auth.service'; import { LocalAuthGuard } from './local-auth.guard'; import { JwtAuthGuard } from './jwt-auth.guard'; @Controller('auth') export class AuthController { constructor(private authService: AuthService) {} @UseGuards(LocalAuthGuard) @Post('login') async login(@Request() req) { return this.authService.login(req.user); } @Post('register') async register(@Body() registerDto: { username: string; password: string }) { return this.authService.register(registerDto.username, registerDto.password); } @UseGuards(JwtAuthGuard) @Post('profile') getProfile(@Request() req) { return req.user; } }

本地认证策略

typescript
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-local'; import { AuthService } from './auth.service'; @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super({ usernameField: 'username', passwordField: 'password', }); } async validate(username: string, password: string): Promise<any> { const user = await this.authService.validateUser(username, password); if (!user) { throw new UnauthorizedException(); } return user; } }

OAuth2 认证

安装依赖

bash
npm install @nestjs/passport passport passport-google-oauth20 npm install -D @types/passport-google-oauth20

创建 Google OAuth 策略

typescript
import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy, VerifyCallback } from 'passport-google-oauth20'; 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: configService.get('GOOGLE_CALLBACK_URL'), 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); } }

授权(Authorization)

基于角色的访问控制(RBAC)

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

角色装饰器

typescript
import { SetMetadata } from '@nestjs/common'; export const ROLES_KEY = 'roles'; export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

使用角色守卫

typescript
@Controller('users') export class UsersController { @Get('admin') @UseGuards(JwtAuthGuard, RolesGuard) @Roles('admin') getAdminData() { return 'Admin data'; } }

密码加密

使用 bcrypt

typescript
import * as bcrypt from 'bcrypt'; async hashPassword(password: string): Promise<string> { const salt = await bcrypt.genSalt(10); return bcrypt.hash(password, salt); } async comparePassword(password: string, hash: string): Promise<boolean> { return bcrypt.compare(password, hash); }

安全中间件

Helmet

typescript
import helmet from 'helmet'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(helmet()); await app.listen(3000); }

CORS 配置

typescript
async function bootstrap() { const app = await NestFactory.create(AppModule); app.enableCors({ origin: 'https://example.com', methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', credentials: true, }); await app.listen(3000); }

速率限制

typescript
import rateLimit from 'express-rate-limit'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use( rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs }), ); await app.listen(3000); }

数据验证

使用 class-validator

typescript
import { IsEmail, IsString, MinLength } from 'class-validator'; export class CreateUserDto { @IsEmail() email: string; @IsString() @MinLength(6) password: string; }

全局验证管道

typescript
import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }), ); await app.listen(3000); }

SQL 注入防护

使用参数化查询

typescript
// 不安全 const query = `SELECT * FROM users WHERE name = '${name}'`; // 安全 const query = `SELECT * FROM users WHERE name = ?`; const result = await this.userRepository.query(query, [name]);

使用 ORM

typescript
// TypeORM 自动防止 SQL 注入 const user = await this.userRepository.findOne({ where: { name: username }, });

XSS 防护

输入清理

typescript
import * as xss from 'xss'; function sanitizeInput(input: string): string { return xss(input); }

内容安全策略(CSP)

typescript
import helmet from 'helmet'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use( helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"], }, }), ); await app.listen(3000); }

CSRF 防护

使用 csurf

typescript
import * as csurf from 'csurf'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(csurf({ cookie: true })); await app.listen(3000); }

安全最佳实践

  1. 使用 HTTPS:始终在生产环境中使用 HTTPS
  2. 环境变量:敏感信息存储在环境变量中
  3. 密码加密:使用 bcrypt 等安全算法加密密码
  4. 输入验证:验证所有用户输入
  5. 最小权限原则:用户只拥有必要的权限
  6. 定期更新依赖:保持依赖项更新以修复安全漏洞
  7. 日志记录:记录安全相关事件
  8. 错误处理:不向客户端暴露敏感错误信息
  9. 会话管理:使用安全的会话管理
  10. 安全头:设置适当的安全头

总结

NestJS 安全和认证提供了:

  • 多种身份验证策略
  • 灵活的授权机制
  • 强大的数据保护
  • 全面的安全防护
  • 易于集成的安全工具

掌握安全性和认证是构建安全、可靠的 NestJS 应用程序的关键。通过合理使用认证策略、授权机制和安全最佳实践,可以保护应用程序免受各种安全威胁,确保用户数据和系统的安全。

标签:NestJS