Angular2使用Guard和Resolve進(jìn)行驗(yàn)證和權(quán)限控制
我們在開發(fā)web應(yīng)用時(shí),在服務(wù)器端都會(huì)控制某種或某個(gè)用戶是否有權(quán)限調(diào)用某個(gè)接口。在前端,我們除了根據(jù)用戶的角色或其他特性來控制一些頁面元素是否顯示以外,也需要控制用戶是否能夠進(jìn)入某些頁面(例如通過直接輸入U(xiǎn)RL的方式直接進(jìn)入)。要控制是否顯示,我們可以使用 *ngIf
、 [hidden]
等方式。而對于控制用戶能否進(jìn)入某個(gè)頁面,Angular2的路由框架也提供了非常方便的方式來實(shí)現(xiàn)這個(gè)功能。
Angular2提供了2種組件, Guard 和 Resolve 。 Guard 顧名思義就是用來保護(hù)一個(gè)路徑??梢杂脕砼袛嘤脩糁挥性跐M足一定的條件的情況下才能打開這個(gè)路徑對應(yīng)的頁面。 Resolve 用來在進(jìn)入某個(gè)路徑之前先獲取數(shù)據(jù)。
Guard
Guard 其實(shí)是一系列接口,只要你實(shí)現(xiàn)了它的方法,配置了這些 Guard ,Angular路由框架就會(huì)根據(jù)這個(gè)方法返回的 true 或 false 來判斷是否激活這個(gè)路由。它包括幾種類型:
1、CanActivate
這種類型的 Guard 用來控制是否允許進(jìn)入當(dāng)前的路徑。
2、CanActivateChild
這種類型的 Guard 用來控制是否允許進(jìn)入當(dāng)前路徑的所有子路徑。
3、CanDeactivate
用來控制是否能離開當(dāng)前頁面進(jìn)入別的路徑
4、CanLoad
用于控制一個(gè)異步加載的子模塊是否允許被加載。
以 CanActivate 為例,這個(gè)接口的定義如下:
exportinterface 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é)果,或者是布爾型的 Promise
或 Observable
。
Resolve
這跟Angular1中ui-router庫的 resolve 類似,就是用來在打開一個(gè)頁面之前先獲取數(shù)據(jù),而不是進(jìn)入頁面以后再加載。這個(gè)接口中的方法,可以返回任意的對象,也可以返回一個(gè) Promise ,或者 Observable
如果在一個(gè)路徑上同時(shí)設(shè)置了 CanActivate 和 Resolve ,首先 CanActivate 接口的方法會(huì)被執(zhí)行,當(dāng)這個(gè)路由可以被激活時(shí), Resolve 接口的方法才會(huì)被執(zhí)行。
實(shí)例
下面,我們來通過一個(gè)比較完整的實(shí)例,來看看, CanActivate , CanActivateChild , CanDeactivate 和 Resolve 的用法。( CanLoad 將會(huì)在之后介紹子模塊、異步加載的文章中再介紹)。這篇教程的源代碼可以在這里 查看。
場景
我們還是用之前的教程 Angular2入門教程-2 實(shí)現(xiàn)TodoList App 中的實(shí)例。
我們先來看一看要解決的一些問題:
- 系統(tǒng)的默認(rèn)頁是home頁面,這個(gè)頁面不需要登錄也可以打開。
- 登陸以后,管理員和用戶分別進(jìn)入不同的頁面。
- 所有的todo模塊的頁面都需要用戶角色,管理頁面需要管理員角色
- 在進(jìn)入任務(wù)列表頁面之前,需要獲取任務(wù)列表數(shù)據(jù),而不是進(jìn)入頁面以后再獲取數(shù)據(jù)。
- 當(dāng)用戶離開任務(wù)詳情頁時(shí),提示是否確認(rèn)要離開。
默認(rèn)頁面home
默認(rèn)頁面就是當(dāng)用戶直接打開你的網(wǎng)頁域名,沒有輸入任何路徑的情況下,默認(rèn)打開的頁面,在之前的教程已經(jīng)講過,這是在配置路由的時(shí)候,用 redirect 實(shí)現(xiàn):
{ path: '', redirectTo: '/home', pathMatch: 'full' }
AuthService
首先我們需要一個(gè)權(quán)限驗(yàn)證的服務(wù) AuthService ,除了用來進(jìn)行登陸操作,還用于驗(yàn)證是否登陸,是否具有擁有某種角色。具體代碼如下:
import{ Observable }from'rxjs/Observable'; import'rxjs/add/observable/of'; @Injectable() exportclassAuthService{ account: Account; // simulation to login. login(role: string): Observable<Account> { letaccount =newAccount(); account.id = 11; account.name = 'super man'; account.roles = [role]; this.account = account; returnObservable.of(account); } getAccount(): Account { returnthis.account; } isLogdedin(): boolean { returnthis.account &&this.account.id !=null; } hasRole(role: string): boolean { returnthis.account &&this.account.roles.includes(role); } }
在最上面我們注意到我們引入了 Observable
和它的一個(gè)方法 of
。這是由于我們的登陸操作一般都是去服務(wù)器端進(jìn)行登陸驗(yàn)證,而使用 Http
服務(wù)從服務(wù)器端獲取數(shù)據(jù)一般都是返回 Observable
,所以這里也使用 Observable
來返回登陸后的用戶信息。我們引入 of
方法,是因?yàn)槲覀儗?code> Observable 的操作都是需要什么操作符就引入什么,而不是直接引入所有的。
最后的 hasRole(role)
方法的用途是,我們可以在頁面上通過 ngIf="hasRole('CUSTOMER')"
的方式來控制是否顯示某個(gè)頁面元素。
原先的todo路由定義
之前todo模塊的路由是這樣:
exportconstTodoRoutes: Route[] = [ { path: 'todo', children: [ { path: 'list', component: TodoListComponent }, { path: 'detail/:id', component: TodoDetailComponent } ] } ];
在路徑 todo 下面,有兩個(gè)子路由,分別是列表和詳情。
然后再針對下面的需求,一個(gè)個(gè)來解決:
- 所有的todo模塊的頁面都需要用戶角色
- 離開詳情頁需要確認(rèn)
- 進(jìn)入列表頁面之前需要先獲取任務(wù)列表數(shù)據(jù)
控制所有todo模塊的都需要用戶角色
對于第一個(gè),我們要保護(hù)所有的todo模塊的頁面,也就是'/todo'路徑的所有子路徑,所以,我們可以使用 CanActivateChild 。這樣,在每進(jìn)入一個(gè)todo的子路徑的時(shí)候,都會(huì)先進(jìn)行檢查來判斷能否進(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() exportclassMyTodoGuardimplementsCanActivateChild{ 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']); returnfalse; } if(this.authService.hasRole('CUSTOMER')) { returntrue; } returnfalse; } }
這個(gè) Guard 的實(shí)現(xiàn)很簡單,就是用 authService 來判斷是否登陸,以及是否具有'CUSTOMER'角色。
注意這個(gè) Guard 的實(shí)現(xiàn)也必須是 Injectable 的,因?yàn)槲覀冃枰狝ngular的依賴注入幫我們創(chuàng)建實(shí)例和自動(dòng)注入。
離開詳情頁需要確認(rèn)
接下來我們看怎么實(shí)現(xiàn)離開詳情頁時(shí)的確認(rèn),也很簡單,就是使用 CanDeactivate ,并把它定義在詳情頁的路由定義上。
@Injectable() exportclassCanLeaveTodoDetailGuardimplementsCanDeactivate<TodoDetailComponent>{ canDeactivate(component: TodoDetailComponent, route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { returnconfirm('Confirm?'); } }
為了簡單,上面的方法直接調(diào)用 confirm('confirm?') 并返回它的結(jié)果,它會(huì)返回一個(gè)布爾型的結(jié)果,表示用戶是否確認(rèn)。如果用戶取消了,就不會(huì)離開詳情頁。
進(jìn)入列表頁面之前需要先獲取數(shù)據(jù)
最后,再看看用 Resolve 來實(shí)現(xiàn)進(jìn)入一個(gè)頁面之前的數(shù)據(jù)初始化。
import{ Injectable }from'@angular/core'; import{ Resolve, ActivatedRouteSnapshot, RouterStateSnapshot }from'@angular/router'; import{ Todo }from'./todo'; import{ TodoService }from'./todo.service'; @Injectable() exportclassMyTodoResolverimplementsResolve<Todo>{ constructor(private todoService: TodoService) { } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { console.log('Get my todo list.'); returnthis.todoService.getAllTodos(); } }
在這個(gè) resolve()
方法中,直接返回調(diào)用 todoService
的 getAllTodos()
方法的結(jié)果。對這個(gè) getAllTodos()
方法我們做一些修改,讓他返回一些測試數(shù)據(jù):
import{ Observable }from'rxjs/Observable'; import'rxjs/add/observable/of'; import'rxjs/add/operator/delay'; // 神略中間的部分 getAllTodos(): Observable<Todo[]> { lettodo1 =newTodo(); todo1.id = 1; todo1.title = 'test task 1'; todo1.createdDate = newDate(); todo1.complete = false; lettodo2 =newTodo(); todo2.id = 2; todo2.title = 'test task 2'; todo2.createdDate = newDate(); todo2.complete = false; this.todos = [todo1, todo2]; returnObservable.of(this.todos).delay(3000); }
在這個(gè)方法里我們創(chuàng)建了2個(gè)測試的任務(wù),封裝成 Observable 返回,并添加了一個(gè)3秒鐘的延時(shí),來模擬從服務(wù)器端獲取數(shù)據(jù)的過程。
通過 Resolve 方式獲取的數(shù)據(jù),會(huì)放在被激活的當(dāng)前路由的 data 屬性里面,我們可以在組件中來獲得。所以,需要修改 TodoListComponent ,從路由的數(shù)據(jù) data 中獲取 todos 的值。然后就可以在頁面中顯示:
exportclassTodoListComponent{ newTodo: Todo = newTodo(); todos: Todo[]; constructor(private todoService: TodoService, private route: ActivatedRoute) { this.todos =this.route.snapshot.data['todos']; } // 省略其他 }
最終的todo模塊路由配置
最后我們再看看加上上面的 Guard 和 Resolve 的路由配置以后,todo模塊的路由配置:
exportconstTodoRoutes: Route[] = [ { path: 'todo', canActivateChild: [MyTodoGuard], children: [ { path: 'list', component: TodoListComponent, resolve: { todos: MyTodoResolver } }, { path: 'detail/:id', component: TodoDetailComponent, canDeactivate: [ CanLeaveTodoDetailGuard ] } ] } ];
我們在'todo'的路由上加了一個(gè) canActivateChild 控制能否激活子路徑, 在 list 的子路徑上配置了一個(gè) resolve 來獲取數(shù)據(jù),在 detail/:id 上配置了一個(gè) canDeactivate 來控制能否離開。
最后,別忘了我們還需要在 todo 模塊的定義 TodoModule 里面的 providers 里添加這些,這樣依賴注入功能才能使用這些服務(wù)。
@NgModule({ imports: [CommonModule, FormsModule ], declarations: [TodoListComponent, TodoDetailComponent, TodoItemComponent], providers: [TodoService, MyTodoResolver, MyTodoGuard, CanLeaveTodoDetailGuard] }) exportclassTodoModule{}
通用的角色驗(yàn)證Guard
在上面的 MyTodoGuard
里面,我們判斷當(dāng)前的用戶是否具有 CUSTOMER
角色,如果我們能夠把這個(gè)需要判斷的 CUSTOMER
角色通過一種方式來傳遞到這個(gè)方法里面,然后通過傳遞不同的參數(shù),就可以用這個(gè)方法來判斷進(jìn)入任意頁面的用戶是否具有某個(gè)角色。我們可以使用Angular2路由里面的 data
屬性來實(shí)現(xiàn)。
當(dāng)我們定義一個(gè)路由時(shí),可以通過 data
屬性來給這個(gè)路由添加一些數(shù)據(jù),如下:
exportconstTodoRoutes: 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è)變量,比如在頁面上顯示。
exportclassTodoListComponent{ newTodo: Todo = newTodo(); todos: Todo[]; title: string; constructor(private todoService: TodoService, private route: ActivatedRoute) { this.todos =this.route.snapshot.data['todos']; this.title =this.route.data['title']; } // 省略其他 }
我們可以通過這種方式,在每個(gè)路由上配置title屬性,然后就可以用一種通用的方式來實(shí)現(xiàn)在頁面上顯示面包屑導(dǎo)航欄的功能。
但是,在這個(gè)實(shí)例中,我們要用 data 上添加的 role: 'CUSTOMER' ,用它來表示當(dāng)前的這個(gè)路徑,需要有 CUSTOMER 角色的用戶才能訪問。然后在 MyTodoGuard 里用它來判斷:
@Injectable() exportclassMyTodoGuardimplementsCanActivateChild{ canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) { if(!this.authService.isLogdedin()) { alert('You need to login!'); this.router.navigate(['/home']); returnfalse; } letrequiredRole = next.data['role']; if(requiredRole ==null||this.authService.hasRole(requiredRole)) { returntrue; } returnfalse; } }
在這里,我們從將要激活的路由的數(shù)據(jù)里面得到 role
,然后判斷當(dāng)前用戶是否具有這個(gè)角色。這樣,我們的這個(gè) MyTodoGuard
,可以把它定義在根路徑上,就可以作為一個(gè)通用的用戶權(quán)限驗(yàn)證的 Guard
來使用。只要路徑上存在這個(gè)值,就說明需要權(quán)限。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 詳解Angular開發(fā)中的登陸與身份驗(yàn)證
- AngularJS實(shí)現(xiàn)表單驗(yàn)證
- AngularJS使用ngMessages進(jìn)行表單驗(yàn)證
- AngularJS實(shí)現(xiàn)表單手動(dòng)驗(yàn)證和表單自動(dòng)驗(yàn)證
- 詳解AngularJS實(shí)現(xiàn)表單驗(yàn)證
- Angular2表單自定義驗(yàn)證器的實(shí)現(xiàn)
- angular實(shí)現(xiàn)表單驗(yàn)證及提交功能
- 教你用AngularJS框架一行JS代碼實(shí)現(xiàn)控件驗(yàn)證效果
- AngularJs驗(yàn)證重復(fù)密碼的方法(兩種)
- angular異步驗(yàn)證防抖踩坑實(shí)錄
相關(guān)文章
Angular.js中$apply()和$digest()的深入理解
相信大家都知道$digest()和$apply()是AngularJS中的兩個(gè)核心并且有時(shí)候容易引人誤解的部分。我們需要深入理解這兩者是如何運(yùn)作的,從而才能理解AngularJS本身是如何運(yùn)作的。本文的目的就是介紹$digest()和$apply()是如何確確實(shí)實(shí)的對你有用的。下面來一起看看吧。2016-10-10Angular 數(shù)據(jù)請求的實(shí)現(xiàn)方法
本篇文章主要介紹了Angular 數(shù)據(jù)請求的實(shí)現(xiàn)方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05AngularJS自定義指令之復(fù)制指令實(shí)現(xiàn)方法
這篇文章主要介紹了AngularJS自定義指令之復(fù)制指令實(shí)現(xiàn)方法,結(jié)合完整實(shí)例形式分析了AngularJS自定義指令實(shí)現(xiàn)復(fù)制功能的相關(guān)操作技巧,需要的朋友可以參考下2017-05-05Angular2中Bootstrap界面庫ng-bootstrap詳解
不知道大家有沒有留意,最近angular-ui團(tuán)隊(duì)終于正式發(fā)布了基于 Angular2的Bootstrap界面庫ng-bootstrap ,之前工作中一直在用 AngularJS 1.x 的UI Bootstrap , 因此對這個(gè)ng-bootstrap 也是很感興趣,所以第一時(shí)間進(jìn)行試用。這篇文章就給大家詳細(xì)介紹下ng-bootstrap。2016-10-10AngularJS中的指令實(shí)踐開發(fā)指南(二)
這篇文章主要介紹了AngularJS中的指令實(shí)踐指南(二)的相關(guān)資料,需要的朋友可以參考下2016-03-03使用 Github Actions 自動(dòng)部署 Angular 應(yīng)用到 Github Pages的方法
這篇文章主要介紹了使用 Github Actions 自動(dòng)部署 Angular 應(yīng)用到 Github Pages,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07AngularJS 中的指令實(shí)踐開發(fā)指南(一)
指令(Directives)是所有AngularJS應(yīng)用最重要的部分。盡管AngularJS已經(jīng)提供了非常豐富的指令,但還是經(jīng)常需要?jiǎng)?chuàng)建應(yīng)用特定的指令。這篇教程會(huì)為你講述如何自定義指令,以及介紹如何在實(shí)際項(xiàng)目中使用2016-03-03淺談Angularjs link和compile的使用區(qū)別
下面小編就為大家?guī)硪黄獪\談Angularjs link和compile的使用區(qū)別。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-10-10