Berikan parameter ke penjaga rute

102

Saya sedang mengerjakan aplikasi yang memiliki banyak peran sehingga saya perlu menggunakan penjaga untuk memblokir navigasi ke bagian aplikasi berdasarkan peran tersebut. Saya menyadari bahwa saya dapat membuat kelas penjaga individu untuk setiap peran, tetapi lebih suka memiliki satu kelas yang entah bagaimana dapat saya berikan parameter. Dengan kata lain, saya ingin bisa melakukan sesuatu yang serupa dengan ini:

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

Tetapi karena semua yang Anda lewati adalah nama tipe penjaga Anda, tidak dapat memikirkan cara untuk melakukan itu. Haruskah saya menggigit peluru dan menulis kelas penjaga individu per peran dan menghancurkan ilusi keanggunan saya dengan memiliki satu tipe parameter?

Brian Noyes
sumber

Jawaban:

218

Alih-alih menggunakan forRole(), Anda dapat melakukan ini:

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

dan gunakan ini di RoleGuard Anda

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

    let roles = route.data.roles as Array<string>;
    ...
}
Hasan Beheshti
sumber
Pilihan bagus juga, terima kasih. Saya suka pendekatan metode pabrik Aluan hanya sedikit lebih baik, tetapi terima kasih telah mengembangkan otak saya pada kemungkinan!
Brian Noyes
7
Menurut saya, keamanan data ini tidak relevan. Anda harus menggunakan otentikasi dan otorisasi di sisi server. Saya pikir inti dari penjagaan ini bukanlah untuk sepenuhnya melindungi aplikasi Anda. Jika seseorang "meretas" dan menavigasi ke halaman admin, dia tidak akan mendapatkan data aman dari server hanya hanya melihat Anda komponen admin yang menurut saya baik-baik saja. Saya pikir ini adalah solusi yang jauh lebih baik daripada yang diterima. Solusi yang diterima memutus injeksi ketergantungan.
bucicimaci
1
Ini adalah solusi yang bagus dan berfungsi dengan baik di AuthGuard generik saya.
SAV
3
Solusi ini bekerja dengan baik. Masalah saya adalah bahwa hal itu bergantung pada lapisan tipuan. Tidak mungkin seseorang yang melihat kode ini akan menyadari bahwa rolesobjek dan penjaga rute terhubung tanpa mengetahui cara kerja kode sebelumnya. Sungguh menyebalkan bahwa Angular tidak mendukung cara untuk melakukan ini dengan cara yang lebih deklaratif. (Untuk memperjelas ini adalah saya meratapi Angular bukan solusi yang masuk akal ini.)
KhalilRavanna
1
@KhalilRavanna terima kasih, ya tapi saya menggunakan solusi ini beberapa waktu yang lalu dan saya pindah ke solusi lain. Solusi baru saya adalah satu RoleGaurd dan satu file dengan nama "access.ts" dengan konstanta Map <URL, AccessRoles>, kemudian saya menggunakannya di RoleGaurd. Jika Anda ingin mengontrol akses Anda di aplikasi Anda, menurut saya cara baru ini jauh lebih baik terutama ketika Anda memiliki lebih dari satu aplikasi dalam satu proyek.
Hasan Beheshti
11

Inilah pendapat saya tentang ini dan kemungkinan solusi untuk masalah penyedia yang hilang.

Dalam kasus saya, kami memiliki penjaga yang mengambil izin atau daftar izin sebagai parameter, tetapi itu hal yang sama memiliki peran.

Kami memiliki kelas untuk menangani penjaga autentikasi dengan atau tanpa izin:

@Injectable()
export class AuthGuardService implements CanActivate {

    checkUserLoggedIn() { ... }

Ini berkaitan dengan memeriksa sesi aktif pengguna, dll.

Ini juga berisi metode yang digunakan untuk mendapatkan penjaga izin khusus, yang sebenarnya bergantung pada AuthGuardServicedirinya sendiri

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;
  }

Ini memungkinkan kami menggunakan metode untuk mendaftarkan beberapa penjaga khusus berdasarkan parameter izin di modul perutean kami:

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

Bagian yang menarik dari forPermissionadalah AuthGuardService.guards.push- ini pada dasarnya memastikan bahwa setiap saat forPermissionsdipanggil untuk mendapatkan kelas penjaga khusus, ia juga akan menyimpannya dalam larik ini. Ini juga statis di kelas utama:

public static guards = [ ]; 

Kemudian kita dapat menggunakan larik ini untuk mendaftarkan semua penjaga - tidak apa-apa selama kita memastikan bahwa pada saat modul aplikasi mendaftarkan penyedia ini, rute telah ditentukan dan semua kelas penjaga telah dibuat (misalnya, periksa urutan impor dan pertahankan penyedia ini serendah mungkin dalam daftar - memiliki modul perutean membantu):

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

Semoga ini membantu.

Ovidiu Dolha
sumber
1
Solusi ini memberi saya kesalahan statis: ERROR in Error mengalami penyelesaian nilai simbol secara statis.
Arninja
Solusi ini berfungsi untuk saya untuk pengembangan, tetapi ketika saya membangun aplikasi untuk produksi dalam kesalahan melemparERROR in Error during template compile of 'RoutingModule' Function calls are not supported in decorators but 'PermGuardService' was called.
kpacn
Apakah ini berfungsi dengan modul yang dimuat lambat yang memiliki modul peruteannya sendiri?
hancurkan
2

Solusi @ AluanHaddad adalah memberikan kesalahan "tidak ada penyedia". Berikut ini cara memperbaikinya (rasanya kotor, tetapi saya kurang memiliki keterampilan untuk membuatnya lebih baik).

Secara konseptual, saya mendaftar, sebagai penyedia, setiap kelas yang dibuat secara dinamis dibuat oleh roleGuard.

Jadi untuk setiap peran yang diperiksa:

canActivate: [roleGuard('foo')]

Kamu harus punya:

providers: [roleGuard('foo')]

Namun, solusi @ AluanHaddad apa adanya akan menghasilkan kelas baru untuk setiap panggilan ke roleGuard, meskipun rolesparameternya sama. Menggunakannya lodash.memoizeterlihat seperti ini:

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;
        }
    }
});

Perhatikan, setiap kombinasi peran menghasilkan kelas baru, jadi Anda perlu mendaftar sebagai penyedia setiap kombinasi peran. Yaitu jika Anda memiliki:

canActivate: [roleGuard('foo')]dan canActivate: [roleGuard('foo', 'bar')]Anda harus mendaftarkan keduanya:providers[roleGuard('foo'), roleGuard('foo', 'bar')]

Solusi yang lebih baik adalah mendaftarkan penyedia secara otomatis dalam kumpulan penyedia global di dalamnya roleGuard, tetapi seperti yang saya katakan, saya kurang memiliki keterampilan untuk mengimplementasikannya.

THX-1138
sumber
Saya sangat suka pendekatan fungsional ini tetapi penutupan mixin dengan DI (kelas) terlihat seperti overhead.
BILL