Truyền tham số vào bảo vệ tuyến đường


102

Tôi đang làm việc trên một ứng dụng có nhiều vai trò mà tôi cần sử dụng bảo vệ để chặn điều hướng đến các phần của ứng dụng dựa trên những vai trò đó. Tôi nhận ra rằng tôi có thể tạo các lớp bảo vệ riêng lẻ cho từng vai trò, nhưng thà có một lớp mà bằng cách nào đó tôi có thể chuyển một tham số vào. Nói cách khác, tôi muốn có thể làm điều gì đó tương tự như sau:

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

Nhưng vì tất cả những gì bạn vượt qua là tên loại của người bảo vệ của bạn, không thể nghĩ ra cách để làm điều đó. Tôi có nên cắn viên đạn và viết các lớp bảo vệ riêng lẻ cho mỗi vai trò và phá vỡ ảo tưởng về sự sang trọng của tôi khi có một kiểu tham số duy nhất không?

Câu trả lời:


218

Thay vì sử dụng forRole(), bạn có thể làm điều này:

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

và sử dụng cái này trong RoleGuard của bạn

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

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

Tùy chọn tuyệt vời là tốt, cảm ơn. Tôi thích cách tiếp cận phương pháp nhà máy của Aluan tốt hơn một chút, nhưng cảm ơn vì đã mở rộng bộ não của tôi về các khả năng!
Brian Noyes

7
Tôi nghĩ rằng tính bảo mật của dữ liệu này là không liên quan. Bạn phải sử dụng xác thực và ủy quyền ở phía máy chủ. Tôi nghĩ rằng quan điểm của người bảo vệ là không bảo vệ hoàn toàn ứng dụng của bạn. Nếu ai đó "hack" nó và điều hướng đến trang quản trị, họ sẽ không nhận được dữ liệu an toàn từ máy chủ mà chỉ nhìn thấy bạn các thành phần quản trị viên, theo tôi là ổn. Tôi nghĩ rằng đây là giải pháp tốt hơn nhiều so với giải pháp được chấp nhận. Giải pháp được chấp nhận phá vỡ tiêm phụ thuộc.
bucicimaci

1
Đây là giải pháp tốt và nó hoạt động tốt trong AuthGuard chung của tôi.
SAV

3
Giải pháp này hoạt động tuyệt vời. Vấn đề của tôi là nó dựa vào một lớp chuyển hướng. Không có cách nào mà ai đó nhìn vào mã này sẽ nhận ra rolesđối tượng đó và bộ bảo vệ tuyến đường được liên kết mà không biết cách mã hoạt động trước thời hạn. Thật tệ là Angular không hỗ trợ một cách nào đó để làm điều này theo một cách khai báo hơn. (Để được rõ ràng điều này là tôi than thở kiễu góc không giải pháp hoàn toàn hợp lý này.)
KhalilRavanna

1
@KhalilRavanna cảm ơn bạn, có nhưng tôi đã sử dụng giải pháp này nhiều lần trước đây và tôi đã chuyển sang giải pháp khác. Giải pháp mới của tôi là một RoleGaurd và một tệp có tên "access.ts" với hằng số Map <URL, AccessRoles> trong đó, sau đó tôi sử dụng nó trong RoleGaurd. Nếu bạn muốn kiểm soát quyền truy cập trong ứng dụng của mình, tôi nghĩ cách mới này sẽ tốt hơn nhiều, đặc biệt là khi bạn có nhiều ứng dụng trong một dự án.
Hasan Beheshti

11

Đây là lý do của tôi và giải pháp khả thi cho vấn đề thiếu nhà cung cấp.

Trong trường hợp của tôi, chúng ta có một người bảo vệ lấy quyền hoặc danh sách các quyền làm tham số, nhưng điều tương tự cũng có vai trò.

Chúng tôi có một lớp học để đối phó với những người bảo vệ xác thực có hoặc không có sự cho phép:

