8. 守卫:Nest.js 的请求授权工具
介绍
欢迎回来!在前几篇文章中,我们已经了解了如何创建控制器、服务、中间件、管道和异常过滤器,并使用它们来处理 HTTP 请求。在这篇文章中,我们将探讨 Nest.js 中的守卫(Guards)。守卫是处理请求授权的强大工具,可以在请求到达控制器之前检查用户的权限。让我们一起深入了解守卫的工作原理和使用方法。
什么是守卫?
守卫是一个类,它实现了 CanActivate
接口,并包含一个 canActivate
方法。canActivate
方法接收两个参数:执行上下文和当前请求对象。守卫可以用于检查用户的权限,并决定是否允许请求继续进行。
创建一个守卫
让我们通过一个实际例子来了解如何创建和使用守卫。假设我们要创建一个身份验证守卫,用于检查请求是否包含有效的身份验证令牌。
首先,使用 Nest CLI 创建一个新的守卫:
bash
nest generate guard auth
这条命令会在 src
目录下生成一个 auth.guard.ts
文件。让我们看看这个文件的内容:
typescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
@Injectable()
:将类标记为可注入的服务。canActivate
方法:守卫的核心方法,接收执行上下文和当前请求对象。
使用守卫进行身份验证
让我们修改 AuthGuard
,在 canActivate
方法中添加身份验证逻辑。假设我们要检查请求头中是否包含 Authorization
令牌。
修改 auth.guard.ts
文件如下:
typescript
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
const authHeader = request.headers.authorization;
if (!authHeader) {
throw new UnauthorizedException('Authorization header is missing');
}
// 在这里添加令牌验证逻辑
const token = authHeader.split(' ')[1];
if (!this.validateToken(token)) {
throw new UnauthorizedException('Invalid token');
}
return true;
}
private validateToken(token: string): boolean {
// 在这里添加令牌验证逻辑
return token === 'valid-token'; // 示例逻辑
}
}
在这个例子中,我们检查请求头中是否包含 Authorization
令牌,并验证令牌的有效性。如果令牌无效,我们抛出一个 UnauthorizedException
异常。
应用守卫
要使用守卫,我们需要将其应用到特定的路由或全局范围内。让我们将 AuthGuard
应用到 BooksController
的所有路由。
打开 books.controller.ts
文件,修改如下:
typescript
import { Controller, Get, Post, Put, Delete, Param, Body, UseGuards } from '@nestjs/common';
import { BooksService } from './books.service';
import { CreateBookDto } from './create-book.dto';
import { AuthGuard } from './auth.guard';
@Controller('books')
@UseGuards(AuthGuard)
export class BooksController {
constructor(private readonly booksService: BooksService) {}
@Get()
findAll(): string {
return this.booksService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string): string {
return this.booksService.findOne(id);
}
@Post()
create(@Body() createBookDto: CreateBookDto): string {
return this.booksService.create(createBookDto);
}
@Put(':id')
update(@Param('id') id: string, @Body() updateBookDto: CreateBookDto): string {
return this.booksService.update(id, updateBookDto);
}
@Delete(':id')
remove(@Param('id') id: string): string {
return this.booksService.remove(id);
}
}
在这个例子中,我们使用 @UseGuards
装饰器将 AuthGuard
应用到 BooksController
。每次请求到达 BooksController
的方法之前,都会先经过 AuthGuard
。
全局守卫
如果你希望守卫应用于所有路由,可以将其注册为全局守卫。打开 main.ts
文件,修改如下:
typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AuthGuard } from './auth.guard';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new AuthGuard());
await app.listen(3000);
}
bootstrap();
在这个例子中,我们使用 app.useGlobalGuards
方法将 AuthGuard
注册为全局守卫。这样,所有的请求都会经过 AuthGuard
。
角色守卫
有时,我们可能需要根据用户的角色进行授权。让我们创建一个新的守卫,用于检查用户是否具有特定角色。
首先,使用 Nest CLI 创建一个新的守卫:
bash
nest generate guard roles
然后,修改 roles.guard.ts
文件如下:
typescript
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
const hasRole = () => user.roles.some((role) => roles.includes(role));
if (!user || !user.roles || !hasRole()) {
throw new ForbiddenException('You do not have the required roles');
}
return true;
}
}
在这个例子中,我们使用 Reflector
获取路由处理器上的角色元数据,并检查用户是否具有这些角色。
要使用这个角色守卫,我们需要定义一个自定义装饰器,用于将角色元数据添加到路由处理器上。
创建一个新的文件 roles.decorator.ts
,内容如下:
typescript
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
然后,修改 books.controller.ts
文件,应用角色守卫和自定义装饰器:
typescript
import { Controller, Get, Post, Put, Delete, Param, Body, UseGuards } from '@nestjs/common';
import { BooksService } from './books.service';
import { CreateBookDto } from './create-book.dto';
import { AuthGuard } from './auth.guard';
import { RolesGuard } from './roles.guard';
import { Roles } from './roles.decorator';
@Controller('books')
@UseGuards(AuthGuard, RolesGuard)
export class BooksController {
constructor(private readonly booksService: BooksService) {}
@Get()
@Roles('admin')
findAll(): string {
return this.booksService.findAll();
}
@Get(':id')
@Roles('admin', 'user')
findOne(@Param('id') id: string): string {
return this.booksService.findOne(id);
}
@Post()
@Roles('admin')
create(@Body() createBookDto: CreateBookDto): string {
return this.booksService.create(createBookDto);
}
@Put(':id')
@Roles('admin')
update(@Param('id') id: string, @Body() updateBookDto: CreateBookDto): string {
return this.booksService.update(id, updateBookDto);
}
@Delete(':id')
@Roles('admin')
remove(@Param('id') id: string): string {
return this.booksService.remove(id);
}
}
在这个例子中,我们使用 @Roles
装饰器将角色元数据添加到路由处理器上,并使用 @UseGuards
装饰器将 RolesGuard
应用到 BooksController
。每次请求到达 BooksController
的方法之前,都会先经过 AuthGuard
和 RolesGuard
。
结论
在这篇文章中,我们深入探讨了 Nest.js 中的守卫,并通过实际例子展示了如何创建和使用守卫。我们还学习了如何将守卫应用到特定的路由或全局范围内,以及如何根据用户的角色进行授权。
守卫是处理请求授权的强大工具,可以在请求到达控制器之前检查用户的权限。通过使用守卫,我们可以确保只有具有适当权限的用户才能访问特定的资源,使应用更加安全和可靠。
感谢你的阅读!如果你有任何问题或建议,欢迎在评论区留言。我们下次再见!
预告
在下一篇文章中,我们将探讨 Nest.js 中的拦截器(Interceptors)。拦截器是处理请求和响应的强大工具,可以在请求到达控制器之前或响应发送到客户端之前执行一些操作。敬请期待!