# 守衛
守衛是一個使用 `@Injectable()` 裝飾器的類。 它實現了`CanActivate`接口。

守衛有一個單獨的責任。它們根據運行時出現的某些條件(例如權限,角色,訪問控制列表等)來確定給定的請求是否由路由處理程序處理。 這通常稱為授權。在傳統的 `Express` 應用程序中,通常由[中間件](https://docs.nestjs.com/middleware)處理授權。中間件是身份驗證的最佳選擇。到目前為止,訪問限制邏輯大多在中間件內。這樣很好,因為諸如 `token` 驗證或將 `request` 對象附加屬性與特定路由沒有強關聯。
但是,就其本質而言,中間件是愚蠢的。中間件不知道調用 `next()` 函數后會執行哪個處理程序。另一方面,**Guards**可以訪問 `ExecutionContext` 實例,因此確切地知道接下來要執行什么。它們的設計與異常過濾器、管道和攔截器非常相似,目的是讓您在請求/響應周期的正確位置插入處理邏輯,并以聲明的方式進行插入。這有助于保持代碼的簡潔和聲明性。
> 守衛在每個中間件**之后執行,但在**任何攔截器或管道之前執行。
## 授權守衛
正如前面提到的,授權是保護的一個很好的用例,因為只有當調用者(通常是經過身份驗證的特定用戶)具有足夠的權限時,特定的路由才可用。我們現在要構建的 `AuthGuard` 假設用戶是經過身份驗證的(因此,請求頭附加了一個`token`)。它將提取和驗證`token`,并使用提取的信息來確定請求是否可以繼續。
> 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> {
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
```
>**提示**:如果您正在尋找有關如何在應用程序中實現身份驗證機制的真實示例,請訪問[本章](https://docs.nestjs.com/security/authentication)。同樣,對于更復雜的授權示例,請查看[此頁面](https://docs.nestjs.com/security/authorization)。
`validateRequest()` 函數中的邏輯可以根據需要變得簡單或復雜。本例的主要目的是說明保護如何適應請求/響應周期。
每個守衛必須實現一個canActivate()函數。此函數應該返回一個布爾值,指示是否允許當前請求。它可以同步或異步地返回響應(通過 `Promise` 或 `Observable`)。`Nest`使用返回值來控制下一個行為:
- 如果返回 `true`, 將處理用戶調用請求。
- 如果返回 `false`, 則 `Nest` 將忽略當前處理的請求。
## 執行上下文
`canActivate()` 函數接收單個參數,即 `ExecutionContext` 實例。`ExecutionContext` 繼承自 `ArgumentsHost` 。`ArgumentsHost` 是傳遞給原始處理程序的參數的包裝器,在上面的示例中,我們只是使用了之前在 `ArgumentsHost`上定義的幫助器方法來獲得對請求對象的引用。有關此主題的更多信息。你可以在[異常過濾器一章的](https://docs.nestjs.com/exception-filters#arguments-host)了解到更多。
通過擴展`ArgumentsHost`,`ExecutionContext`還添加了幾個新的輔助方法,這些方法提供了有關當前執行過程的更多詳細信息。這些細節有助于構建更通用的防護,這些防護可以在廣泛的控制器、方法和執行上下文中工作。`ExecutionContext`[在此處](https://docs.nestjs.com/fundamentals/execution-context)了解更多信息。
```typescript
export interface ExecutionContext extends ArgumentsHost {
getClass<T = any>(): Type<T>;
getHandler(): Function;
}
```
`getHandler()`方法返回對將要調用的處理程序的引用。`getClass()`方法返回這個特定處理程序所屬的 `Controller` 類的類型。例如,如果當前處理的請求是 `POST` 請求,目標是 `CatsController`上的 `create()` 方法,那么 `getHandler()` 將返回對 `create()` 方法的引用,而 `getClass()`將返回一個`CatsControllertype`(而不是實例)。
## 基于角色認證
一個更詳細的例子是一個 `RolesGuard` 。這個守衛只允許具有特定角色的用戶訪問。我們將從一個基本模板開始,并在接下來的部分中構建它。目前,它允許所有請求繼續:
> roles.guard.ts
```typescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
```
## 綁定守衛
與管道和異常過濾器一樣,守衛可以是控制范圍的、方法范圍的或全局范圍的。下面,我們使用 `@UseGuards()`裝飾器設置了一個控制范圍的守衛。這個裝飾器可以使用單個參數,也可以使用逗號分隔的參數列表。也就是說,你可以傳遞幾個守衛并用逗號分隔它們。這使您可以通過一個聲明輕松地應用適當的保護集。
```typescript
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
```
> `@UseGuards()` 裝飾器需要從 `@nestjs/common` 包導入。
上例,我們已經傳遞了 `RolesGuard` 類型(而不是實例), 將實例化的責任留給了框架并啟用了依賴注入。與管道和異常過濾器一樣,我們也可以傳遞一個實例:
```typescript
@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}
```
上面的構造將守衛附加到此控制器聲明的每個處理程序。如果我們決定只限制其中一個, 我們只需要在方法級別設置守衛。
為了設置全局守衛,請使用Nest應用程序實例的 `useGlobalGuards()` 方法:
```typescript
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
```
> 對于混合應用程序,`useGlobalGuards()` 方法不會為網關和微服務設置守衛。對于“標準”(非混合)微服務應用程序,`useGlobalGuards()`在全局安裝守衛。
全局守衛用于整個應用程序, 每個控制器和每個路由處理程序。在依賴注入方面, 從任何模塊外部注冊的全局守衛 (如上面的示例中所示) 不能插入依賴項, 因為它們不屬于任何模塊。為了解決此問題, 您可以使用以下構造直接從任何模塊設置一個守衛:
> app.module.ts
```typescript
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
```
> 當使用此方法為守衛程序執行依賴項注入時,請注意,無論使用此構造的模塊是什么,守衛程序實際上是全局的。應該在哪里進行?選擇定義守衛的模塊(上例中的 `RolesGuard`)。此外,`useClass`不是處理自定義 `providers` 注冊的唯一方法。在[這里](https://docs.nestjs.com/fundamentals/custom-providers)了解更多。
## 反射器
守衛現在在正常工作,但還不是很智能。我們仍然沒有利用最重要的守衛的特征,即`執行上下文`。它還不知道角色,或者每個處理程序允許哪些角色。例如,`CatsController` 可以為不同的路由提供不同的權限方案。其中一些可能只對管理用戶可用,而另一些則可以對所有人開放。我們如何以靈活和可重用的方式將角色與路由匹配起來?
這就是自定義元數據發揮作用的地方。`Nest`提供了通過 `@SetMetadata()` 裝飾器將定制元數據附加到路由處理程序的能力。這些元數據提供了我們所缺少的角色數據,而守衛需要這些數據來做出決策。讓我們看看使用`@SetMetadata()`:
> cats.controller.ts
```typescript
@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
```
> `@SetMetadata()` 裝飾器需要從 `@nestjs/common` 包導入。
通過上面的構建,我們將 `roles` 元數據(`roles` 是一個鍵,而 `['admin']` 是一個特定的值)附加到 `create()` 方法。 直接使用 `@SetMetadata()` 并不是一個好習慣。 相反,你應該創建你自己的裝飾器。如下所示:
> roles.decorator.ts
```typescript
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
```
這種方法更簡潔、更易讀,而且是強類型的。現在我們有了一個自定義的 `@Roles()` 裝飾器,我們可以使用它來裝飾 `create()`方法。
> cats.controller.ts
```typescript
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
```
讓我們再次回到 `RolesGuard` 。 它只是在所有情況下返回 `true`,到目前為止允許請求繼續。我們希望根據分配給當前用戶的角色與正在處理的當前路由所需的實際角色之間的比較來設置返回值的條件。 為了訪問路由的角色(自定義元數據),我們將使用在 `@nestjs/core` 中提供的 `Reflector` 幫助類。
> roles.guard.ts
```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 roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return matchRoles(roles, user.roles);
}
}
```
> 在 `node.js` 世界中,將授權用戶附加到 `request` 對象是一種常見的做法。 因此,在上面的示例代碼中。我們假設 `request.user` 包含用戶實例和允許的角色。 在您的應用中,您可能會在自定義身份驗證(或中間件)中建立該關聯。
> `matchRoles()` 函數內部的邏輯可以根據需要簡單或復雜。該示例的重點是顯示防護如何適應請求/響應周期。
有關以上下文相關方式進行利用的更多詳細信息,請參見“ 執行”上下文章節的“ 反射和元數據”部分。
當特權不足的用戶請求端點時,Nest自動返回以下響應:
```json
{
"statusCode": 403,
"message": "Forbidden resource"
}
```
請注意,當返回 `false` 時,守衛會拋出一個 `HttpException` 異常。如果您想要向最終用戶返回不同的錯誤響應,你應該拋出一個你自己的特定異常。
```typescript
throw new UnauthorizedException();
```
由守衛引發的任何異常都將由異常層(全局異常過濾器和應用于當前上下文的任何異常過濾器)處理。
**提示**:如果您正在尋找有關如何實現授權的真實示例,請查看[本章](https://docs.nestjs.com/security/authorization)。
- 介紹
- 概述
- 第一步
- 控制器
- 提供者
- 模塊
- 中間件
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 自定義裝飾器
- 基礎知識
- 自定義提供者
- 異步提供者
- 動態模塊
- 注入作用域
- 循環依賴
- 模塊參考
- 懶加載模塊
- 應用上下文
- 生命周期事件
- 跨平臺
- 測試
- 技術
- 數據庫
- Mongo
- 配置
- 驗證
- 緩存
- 序列化
- 版本控制
- 定時任務
- 隊列
- 日志
- Cookies
- 事件
- 壓縮
- 文件上傳
- 流式處理文件
- HTTP模塊
- Session(會話)
- MVC
- 性能(Fastify)
- 服務器端事件發送
- 安全
- 認證(Authentication)
- 授權(Authorization)
- 加密和散列
- Helmet
- CORS(跨域請求)
- CSRF保護
- 限速
- GraphQL
- 快速開始
- 解析器(resolvers)
- 變更(Mutations)
- 訂閱(Subscriptions)
- 標量(Scalars)
- 指令(directives)
- 接口(Interfaces)
- 聯合類型
- 枚舉(Enums)
- 字段中間件
- 映射類型
- 插件
- 復雜性
- 擴展
- CLI插件
- 生成SDL
- 其他功能
- 聯合服務
- 遷移指南
- Websocket
- 網關
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 適配器
- 微服務
- 概述
- Redis
- MQTT
- NATS
- RabbitMQ
- Kafka
- gRPC
- 自定義傳輸器
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 獨立應用
- Cli
- 概述
- 工作空間
- 庫
- 用法
- 腳本
- Openapi
- 介紹
- 類型和參數
- 操作
- 安全
- 映射類型
- 裝飾器
- CLI插件
- 其他特性
- 遷移指南
- 秘籍
- CRUD 生成器
- 熱重載
- MikroORM
- TypeORM
- Mongoose
- 序列化
- 路由模塊
- Swagger
- 健康檢查
- CQRS
- 文檔
- Prisma
- 靜態服務
- Nest Commander
- 問答
- Serverless
- HTTP 適配器
- 全局路由前綴
- 混合應用
- HTTPS 和多服務器
- 請求生命周期
- 常見錯誤
- 實例
- 遷移指南
- 發現
- 誰在使用Nest?