@Injectable()
export class AuthGuardService implements CanActivate {

    checkUserLoggedIn() { ... }

Điều này đề cập đến việc kiểm tra phiên hoạt động của người dùng, v.v.

Nó cũng chứa một phương thức được sử dụng để có được bộ bảo vệ quyền tùy chỉnh, điều này thực sự phụ thuộc vào AuthGuardServicechính nó

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

Điều này cho phép chúng tôi sử dụng phương pháp để đăng ký một số bảo vệ tùy chỉnh dựa trên tham số quyền trong mô-đun định tuyến của chúng tôi:

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

Điều thú vị forPermissionAuthGuardService.guards.push- điều này về cơ bản đảm bảo rằng bất kỳ lúc nào forPermissionsđược gọi để có được một lớp bảo vệ tùy chỉnh, nó cũng sẽ lưu trữ nó trong mảng này. Đây cũng là tĩnh trên lớp chính:

public static guards = [ ]; 

Sau đó, chúng ta có thể sử dụng mảng này để đăng ký tất cả các trình bảo vệ - điều này là được miễn là chúng tôi đảm bảo rằng vào thời điểm mô-đun ứng dụng đăng ký các nhà cung cấp này, các tuyến đường đã được xác định và tất cả các lớp bảo vệ đã được tạo (ví dụ: kiểm tra thứ tự nhập và giữ cho các nhà cung cấp này càng thấp càng tốt trong danh sách - có mô-đun định tuyến sẽ giúp):

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

Hi vọng điêu nay co ich.


1
Giải pháp này mang lại cho tôi một lỗi tĩnh: ERROR in Error gặp phải khi giải quyết các giá trị biểu tượng một cách tĩnh.
Arninja

Giải pháp này làm việc cho tôi để phát triển, nhưng khi tôi xây dựng ứng dụng phục vụ sản xuất trong throws lỗiERROR in Error during template compile of 'RoutingModule' Function calls are not supported in decorators but 'PermGuardService' was called.
kpacn

Điều này có hoạt động với các mô-đun được tải chậm có các mô-đun định tuyến của riêng chúng không?
nghiền nát

2

Giải pháp của @ AluanHaddad đang đưa ra lỗi "không có nhà cung cấp". Đây là một bản sửa lỗi cho điều đó (nó cảm thấy bẩn, nhưng tôi thiếu kỹ năng để tạo ra một cái tốt hơn).

Về mặt khái niệm, tôi đăng ký, với tư cách là nhà cung cấp, mỗi lớp được tạo động được tạo bởi roleGuard.

Vì vậy, đối với mọi vai trò được kiểm tra:

canActivate: [roleGuard('foo')]

bạn nên có:

providers: [roleGuard('foo')]

Tuy nhiên, giải pháp của @ AluanHaddad nguyên trạng sẽ tạo ra lớp mới cho mỗi lần gọi đến roleGuard, ngay cả khi rolestham số giống nhau. Sử dụng lodash.memoizenó trông như thế này:

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

Lưu ý, mỗi tổ hợp các vai trò sẽ tạo ra một lớp mới, vì vậy bạn cần đăng ký làm nhà cung cấp cho mọi tổ hợp các vai trò. Tức là nếu bạn có:

canActivate: [roleGuard('foo')]canActivate: [roleGuard('foo', 'bar')]bạn sẽ phải đăng ký cả hai:providers[roleGuard('foo'), roleGuard('foo', 'bar')]

Một giải pháp tốt hơn sẽ là đăng ký tự động các nhà cung cấp trong bộ sưu tập các nhà cung cấp toàn cầu bên trong roleGuard, nhưng như tôi đã nói, tôi thiếu kỹ năng để thực hiện điều đó.


Tôi thực sự thích cách tiếp cận chức năng này nhưng việc đóng mixin với DI (các lớp) trông giống như chi phí.
BILL
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.