ส่งผ่านพารามิเตอร์ไปยังตัวป้องกันเส้นทาง


110

ฉันกำลังทำงานกับแอพที่มีบทบาทมากมายที่ฉันต้องใช้ยามเพื่อบล็อกการนำทางไปยังส่วนต่างๆของแอพตามบทบาทเหล่านั้น ฉันรู้ว่าฉันสามารถสร้างคลาสการ์ดป้องกันสำหรับแต่ละบทบาทได้ แต่จะมีคลาสเดียวที่ฉันสามารถส่งผ่านพารามิเตอร์ไปได้ กล่าวอีกนัยหนึ่งฉันต้องการที่จะทำสิ่งที่คล้ายกันนี้:

{ 
  path: 'super-user-stuff', 
  component: SuperUserStuffComponent,
  canActivate: [RoleGuard.forRole('superUser')]
}

แต่เนื่องจากทั้งหมดที่คุณผ่านคือชื่อประเภทของยามของคุณจึงไม่สามารถคิดวิธีที่จะทำเช่นนั้นได้ ฉันควรจะบิตกระสุนและเขียนคลาสยามแต่ละตัวต่อบทบาทและทำลายภาพลวงตาของความสง่างามด้วยการมีประเภทพารามิเตอร์เดี่ยวแทนหรือไม่

คำตอบ:


231

แทนที่จะใช้forRole()คุณสามารถทำได้:

{ 
   path: 'super-user-stuff', 
   component: SuperUserStuffComponent,
   canActivate: RoleGuard,
   data: {roles: ['SuperAdmin', ...]}
}

และใช้สิ่งนี้ใน RoleGuard ของคุณ

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot)
    : Observable<boolean> | Promise<boolean> | boolean  {

    let roles = route.data.roles as Array<string>;
    ...
}

ตัวเลือกที่ดีเช่นกันขอบคุณ ฉันชอบวิธีการแบบโรงงานของ Aluan ดีกว่าเล็กน้อย แต่ขอบคุณที่ขยายสมองของฉันเกี่ยวกับความเป็นไปได้
Brian Noyes

7
ฉันคิดว่าความปลอดภัยของข้อมูลนี้ไม่เกี่ยวข้อง คุณต้องใช้การพิสูจน์ตัวตนและการอนุญาตทางฝั่งเซิร์ฟเวอร์ ฉันคิดว่าประเด็นของยามไม่ใช่เพื่อปกป้องแอปพลิเคชันของคุณอย่างเต็มที่ หากมีคน "แฮ็ก" และไปที่หน้าผู้ดูแลระบบเขา / เธอจะไม่ได้รับข้อมูลที่ปลอดภัยจากเซิร์ฟเวอร์เพียงแค่เห็นส่วนประกอบผู้ดูแลระบบของคุณซึ่งก็โอเคในความคิดของฉัน ฉันคิดว่านี่เป็นทางออกที่ดีกว่ามากที่ได้รับการยอมรับ วิธีแก้ปัญหาที่ได้รับการยอมรับจะทำลายการฉีดแบบพึ่งพา
bucicimaci

1
นี่เป็นทางออกที่ดีและใช้งานได้ดีใน AuthGuard ทั่วไปของฉัน
SAV

4
โซลูชันนี้ใช้งานได้ดี ปัญหาของฉันคือมันขึ้นอยู่กับเลเยอร์ของทิศทาง ไม่มีทางที่ใครบางคนที่มองรหัสนี้จะรู้ว่าrolesวัตถุนั้นและตัวป้องกันเส้นทางเชื่อมโยงกันโดยที่ไม่รู้ว่ารหัสทำงานอย่างไรก่อนเวลา มันแย่มากที่ Angular ไม่สนับสนุนวิธีการทำเช่นนี้ในลักษณะที่เปิดเผยมากขึ้น (เพื่อให้ชัดเจนว่านี่คือฉันกำลังคร่ำครวญ Angular ไม่ใช่วิธีแก้ปัญหาที่สมเหตุสมผลอย่างสมบูรณ์แบบ)
KhalilRavanna

1
@KhalilRavanna ขอบคุณใช่ แต่ฉันใช้วิธีนี้หลายครั้งแล้วและฉันก็ย้ายไปใช้วิธีอื่น โซลูชันใหม่ของฉันคือหนึ่ง RoleGaurd และหนึ่งไฟล์ที่มีชื่อ "access.ts" ที่มีค่าคงที่ Map <URL, AccessRoles> จากนั้นฉันก็ใช้มันใน RoleGaurd หากคุณต้องการควบคุมการเข้าถึงในแอปของคุณฉันคิดว่าวิธีใหม่นี้ดีกว่ามากโดยเฉพาะเมื่อคุณมีแอปมากกว่าหนึ่งแอปในโครงการเดียว
Hasan Beheshti

12

นี่คือสิ่งที่ฉันทำและวิธีแก้ปัญหาที่เป็นไปได้สำหรับปัญหาผู้ให้บริการที่หายไป

ในกรณีของฉันเรามียามที่รับการอนุญาตหรือรายการการอนุญาตเป็นพารามิเตอร์ แต่สิ่งเดียวกันก็มีบทบาท

เรามีคลาสสำหรับจัดการกับเจ้าหน้าที่รักษาความปลอดภัยโดยมีหรือไม่ได้รับอนุญาต:

@Injectable()
export class AuthGuardService implements CanActivate {

