NestJS — Node.js للمؤسسات

الحُرّاس: التفويض

17 دقيقة الدرس 13 من 30

الحُرّاس: التفويض

يجيب الحارس (guard) عن سؤال واحد: هل يُسمح لهذا الطلب بالمتابعة؟ يُعيد true للسماح بمرور الطلب إلى المعالج، أو false (أو يرمي خطأ) لحجبه. والحُرّاس هم المكان القياسي لمنطق المصادقة والتفويض.

واجهة CanActivate

الحارس صنف قابل للحقن يُنفّذ CanActivate بدالّة canActivate() واحدة. تستقبل ExecutionContext وتُعيد قيمة منطقية (أو وعدًا/Observable بها):

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; @Injectable() export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); return Boolean(request.headers['authorization']); } }

إن أعادت canActivate() القيمة false، يستجيب NestJS تلقائيًا بـ 403 Forbidden ولا يعمل المعالج أبدًا.

سياق التنفيذ ExecutionContext

يصف ExecutionContext الطلب الحالي بطريقة مستقلّة عن النقل. تمنحك switchToHttp() طلب/استجابة HTTP، لكنّ الحارس نفسه قد يعمل لـ WebSockets أو الخدمات المصغّرة عبر switchToWs() / switchToRpc(). كما يكشف الصنف الهدف والمعالج — وهو أساسي لقراءة البيانات الوصفية.

تطبيق الحُرّاس

اربط حارسًا بـ @UseGuards() على مستوى الدالّة أو المتحكّم، أو عامًّا في main.ts:

import { UseGuards } from '@nestjs/common'; @Controller('admin') @UseGuards(AuthGuard) // يحمي كل مسار في المتحكّم export class AdminController {} // أو عامًّا: app.useGlobalGuards(new AuthGuard());

التفويض القائم على الأدوار بالبيانات الوصفية

يصبح الحُرّاس أقوياء حين يُدمَجون مع بيانات وصفية مخصّصة. تُرفِق الأدوار المطلوبة بمعالج عبر @SetMetadata() (أو مُزخرِف مخصّص)، ثم تقرأها داخل الحارس باستخدام Reflector:

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 required = this.reflector.get('roles', context.getHandler()); if (!required) return true; // لا أدوار مطلوبة -> اسمح const { user } = context.switchToHttp().getRequest(); return required.some((role: string) => user?.roles?.includes(role)); } } // على مسار: @SetMetadata('roles', ['admin']) @Get('reports') getReports() {}
يقرأ Reflector البيانات الوصفية. وهكذا يُكيّف حارسٌ واحد قابل لإعادة الاستخدام سلوكه لكل مسار — المُزخرِف يُعلِن المتطلّب، والحارس يفرضه.

الحُرّاس مقابل الوسيطات

لماذا حارس لا وسيط للمصادقة؟ يعمل الحُرّاس بعد الوسيطات ولديهم ExecutionContext الكامل — يعرفون أي معالج مستهدَف بالضبط ويقرؤون بياناته الوصفية (كالأدوار المطلوبة). أمّا الوسيط فيعمل مبكرًا جدًا قبل معرفة أيٍّ من ذلك.
الحُرّاس يقرّرون الوصول، لا تحويل البيانات. يجب أن يُعيد الحارس سماحًا/منعًا. أمّا تشكيل الاستجابة أو إثراء البيانات فمن شأن المعترِضات، التالية.

الخلاصة

يُنفّذ الحُرّاس CanActivate ويُعيدون true/false للسماح أو الحجب، فيستجيبون 403 عند المنع. يستقبلون ExecutionContext (مستقلًّا عن النقل)، ويُطبَّقون بـ @UseGuards()، ويصيرون واعين للأدوار بقراءة بيانات المسار عبر Reflector. يعمل الحُرّاس بعد الوسيطات وهم المكان الصحيح للتفويض. تاليًا: المعترِضات، التي تُغلّف المعالج نفسه.