Angular2路由教程2-使用Guard和Resolve進(jìn)行驗(yàn)證和權(quán)限控制

Coding I am

Angular2路由教程2-使用Guard和Resolve進(jìn)行驗(yàn)證和權(quán)限控制

發(fā)表于 <time title="創(chuàng)建于" itemprop="dateCreated datePublished" datetime="2016-11-12T23:53:48+08:00">2016-11-12 </time>| 分類于 教程

</header>

在上一篇文章:Angular2路由教程1-基礎(chǔ)中,我們?cè)敿?xì)介紹了Angular2路由的基礎(chǔ)和用法。在這篇文章中,我們就來(lái)看看利用Angular2的路由來(lái)實(shí)現(xiàn)客戶端的權(quán)限控制。
我們?cè)陂_發(fā)web應(yīng)用時(shí),在服務(wù)器端都會(huì)控制某種或某個(gè)用戶是否有權(quán)限調(diào)用某個(gè)接口。在前端,我們除了根據(jù)用戶的角色或其他特性來(lái)控制一些頁(yè)面元素是否顯示以外,也需要控制用戶是否能夠進(jìn)入某些頁(yè)面(例如通過(guò)直接輸入U(xiǎn)RL的方式直接進(jìn)入)。要控制是否顯示,我們可以使用*ngIf[hidden]等方式。而對(duì)于控制用戶能否進(jìn)入某個(gè)頁(yè)面,Angular2的路由框架也提供了非常方便的方式來(lái)實(shí)現(xiàn)這個(gè)功能。

Angular2提供了2種組件,GuardResolveGuard顧名思義就是用來(lái)保護(hù)一個(gè)路徑??梢杂脕?lái)判斷用戶只有在滿足一定的條件的情況下才能打開這個(gè)路徑對(duì)應(yīng)的頁(yè)面。Resolve用來(lái)在進(jìn)入某個(gè)路徑之前先獲取數(shù)據(jù)。

Guard

Guard其實(shí)是一系列接口,只要你實(shí)現(xiàn)了它的方法,配置了這些Guard,Angular路由框架就會(huì)根據(jù)這個(gè)方法返回的truefalse來(lái)判斷是否激活這個(gè)路由。它包括幾種類型:

  • CanActivate
    這種類型的Guard用來(lái)控制是否允許進(jìn)入當(dāng)前的路徑。
  • CanActivateChild
    這種類型的Guard用來(lái)控制是否允許進(jìn)入當(dāng)前路徑的所有子路徑。
  • CanDeactivate
    用來(lái)控制是否能離開當(dāng)前頁(yè)面進(jìn)入別的路徑
  • CanLoad
    用于控制一個(gè)異步加載的子模塊是否允許被加載。

CanActivate為例,這個(gè)接口的定義如下:

export interface CanActivate {

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

}

這個(gè)接口定義了一個(gè)方法,當(dāng)你實(shí)現(xiàn)這個(gè)接口,并把它配置到某一個(gè)路由上以后,當(dāng)用戶進(jìn)入這個(gè)路由的路徑之前,就會(huì)調(diào)用它里面的canActivate()方法,它第一個(gè)參數(shù),就是將要激活的路由,第二個(gè)參數(shù)是路由器當(dāng)前的狀態(tài)。它返回一個(gè)布爾型的結(jié)果,或者是布爾型的PromiseObservable。

Resolve

這跟Angular1中ui-router庫(kù)的resolve類似,就是用來(lái)在打開一個(gè)頁(yè)面之前先獲取數(shù)據(jù),而不是進(jìn)入頁(yè)面以后再加載。這個(gè)接口中的方法,可以返回任意的對(duì)象,也可以返回一個(gè)Promise,或者Observable

如果在一個(gè)路徑上同時(shí)設(shè)置了CanActivateResolve,首先CanActivate接口的方法會(huì)被執(zhí)行,當(dāng)這個(gè)路由可以被激活時(shí),Resolve接口的方法才會(huì)被執(zhí)行。

實(shí)例

下面,我們來(lái)通過(guò)一個(gè)比較完整的實(shí)例,來(lái)看看,CanActivate,CanActivateChild,CanDeactivateResolve的用法。(CanLoad將會(huì)在之后介紹子模塊、異步加載的文章中再介紹)。這篇教程的源代碼可以在這里查看。

場(chǎng)景

