Nest.js 認証検証方法の例

Nest.js 認証検証方法の例

0x0 はじめに

システム認可とは、ログインしたユーザーが操作を実行するプロセスを指します。たとえば、管理者はシステム上でユーザー操作を実行したり、Web サイトの投稿を管理したりできますが、管理者以外のユーザーは投稿の許可された読み取りなどの操作を実行できます。したがって、システム認可を実装するには、ID 認証メカニズムが必要です。以下は、最も基本的なロールベースのアクセス制御システムの実装です。

0x1 RBAC 実装

ロールベースのアクセス制御 (RBAC) は、ロール権限や定義済みポリシーに依存しないアクセス制御メカニズムです。まず、システム ロール列挙情報を表す role.enum.ts ファイルを作成します。

エクスポート列挙型ロール{
 ユーザー = 'user'、
 管理者 = 'admin'
}

より複雑なシステムの場合は、管理を改善するためにロール情報をデータベースに保存することをお勧めします。

次に、デコレータを作成し、@Roles() を使用して、アクセスに必要な指定されたリソース ロールを実行します。roles.decorator.ts を作成します。

'@nestjs/common' から { SetMetadata } をインポートします。
'./role.enum' から { Role } をインポートします。

エクスポート const ROLES_KEY = 'roles'
エクスポート const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles)

上記は、@Roles() という名前のデコレータを作成します。これは、ユーザーの作成など、任意のルート コントローラを装飾するために使用できます。

@役職()
@Roles(ロール.管理者)
作成(@Body() createUserDto: CreateUserDto): Promise<UserEntity> {
 this.userService.create(createUserDto) を返します。
}

最後に、現在のユーザーに割り当てられているロールと現在のルーティング コントローラーに必要なロールを比較する RolesGuard クラスを作成します。ルーティング ロール (カスタム メタデータ) にアクセスするには、Reflector ツール クラスを使用します。新しい roles.guard.ts を作成します。

'@nestjs/common' から { Injectable、CanActivate、ExecutionContext } をインポートします。
'@nestjs/core' から { Reflector } をインポートします。

'./role.enum' から { Role } をインポートします。
'./roles.decorator' から { ROLES_KEY } をインポートします。

@インジェクタブル()
RolesGuardクラスをエクスポートし、CanActivateを実装します。
 コンストラクター(プライベートリフレクター: Reflector) {}

 canActivate(コンテキスト: ExecutionContext): ブール値 {
 const requireRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY、[context.getHandler(), context.getClass()])
 役割が必要な場合
  真を返す
 }
 const { ユーザー } = context.switchToHttp().getRequest()
 requireRoles.some(role => user.roles?.includes(role)) を返します
 }
}

request.user に roles 属性が含まれていると仮定します。

クラスユーザー{
 // ...その他のプロパティ
 役割: 役割[]
}

次に、RolesGuard がコントローラーにグローバルに登録されます。

プロバイダー:
 {
 提供: APP_GUARD、
 使用クラス: RolesGuard
 }
]

ユーザーがロールの範囲を超えたリクエストにアクセスする場合:

{
 「ステータスコード」: 403,
 "メッセージ": "禁止されたリソース",
 「エラー」: 「禁止」
}

0x2 クレームベースの承認

アイデンティティを作成した後、システムは 1 つ以上の宣言的権限をアイデンティティに割り当てることができます。これは、現在のユーザーが何であるかではなく、現在のユーザーに何をすべきかを指示することを意味します。Nest システムでは、宣言的承認は上記の RBAC と同様の方法で実装されていますが、違いがあります。特定のロールを判断するのではなく、権限を比較する必要があります。各ユーザーには、@RequirePermissions() デコレータを定義してから必要な権限属性にアクセスするなど、一連の権限が割り当てられます。

@役職()
@RequirePermissions(権限.CREATE_USER)
作成(@Body() createUserDto: CreateUserDto): Promise<UserEntity> {
 this.userService.create(createUserDto) を返します。
}

権限は、システムがアクセスできる権限グループを含む PRAC のロール列挙に似ています。

エクスポート列挙型ロール{
 CREATE_USER = ['追加'、'読み取り'、'更新'、'削除']、
 READ_USER = ['読み取り']
}

0x3 統合 CASL

CASL は、クライアントがアクセスするルーティング コントローラー リソースを制限できる同種認証ライブラリです。インストールの依存関係:

糸を追加 @casl/ability

以下は、CASL メカニズムを実装し、User と Article という 2 つのエンティティ クラスを作成する最も簡単な例です。

クラスユーザー{
 id: 番号
 isAdmin: ブール値
}

User エンティティ クラスには、ユーザー ID と、ユーザーに管理者権限があるかどうかという 2 つの属性があります。

クラス記事{
 id: 番号
 isPublished: ブール値
 著者ID: 文字列
}

Article エンティティ クラスには、記事番号、記事のステータス (公開されているかどうか)、記事を書いた著者番号という 3 つの属性があります。

