在上一篇文章中,我们介绍了 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 中的认证与授权实现:
- JWT 认证的完整实现
- OAuth2.0 社交登录集成
- RBAC 权限控制系统
- 安全最佳实践
在下一篇文章中,我们将探讨 NestJS 的中间件与拦截器实现。
如果觉得这篇文章对你有帮助,别忘了点个赞 👍