    checkUserLoggedIn() { ... }

สิ่งนี้เกี่ยวข้องกับการตรวจสอบเซสชันที่ใช้งานของผู้ใช้ ฯลฯ

นอกจากนี้ยังมีวิธีการที่ใช้เพื่อรับตัวป้องกันสิทธิ์แบบกำหนดเองซึ่งขึ้นอยู่กับAuthGuardServiceตัวมันเอง

static forPermissions(permissions: string | string[]) {
    @Injectable()
    class AuthGuardServiceWithPermissions {
      constructor(private authGuardService: AuthGuardService) { } // uses the parent class instance actually, but could in theory take any other deps

      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        // checks typical activation (auth) + custom permissions
        return this.authGuardService.canActivate(route, state) && this.checkPermissions();
      }

      checkPermissions() {
        const user = ... // get the current user
        // checks the given permissions with the current user 
        return user.hasPermissions(permissions);
      }
    }

    AuthGuardService.guards.push(AuthGuardServiceWithPermissions);
    return AuthGuardServiceWithPermissions;
  }

สิ่งนี้ช่วยให้เราใช้วิธีการลงทะเบียนยามที่กำหนดเองตามพารามิเตอร์สิทธิ์ในโมดูลเส้นทางของเรา:

....
{ path: 'something', 
  component: SomeComponent, 
  canActivate: [ AuthGuardService.forPermissions('permission1', 'permission2') ] },

ส่วนที่น่าสนใจforPermissionคือAuthGuardService.guards.push- โดยพื้นฐานแล้วจะทำให้แน่ใจว่าเมื่อใดก็ตามที่forPermissionsถูกเรียกเพื่อรับคลาสยามที่กำหนดเองมันจะเก็บไว้ในอาร์เรย์นี้ด้วย สิ่งนี้เป็นแบบคงที่ในคลาสหลัก:

public static guards = [ ]; 

จากนั้นเราสามารถใช้อาร์เรย์นี้เพื่อลงทะเบียนยามทั้งหมดได้ - นี่เป็นสิ่งที่ใช้ได้ตราบเท่าที่เราตรวจสอบให้แน่ใจว่าเมื่อถึงเวลาที่โมดูลแอปลงทะเบียนผู้ให้บริการเหล่านี้เส้นทางได้รับการกำหนดและสร้างคลาสยามทั้งหมดแล้ว (เช่นตรวจสอบใบสั่งนำเข้าและ ทำให้ผู้ให้บริการเหล่านี้ต่ำที่สุดในรายการ - การมีโมดูลการกำหนดเส้นทางจะช่วยได้):

providers: [
    // ...
    AuthGuardService,
    ...AuthGuardService.guards,
]

หวังว่านี่จะช่วยได้


1
โซลูชันนี้ทำให้ฉันมีข้อผิดพลาดแบบคงที่: ERROR in Error พบการแก้ไขค่าสัญลักษณ์แบบคงที่
Arninja

วิธีนี้ใช้ได้ผลสำหรับฉันในการพัฒนา แต่เมื่อฉันสร้างแอปพลิเคชันสำหรับการผลิตเกิดข้อผิดพลาดERROR in Error during template compile of 'RoutingModule' Function calls are not supported in decorators but 'PermGuardService' was called.
kpacn

สิ่งนี้ใช้ได้กับโมดูลที่โหลดแบบขี้เกียจที่มีโมดูลการกำหนดเส้นทางของตัวเองหรือไม่
ขยี้

2

โซลูชันของ @ AluanHaddad ให้ข้อผิดพลาด "ไม่มีผู้ให้บริการ" นี่คือการแก้ไขสำหรับสิ่งนั้น (รู้สึกสกปรก แต่ฉันขาดทักษะในการทำให้ดีขึ้น)

ตามแนวคิดฉันลงทะเบียนในฐานะผู้ให้บริการแต่ละคลาสที่สร้างแบบไดนามิกที่สร้างขึ้นโดยroleGuard.

ดังนั้นสำหรับทุกบทบาทที่ตรวจสอบ:

canActivate: [roleGuard('foo')]

คุณควรจะมี:

providers: [roleGuard('foo')]

อย่างไรก็ตามโซลูชันของ @ AluanHaddad ตามที่เป็นอยู่จะสร้างคลาสใหม่สำหรับการเรียกแต่ละครั้งroleGuardแม้ว่าrolesพารามิเตอร์จะเหมือนกันก็ตาม การใช้งานlodash.memoizeมีลักษณะดังนี้:

export var roleGuard = _.memoize(function forRole(...roles: string[]): Type<CanActivate> {
    return class AuthGuard implements CanActivate {
        canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
            Observable<boolean>
            | Promise<boolean>
            | boolean {
            console.log(`checking access for ${roles.join(', ')}.`);
            return true;
        }
    }
});

หมายเหตุการรวมกันของแต่ละบทบาทจะสร้างคลาสใหม่ดังนั้นคุณต้องลงทะเบียนเป็นผู้ให้บริการทุกบทบาทรวมกัน กล่าวคือถ้าคุณมี:

canActivate: [roleGuard('foo')]และcanActivate: [roleGuard('foo', 'bar')]คุณจะต้องลงทะเบียนทั้งสองอย่าง:providers[roleGuard('foo'), roleGuard('foo', 'bar')]

ทางออกที่ดีกว่าคือการลงทะเบียนผู้ให้บริการโดยอัตโนมัติในคอลเลกชันผู้ให้บริการระดับโลกภายในroleGuardแต่อย่างที่ฉันพูดไปฉันขาดทักษะในการนำไปใช้


ฉันชอบวิธีการใช้งานนี้มาก แต่การปิด mixin ด้วย DI (คลาส) ดูเหมือนอยู่เหนือศีรษะ
BILL
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.