<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 授權(Authorization) 授權是指確定一個用戶可以做什么的過程。例如,管理員用戶可以創建、編輯和刪除文章,非管理員用戶只能授權閱讀文章。 授權和認證是相互獨立的。但是授權需要依賴認證機制。 有很多方法和策略來處理權限。這些方法取決于其應用程序的特定需求。本章提供了一些可以靈活運用在不同需求條件下的權限實現方式。 ### 基礎的RBAC實現 基于角色的訪問控制(**RBAC**)是一個基于角色和權限等級的中立的訪問控制策略。本節通過使用`Nest`[守衛](https://docs.nestjs.com/guards)來實現一個非常基礎的`RBAC`。 首先創建一個`Role`枚舉來表示系統中的角色: > role.enum.ts ```TypeScript export enum Role { User = 'user', Admin = 'admin', } ``` > 在更復雜的系統中,角色信息可能會存儲在數據庫里,或者從一個外部認證提供者那里獲取。 有了這個,我們可以創建一個`@Roles()`的裝飾器,該裝飾器允許某些角色擁有獲取特定資源訪問權。 > roles.decorator.ts ```TypeScript 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); ``` 現在可以將`@Roles()`裝飾器應用于任何路徑處理程序。 >cats.controller.ts ```TypeScript @Post() @Roles(Role.Admin) create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } ``` 最后,我們創建一個`RolesGuard`類來比較當前用戶擁有的角色和當前路徑需要的角色。為了獲取路徑的角色(自定義元數據),我們使用`Reflector`輔助類,這是個`@nestjs/core`提供的一個開箱即用的類。 > 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 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)); } } ``` > 參見[應用上下文](https://docs.nestjs.com/fundamentals/execution-context#reflection-and-metadata)章節的反射與元數據部分,了解在上下文敏感的環境中使用`Reflector`的細節。 > 該例子被稱為“基礎的”,是因為我們僅僅在路徑處理層面檢查了用戶權限。在實際項目中,你可能有包含不同操作的終端/處理程序,它們各自需要不同的權限組合。在這種情況下,你可能要在你的業務邏輯中提供一個機制來檢查角色,這在一定程度上會變得難以維護,因為缺乏一個集中的地方來關聯不同的操作與權限。 在這個例子中,我們假設`request.user`包含用戶實例以及允許的角色(在`roles`屬性中)。在你的應用中,需要將其與你的認證守衛關聯起來,參見[認證](#認證(Authentication))。 要確保該示例可以工作,你的`User`類看上去應該像這樣: ```TypeScript class User { // ...other properties roles: Role[]; } ``` 最后,在控制層或者全局注冊`RolesGuard`。 ```TypeScript providers: [ { provide: APP_GUARD, useClass: RolesGuard, }, ], ``` 當一個沒有有效權限的用戶訪問一個終端時,Nest自動返回以下響應: ```JSON { "statusCode": 403, "message": "Forbidden resource", "error": "Forbidden" } ``` > 如果你想返回一個不同的錯誤響應,你應該拋出你自己的特定異常而不是返回一個布爾值。 ### 基于權利(Claims)的權限 一個身份被創建后,可能關聯來來自信任方的一個或者多個權利。權利是指一個表示對象可以做什么,而不是對象是什么的鍵值對。 要在Nest中實現基于權利的權限,你可以參考我們在`RBAC`部分的步驟,僅僅有一個顯著區別:比較`許可(permissions)`而不是角色。每個用戶應該被授予了一組許可,相似地,每個資源/終端都應該定義其需要的許可(例如通過專屬的`@RequirePermissions()`裝飾器)。 > cats.controller.ts ```TypeScript @Post() @RequirePermissions(Permission.CREATE_CAT) create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } ``` > 在這個例子中,`Permission`(和RBAC部分的`角色`類似)是一個TypeScript的枚舉,它包含了系統中所有的許可。 ### 與`CASL`集成 `CASL`是一個權限庫,用于限制用戶可以訪問哪些資源。它被設計為可漸進式增長的,從基礎權利權限到完整的基于主題和屬性的權限都可以實現。 首先,安裝`@casl/ability`包: ```bash $ npm i @casl/ability ``` > 在本例中,我們選擇`CASL`,但也可以根據項目需要選擇其他類似庫例如`accesscontrol`或者`acl`。 安裝完成后,為了說明CASL的機制,我們定義了兩個類實體,`User`和`Article`。 ```TypeScript class User { id: number; isAdmin: boolean; } ``` `User`類包含兩個屬性,`id`是用戶的唯一標識,`isAdmin`代表用戶是否有管理員權限。 ```TypeScript class Article { id: number; isPublished: boolean; authorId: number; } ``` `Article`類包含三個屬性,分別是`id`、`isPublished`和`authorId`,`id`是文章的唯一標識,`isPublished`代表文章是否發布,`authorId`代表發表該文章的用戶id。 接下來回顧并確定本示例中的需求: - 管理員可以管理(創建、閱讀、更新、刪除/CRUD)所有實體 - 用戶對所有內容有閱讀權限 - 用戶可以更新自己的文章(`article.authorId===userId`) - 已發布的文章不能被刪除 (`article.isPublised===true`) 基于這些需求,我們開始創建`Action`枚舉,包含了用戶可能對實體的所有操作。 ```TypeScript export enum Action { Manage = 'manage', Create = 'create', Read = 'read', Update = 'update', Delete = 'delete', } ``` > `manage`是CASL的關鍵詞,代表`任何`操作。 要封裝CASL庫,需要創建`CaslModule`和`CaslAbilityFactory`。 ```bash $ nest g module casl $ nest g class casl/casl-ability.factory ``` 創建完成后,在`CaslAbilityFactory`中定義`createForUser()`方法。該方法將為用戶創建`Ability`對象。 ```TypeScript type Subjects = InferSubjects<typeof Article | typeof User> | 'all'; export type AppAbility = Ability<[Action, Subjects]>; @Injectable() export class CaslAbilityFactory { createForUser(user: User) { const { can, cannot, build } = new AbilityBuilder< Ability<[Action, Subjects]> >(Ability as AbilityClass<AppAbility>); if (user.isAdmin) { can(Action.Manage, 'all'); // read-write access to everything } else { can(Action.Read, 'all'); // read-only access to everything } can(Action.Update, Article, { authorId: user.id }); cannot(Action.Delete, Article, { isPublished: true }); return build({ // Read https://casl.js.org/v5/en/guide/subject-type-detection#use-classes-as-subject-types for details detectSubjectType: item => item.constructor as ExtractSubjectType<Subjects> }); } } ``` > `all`是CASL的關鍵詞,代表`任何對象`。 > `Ability`,`AbilityBuilder`,和`AbilityClass`從`@casl/ability`包中導入。 在上述例子中,我們使用`AbilityBuilder`創建了`Ability`實例,如你所見,`can`和`cannot`接受同樣的參數,但代表不同含義,`can`允許對一個對象執行操作而`cannot`禁止操作,它們各能接受4個參數,參見[CASL文檔](https://casl.js.org/v4/en/guide/intro)。 最后,將`CaslAbilityFactory`添加到提供者中,并在`CaslModule`模塊中導出。 ```TypeScript import { Module } from '@nestjs/common'; import { CaslAbilityFactory } from './casl-ability.factory'; @Module({ providers: [CaslAbilityFactory], exports: [CaslAbilityFactory], }) export class CaslModule {} ``` 現在,只要將`CaslModule`引入對象的上下文中,就可以將`CaslAbilityFactory`注入到任何標準類中。 ```TypeScript constructor(private caslAbilityFactory: CaslAbilityFactory) {} ``` 在類中使用如下: ```TypeScript const ability = this.caslAbilityFactory.createForUser(user); if (ability.can(Action.Read, 'all')) { // "user" has read access to everything } ``` > `Ability`類更多細節參見[CASL 官方文檔](https://casl.js.org/v4/en/guide/intro)。 例如,一個非管理員用戶,應該可以閱讀文章,但不允許創建一篇新文章或者刪除一篇已有文章。 ```TypeScript const user = new User(); user.isAdmin = false; const ability = this.caslAbilityFactory.createForUser(user); ability.can(Action.Read, Article); // true ability.can(Action.Delete, Article); // false ability.can(Action.Create, Article); // false ``` > 雖然`Ability`和`AlbilityBuilder`類都提供`can`和`cannot`方法,但其目的并不一樣,接受的參數也略有不同。 依照我們的需求,一個用戶應該能更新自己的文章。 ```TypeScript const user = new User(); user.id = 1; const article = new Article(); article.authorId = user.id; const ability = this.caslAbilityFactory.createForUser(user); ability.can(Action.Update, article); // true article.authorId = 2; ability.can(Action.Update, article); // false ``` 如你所見,`Ability`實例允許我們通過一種可讀的方式檢查許可。`AbilityBuilder`采用類似的方式允許我們定義許可(并定義不同條件)。查看官方文檔了解更多示例。 ### 進階:通過策略守衛的實現 本節我們說明如何聲明一個更復雜的守衛,用來配置在方法層面(也可以配置在類層面)檢查用戶是否滿足權限策略。在本例中,將使用CASL包進行說明,但它并不是必須的。同樣,我們將使用前節創建的`CaslAbilityFactory`提供者。 首先更新我們的需求。目的是提供一個機制來檢查每個路徑處理程序的特定權限。我們將同時支持對象和方法(分別針對簡易檢查和面向函數式編程的目的)。 從定義接口和策略處理程序開始。 ```TypeScript import { AppAbility } from '../casl/casl-ability.factory'; interface IPolicyHandler { handle(ability: AppAbility): boolean; } type PolicyHandlerCallback = (ability: AppAbility) => boolean; export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback; ``` 如上所述,我們提供了兩個可能的定義策略處理程序的方式,一個對象(實現了`IPolicyHandle`接口的類的實例)和一個函數(滿足`PolicyHandlerCallback`類型)。 接下來創建一個`@CheckPolicies()`裝飾器,該裝飾器允許配置訪問特定資源需要哪些權限。 ```TypeScript export const CHECK_POLICIES_KEY = 'check_policy'; export const CheckPolicies = (...handlers: PolicyHandler[]) => SetMetadata(CHECK_POLICIES_KEY, handlers); ``` 現在創建一個`PoliciesGuard`,它將解析并執行所有和路徑相關的策略程序。 ```TypeScript @Injectable() export class PoliciesGuard implements CanActivate { constructor( private reflector: Reflector, private caslAbilityFactory: CaslAbilityFactory, ) {} async canActivate(context: ExecutionContext): Promise<boolean> { const policyHandlers = this.reflector.get<PolicyHandler[]>( CHECK_POLICIES_KEY, context.getHandler(), ) || []; const { user } = context.switchToHttp().getRequest(); const ability = this.caslAbilityFactory.createForUser(user); return policyHandlers.every((handler) => this.execPolicyHandler(handler, ability), ); } private execPolicyHandler(handler: PolicyHandler, ability: AppAbility) { if (typeof handler === 'function') { return handler(ability); } return handler.handle(ability); } } ``` > 在本例中,我們假設`request.user`包含了用戶實例。在你的應用中,可能將其與你自定義的認證守衛關聯。參見認證章節。 我們分析一下這個例子。`policyHandlers`是一個通過`@CheckPolicies()`裝飾器傳遞給方法的數組,接下來,我們用`CaslAbilityFactory#create`方法創建`Ability`對象,允許我們確定一個用戶是否擁有足夠的許可去執行特定行為。我們將這個對象傳遞給一個可能是函數或者實現了`IPolicyHandler`類的實例的策略處理程序,暴露出`handle()`方法并返回一個布爾量。最后,我們使用`Array#every`方法來確保所有處理程序返回`true`。 為了測試這個守衛,我們綁定任意路徑處理程序,并且注冊一個行內的策略處理程序(函數實現),如下: ```TypeScript @Get() @UseGuards(PoliciesGuard) @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, Article)) findAll() { return this.articlesService.findAll(); } ``` 我們也可以定義一個實現了`IPolicyHandler`的類來代替函數。 ```TypeScript export class ReadArticlePolicyHandler implements IPolicyHandler { handle(ability: AppAbility) { return ability.can(Action.Read, Article); } } ``` 并這樣使用。 ```TypeScript @Get() @UseGuards(PoliciesGuard) @CheckPolicies(new ReadArticlePolicyHandler()) findAll() { return this.articlesService.findAll(); } ``` > 由于我們必須使用 `new`關鍵詞來實例化一個策略處理函數,`CreateArticlePolicyHandler`類不能使用注入依賴。這在`ModuleRef#get`方法中強調過,參見[這里](8/fundamentals.md#依賴注入))。基本上,要替代通過`@CheckPolicies()`裝飾器注冊函數和實例,你需要允許傳遞一個`Type<IPolicyHandler>`,然后在守衛中使用一個類型引用(`moduleRef.get(YOUR_HANDLER_TYPE`)獲取實例,或者使用`ModuleRef#create`方法進行動態實例化。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看