我們還是用之前的教程Angular2入門教程-2 實(shí)現(xiàn)TodoList App 中的實(shí)例。
我們先來(lái)看一看要解決的一些問(wèn)題:

  1. 系統(tǒng)的默認(rèn)頁(yè)是home頁(yè)面,這個(gè)頁(yè)面不需要登錄也可以打開。
  2. 登陸以后,管理員和用戶分別進(jìn)入不同的頁(yè)面。
  3. 所有的todo模塊的頁(yè)面都需要用戶角色,管理頁(yè)面需要管理員角色
  4. 在進(jìn)入任務(wù)列表頁(yè)面之前,需要獲取任務(wù)列表數(shù)據(jù),而不是進(jìn)入頁(yè)面以后再獲取數(shù)據(jù)。
  5. 當(dāng)用戶離開任務(wù)詳情頁(yè)時(shí),提示是否確認(rèn)要離開。

默認(rèn)頁(yè)面home

默認(rèn)頁(yè)面就是當(dāng)用戶直接打開你的網(wǎng)頁(yè)域名,沒有輸入任何路徑的情況下,默認(rèn)打開的頁(yè)面,在之前的教程已經(jīng)講過(guò),這是在配置路由的時(shí)候,用redirect實(shí)現(xiàn):

{

path: '',

redirectTo: '/home',

pathMatch: 'full'

}

AuthService

首先我們需要一個(gè)權(quán)限驗(yàn)證的服務(wù)AuthService,除了用來(lái)進(jìn)行登陸操作,還用于驗(yàn)證是否登陸,是否具有擁有某種角色。具體代碼如下:
import { Observable } from 'rxjs/Observable';

import 'rxjs/add/observable/of';

@Injectable()

export class AuthService {

account: Account;

// simulation to login.

login(role: string): Observable<Account> {

    let account = new Account();

    account.id = 11;

    account.name = 'super man';

    account.roles = [role];

    this.account = account;

    return Observable.of(account);

}

getAccount(): Account {

    return this.account;

}

isLogdedin(): boolean {

    return this.account && this.account.id != null;

}

hasRole(role: string): boolean {

    return this.account && this.account.roles.includes(role);

}

}

在最上面我們注意到我們引入了Observable和它的一個(gè)方法of。這是由于我們的登陸操作一般都是去服務(wù)器端進(jìn)行登陸驗(yàn)證,而使用Http服務(wù)從服務(wù)器端獲取數(shù)據(jù)一般都是返回Observable,所以這里也使用Observable來(lái)返回登陸后的用戶信息。我們引入of方法,是因?yàn)槲覀儗?duì)Observable的操作都是需要什么操作符就引入什么,而不是直接引入所有的。
最后的hasRole(role)方法的用途是,我們可以在頁(yè)面上通過(guò)ngIf="hasRole('CUSTOMER')"的方式來(lái)控制是否顯示某個(gè)頁(yè)面元素。

原先的todo路由定義

之前todo模塊的路由是這樣:

export const TodoRoutes: Route[] = [

{

    path: 'todo',

    children: [

        {

            path: 'list',

            component: TodoListComponent

        },

        {

            path: 'detail/:id',

            component: TodoDetailComponent

        }

    ]

}

];

在路徑todo下面,有兩個(gè)子路由,分別是列表和詳情。
然后再針對(duì)下面的需求,一個(gè)個(gè)來(lái)解決:

  1. 所有的todo模塊的頁(yè)面都需要用戶角色
  2. 離開詳情頁(yè)需要確認(rèn)
  3. 進(jìn)入列表頁(yè)面之前需要先獲取任務(wù)列表數(shù)據(jù)

控制所有todo模塊的都需要用戶角色

對(duì)于第一個(gè),我們要保護(hù)所有的todo模塊的頁(yè)面,也就是’/todo’路徑的所有子路徑,所以,我們可以使用CanActivateChild。這樣,在每進(jìn)入一個(gè)todo的子路徑的時(shí)候,都會(huì)先進(jìn)行檢查來(lái)判斷能否進(jìn)入。代碼如下:

import { Injectable } from '@angular/core';

import { CanActivateChild, Router,

ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

import { AuthService } from '../services/auth.service';

import { TodoDetailComponent } from './detail/detail.component';

@Injectable()

export class MyTodoGuard implements CanActivateChild {

constructor(private authService: AuthService, private router: Router) {}

canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

    if (!this.authService.isLogdedin()) {

        alert('You need to login!');

        this.router.navigate(['/home']);

        return false;

    }

    if (this.authService.hasRole('CUSTOMER')) {

        return true;

    }

    return false;

}

}