上記の最も単純な 2 つの例に基づいて、最も単純な関数を作成できます。

  • 管理者権限を持つユーザーは、すべてのエンティティ(作成、読み取り、更新、削除)を管理できます。
  • ユーザーはすべてのコンテンツに対して読み取り専用アクセス権を持ちます
  • ユーザーは自分の記事を更新できます authorId === userId
  • 公開された記事は削除できません。article.isPublished === true

上記の関数では、エンティティに対するユーザーの操作を表す Action 列挙体を作成できます。

エクスポート列挙アクション{
 管理 = '管理'、
 作成 = '作成'、
 読む = '読む'、
 更新 = '更新'、
 削除 = '削除'、
}

Manage は CASL の特別なキーワードであり、任意の操作を実行できることを意味します。

関数を実装するには、CASL ライブラリを 2 回カプセル化する必要があります。必要なビジネスを作成するには、nest-cli を実行します。

ネスト g モジュール casl
ネスト g クラス casl/casl-ability.factory

ユーザーのオブジェクトを作成するには、CaslAbilityFactory の createForUser() メソッドを定義します。

type Subjects = InferSubjects<typeof Article | typeof User> | 'all'

エクスポートタイプ AppAbility = Ability<[Action, Subjects]>

@インジェクタブル()
CaslAbilityFactoryクラスをエクスポートします。
 ユーザーを作成します(ユーザー: ユーザー) {
 const { can, cannot, build } = new AbilityBuilder<
  能力<[アクション、対象]>
 >(Ability を AbilityClass<AppAbility> として)

 (ユーザー.isAdmin){
  can(Action.Manage, 'all') // すべての読み取りおよび書き込み操作を許可する } else {
  can(Action.Read, 'all') // 読み取り専用操作}

 できます(Action.Update、記事、{authorId:user.id})
 できません(Action.Delete、Article、{isPublished: true})

 ビルドを返す({
  // 詳細: https://casl.js.org/v5/en/guide/subject-type-detection#use-classes-as-subject-types
  対象タイプを検出: item => item.constructor を ExtractSubjectType<Subjects> として
 })
 }
}

次に、それを CaslModule にインポートします。

'@nestjs/common' から { モジュール } をインポートします。
'./casl-ability.factory' から { CaslAbilityFactory } をインポートします。

@モジュール({
 プロバイダー: [CaslAbilityFactory],
 エクスポート: [CaslAbilityFactory]
})
CaslModule クラスをエクスポートします {}

次に、CaslModule を任意のビジネスにインポートし、コンストラクターに挿入して使用します。

コンストラクター(プライベート caslAbilityFactory: CaslAbilityFactory) {}

定数 能力 = this.caslAbilityFactory.createForUser(ユーザー)
(能力がAction.Read、'すべて')の場合){
 // "user" はすべてのコンテンツを読み書きできます}

現在のユーザーが通常の権限を持つ管理者以外のユーザーである場合、記事を読むことはできますが、新しい記事を作成したり、既存の記事を削除したりすることはできません。

定数ユーザー = 新しいユーザー()
ユーザー.isAdmin = false

定数 能力 = this.caslAbilityFactory.createForUser(ユーザー)
能力.can(Action.Read, Article) // true
能力.can(Action.Delete, Article) // false
能力.can(Action.Create, Article) // false

これは明らかに問題があります。現在のユーザーが記事の著者である場合、次の操作を実行できるはずです。

定数ユーザー = 新しいユーザー()
ユーザーID = 1

const article = 新しい Article()
記事の著者ID = ユーザーID

定数 能力 = this.caslAbilityFactory.createForUser(ユーザー)
ability.can(Action.Update, article) // true

記事の著者ID = 2
ability.can(Action.Update, article) // false

0x4 警察ガード

上記の単純な実装では、複雑なシステムのより複雑な要件を満たすことができないため、前回の認証の記事を使用してクラスレベルの承認戦略モードを拡張し、元の CaslAbilityFactory クラスを拡張します。

'../casl/casl-ability.factory' から { AppAbility } をインポートします

インターフェース IPolicyHandler {
 ハンドル(能力: AppAbility): ブール値
}

タイプ PolicyHandlerCallback = (ability: AppAbility) => ブール値

エクスポート型 PolicyHandler = IPolicyHandler | PolicyHandlerCallback

各ルーティング コントローラーでのポリシー チェックのためのサポート オブジェクトと関数 (IPolicyHandler および PolicyHandlerCallback) を提供します。

次に、特定のリソースに対して指定されたアクセス ポリシーを実行するための @CheckPolicies() デコレータを作成します。

エクスポート const CHECK_POLICIES_KEY = 'check_policy'
エクスポート const CheckPolicies = (...handlers: PolicyHandler[]) => SetMetadata(CHECK_POLICIES_KEY, handlers)

ルーティング コントローラにバインドされているすべてのポリシーを抽出して実行する PoliciesGuard クラスを作成します。

@インジェクタブル()
エクスポートクラスPoliciesGuardはCanActivateを実装します{
 コンストラクタ(
 プライベートリフレクター:リフレクター、
 プライベート caslAbilityFactory: CaslAbilityFactory、
 ){}

 非同期canActivate(コンテキスト: ExecutionContext): Promise<boolean> {
 定数ポリシーハンドラ =
  this.reflector.get<ポリシーハンドラ[]>(
  CHECK_POLICIES_KEY、
  コンテキスト.getHandler()
  ) || []

 const { ユーザー } = context.switchToHttp().getRequest()
 定数 能力 = this.caslAbilityFactory.createForUser(ユーザー)

 戻り値 policyHandlers.every((handler) =>
  this.execPolicyHandler(ハンドラ、機能)
 )
 }

 プライベート execPolicyHandler(ハンドラー: PolicyHandler、機能: AppAbility) {
 if (ハンドラの型 === '関数') {
  ハンドラを返す(機能)
 }
 handler.handle(ability) を返す
 }
}

request.user にユーザー インスタンスが含まれていると仮定すると、policyHandler はデコレータ @CheckPolicies() を介して割り当てられ、aslAbilityFactory#create を使用して Ability オブジェクト メソッドを構築し、ユーザーが特定のアクションを実行するのに十分な権限を持っているかどうかを確認し、このオブジェクトをポリシー処理メソッド (実装関数またはクラス IPolicyHandler のインスタンス) に渡して、ブール値を返す handle() メソッドを公開します。

@得る()
@UseGuards(ポリシーガード)
@CheckPolicies((ability: AppAbility) => ability.can(Action.Read, Article))
すべて検索() {
 this.articlesService.findAll() を返す
}

IPolicyHandler インターフェイス クラスを定義することもできます。

ReadArticlePolicyHandlerクラスをエクスポートし、IPolicyHandlerを実装します。
 ハンドル(能力: AppAbility) {
 能力を返すことができます(Action.Read, Article)
 }
}