這個(gè)Guard的實(shí)現(xiàn)很簡(jiǎn)單,就是用authService來(lái)判斷是否登陸,以及是否具有’CUSTOMER’角色。
注意這個(gè)Guard的實(shí)現(xiàn)也必須是Injectable的,因?yàn)槲覀冃枰狝ngular的依賴注入幫我們創(chuàng)建實(shí)例和自動(dòng)注入。

離開詳情頁(yè)需要確認(rèn)

接下來(lái)我們看怎么實(shí)現(xiàn)離開詳情頁(yè)時(shí)的確認(rèn),也很簡(jiǎn)單,就是使用CanDeactivate,并把它定義在詳情頁(yè)的路由定義上。

@Injectable()

export class CanLeaveTodoDetailGuard implements CanDeactivate<TodoDetailComponent> {

canDeactivate(component: TodoDetailComponent, route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

    return confirm('Confirm?');

}

}

為了簡(jiǎn)單,上面的方法直接調(diào)用confirm('confirm?')并返回它的結(jié)果,它會(huì)返回一個(gè)布爾型的結(jié)果,表示用戶是否確認(rèn)。如果用戶取消了,就不會(huì)離開詳情頁(yè)。

進(jìn)入列表頁(yè)面之前需要先獲取數(shù)據(jù)

最后,再看看用Resolve來(lái)實(shí)現(xiàn)進(jìn)入一個(gè)頁(yè)面之前的數(shù)據(jù)初始化。

import { Injectable } from '@angular/core';

import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

import { Todo } from './todo';

import { TodoService } from './todo.service';

@Injectable()

export class MyTodoResolver implements Resolve<Todo> {

constructor(private todoService: TodoService) { }

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

    console.log('Get my todo list.');

    return this.todoService.getAllTodos();

}

}

在這個(gè)resolve()方法中,直接返回調(diào)用todoServicegetAllTodos()方法的結(jié)果。對(duì)這個(gè)getAllTodos()方法我們做一些修改,讓他返回一些測(cè)試數(shù)據(jù):

import { Observable } from 'rxjs/Observable';

import 'rxjs/add/observable/of';

import 'rxjs/add/operator/delay';

// 神略中間的部分

getAllTodos(): Observable<Todo[]> {

let todo1 = new Todo();

todo1.id = 1;

todo1.title = 'test task 1';

todo1.createdDate = new Date();

todo1.complete = false;

let todo2 = new Todo();

todo2.id = 2;

todo2.title = 'test task 2';

todo2.createdDate = new Date();

todo2.complete = false;

this.todos = [todo1, todo2];

return Observable.of(this.todos).delay(3000);

}

在這個(gè)方法里我們創(chuàng)建了2個(gè)測(cè)試的任務(wù),封裝成Observable返回,并添加了一個(gè)3秒鐘的延時(shí),來(lái)模擬從服務(wù)器端獲取數(shù)據(jù)的過(guò)程。
通過(guò)Resolve方式獲取的數(shù)據(jù),會(huì)放在被激活的當(dāng)前路由的data屬性里面,我們可以在組件中來(lái)獲得。所以,需要修改TodoListComponent,從路由的數(shù)據(jù)data中獲取todos的值。然后就可以在頁(yè)面中顯示:

export class TodoListComponent {

newTodo: Todo = new Todo();

todos: Todo[];

constructor(private todoService: TodoService, private route: ActivatedRoute) {

    this.todos = this.route.snapshot.data['todos'];

}

// 省略其他

}

最終的todo模塊路由配置

最后我們?cè)倏纯醇由仙厦娴?code>Guard和Resolve的路由配置以后,todo模塊的路由配置:

export const TodoRoutes: Route[] = [

{

    path: 'todo',

    canActivateChild: [MyTodoGuard],

    children: [

        {

            path: 'list',

            component: TodoListComponent,

            resolve: { todos: MyTodoResolver }

        },

        {

            path: 'detail/:id',

            component: TodoDetailComponent,

            canDeactivate: [ CanLeaveTodoDetailGuard ]

        }

    ]

}

我們?cè)凇痶odo’的路由上加了一個(gè)canActivateChild控制能否激活子路徑, 在list的子路徑上配置了一個(gè)resolve來(lái)獲取數(shù)據(jù),在detail/:id上配置了一個(gè)canDeactivate來(lái)控制能否離開。

最后,別忘了我們還需要在todo模塊的定義TodoModule里面的providers里添加這些,這樣依賴注入功能才能使用這些服務(wù)。

@NgModule({

imports: [CommonModule, FormsModule ],

declarations: [TodoListComponent, TodoDetailComponent, TodoItemComponent],

providers: [TodoService, MyTodoResolver, MyTodoGuard, CanLeaveTodoDetailGuard]

})