次のように使用します。

@得る()
@UseGuards(ポリシーガード)
@CheckPolicies(新しい ReadArticlePolicyHandler())
すべて検索() {
 this.articlesService.findAll() を返す
}

Nest.js 認可検証方法の例に関するこの記事はこれで終わりです。Nest.js 認可検証に関するその他の関連コンテンツについては、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Nest.js 環境変数の設定とシリアル化の詳細な説明
  • expressを使用して複数の静的ディレクトリを提供するためにnest.jsを使用する方法

<<:  Windows での MySQL5 グリーン バージョンのインストールの概要 (推奨)

>>:  VmWareでcentos7をインストールするときにインターネットにアクセスできない問題の解決策

推薦する

MySQL で結果を選択して更新を実行する例のチュートリアル

1. 単一テーブルクエリ -> 更新 テーブル名の更新 フィールド1=新しい値1、フィールド2...

JavaScript es6 の新しい配列メソッドの詳細な説明

目次1. 各() 2. arr.filter() 3. arr.every() 4. arr.map...

黒、白、グレーの控えめでエレガントなウェブデザインを鑑賞

クラシックな色の組み合わせの中でも、黒、白、グレーの時代を超えた魅力を否定できる人はおそらくいないで...

Dapr を使用してマイクロサービスをゼロから簡素化する例

目次序文1. Dockerをインストールする2. Dapr CLIをインストールする3. Net6 ...

MySQL データベース クエリ パフォーマンス最適化戦略

クエリを最適化するExplain ステートメントを使用してクエリ ステートメントを分析するExpla...

JavaScript の基礎: 即時実行関数

目次関数フォーマットを即時実行関数を即座に実行する他の方法 – 式即時実行される関数はパラメータを取...

コード分​​析を実現するためのFastDFSとNginxの統合

FastDFSとNginxの統合:トラッカーは、負荷分散と高可用性のために Nginx と組み合わせ...

Alibaba Cloud ESC に MYSQL8.0 をインストールするチュートリアル

接続ツールを開きます。私はMobaXterm_Personal_12.1を使用します(公式サイトのダ...

MySQL マスタースレーブレプリケーション 読み書き分離の設定方法の詳細説明

1. 説明前回は、MySQL のインストールと構成、MySQL ステートメントの使用、MySQL デ...

Celery と Docker を使用して Django で定期的なタスクを処理する方法

Django アプリケーションを構築して拡張していくと、必然的に特定のタスクをバックグラウンドで自動...

Linux ディレクトリ切り替え実装コード例

ファイルの切り替えは Linux でよく行われる操作です。Linux を初めて学ぶときに最初に触れる...

MySQLのインストールと設定に関する詳細なチュートリアル

目次インストール不要のMySQLバージョン1. インストール パッケージをダウンロードします。 2....

Nginx 仮想ホストの詳細な分析

目次1. 仮想ホスト1.1 仮想ホストの概念1.2 仮想ホストタイプ2. IP仮想ホストに基づく2....

Node はあいまい検索用の検索ボックスを実装します

この記事の例では、検索ボックスでファジークエリを実装するためのNodeの具体的なコードを参考までに共...

MySQL 8で追加された3つの新しいインデックスは、非表示、降順、関数です。

目次MySQL 8 の隠しインデックス、降順インデックス、関数インデックス1. 隠しインデックス1....