export class TodoModule {}

通用的角色驗(yàn)證Guard

在上面的MyTodoGuard里面,我們判斷當(dāng)前的用戶是否具有CUSTOMER角色,如果我們能夠把這個(gè)需要判斷的CUSTOMER角色通過(guò)一種方式來(lái)傳遞到這個(gè)方法里面,然后通過(guò)傳遞不同的參數(shù),就可以用這個(gè)方法來(lái)判斷進(jìn)入任意頁(yè)面的用戶是否具有某個(gè)角色。我們可以使用Angular2路由里面的data屬性來(lái)實(shí)現(xiàn)。
當(dāng)我們定義一個(gè)路由時(shí),可以通過(guò)data屬性來(lái)給這個(gè)路由添加一些數(shù)據(jù),如下:

export const TodoRoutes: Route[] = [

{

    path: 'todo',

    data: {

        role: 'CUSTOMER'

    },

    canActivateChild: [MyTodoGuard],

    children: [

        {

            path: 'list',

            component: TodoListComponent,

            resolve: {

                todos: MyTodoResolver

            },

            data: {

                title: '列表'

            }

        },

        {

            path: 'detail/:id',

            component: TodoDetailComponent,

            canDeactivate: [ CanLeaveTodoDetailGuard ],

            data: {

                title: '詳情'

            }

        }

    ]

}

];

我們給’todo’這個(gè)路由添加了1個(gè)變量,角色,我們可以在這個(gè)路由定義的組件以及它所有的子組件中的當(dāng)前路由中得到這些數(shù)據(jù)。而且在子路由里,都添加了一個(gè)title的變量。然后在TodoListComponent里面就可以使用這個(gè)變量,比如在頁(yè)面上顯示。

export class TodoListComponent {

newTodo: Todo = new Todo();

todos: Todo[];

title: string;

constructor(private todoService: TodoService, private route: ActivatedRoute) {

    this.todos = this.route.snapshot.data['todos'];

    this.title = this.route.data['title'];

}

// 省略其他

}

</pre>

|

</figure>

我們可以通過(guò)這種方式,在每個(gè)路由上配置title屬性,然后就可以用一種通用的方式來(lái)實(shí)現(xiàn)在頁(yè)面上顯示面包屑導(dǎo)航欄的功能。

但是,在這個(gè)實(shí)例中,我們要用data上添加的role: 'CUSTOMER',用它來(lái)表示當(dāng)前的這個(gè)路徑,需要有CUSTOMER角色的用戶才能訪問(wèn)。然后在MyTodoGuard里用它來(lái)判斷:

@Injectable()

export class MyTodoGuard implements CanActivateChild {

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

    if (!this.authService.isLogdedin()) {

        alert('You need to login!');

        this.router.navigate(['/home']);

        return false;

    }

    let requiredRole = next.data['role'];

    if (requiredRole == null || this.authService.hasRole(requiredRole)) {

        return true;

    }

    return false;

}

}

在這里,我們從將要激活的路由的數(shù)據(jù)里面得到role,然后判斷當(dāng)前用戶是否具有這個(gè)角色。這樣,我們的這個(gè)MyTodoGuard,可以把它定義在根路徑上,就可以作為一個(gè)通用的用戶權(quán)限驗(yàn)證的Guard來(lái)使用。只要路徑上存在這個(gè)值,就說(shuō)明需要權(quán)限。

堅(jiān)持原創(chuàng)技術(shù)分享,您的支持將鼓勵(lì)我繼續(xù)創(chuàng)作!

</article>

</main>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,697評(píng)論 19 139
  • # 1 :重定向路由 {path: '',redirectTo:'home', parthMatch:'full'...
    __凌閱讀 772評(píng)論 0 0
  • 導(dǎo)航是很簡(jiǎn)單的,只是不同頁(yè)面之間的切換,路由是實(shí)現(xiàn)導(dǎo)航的一種。 一個(gè)url對(duì)應(yīng)的一個(gè)頁(yè)面,在angular2中是一...
    賀賀v5閱讀 3,180評(píng)論 5 9
  • core package 概要:Core是所有其他包的基礎(chǔ)包.它提供了大部分功能包括metadata,templa...
    LOVE小狼閱讀 2,891評(píng)論 0 3
  • 2017年6月23日 昨天一天我們工作人員都在緊鑼密鼓地做著各種考前準(zhǔn)備工作,從廣播指令的試音、試放,到全樓每個(gè)監(jiān)...
    魅力春天閱讀 264評(píng)論 0 1

友情鏈接更多精彩內(nèi)容