Angular2使用Guard和Resolve进行验证和权限控制


Posted in Javascript onApril 24, 2017

我们在开发web应用时,在服务器端都会控制某种或某个用户是否有权限调用某个接口。在前端,我们除了根据用户的角色或其他特性来控制一些页面元素是否显示以外,也需要控制用户是否能够进入某些页面(例如通过直接输入URL的方式直接进入)。要控制是否显示,我们可以使用 *ngIf [hidden] 等方式。而对于控制用户能否进入某个页面,Angular2的路由框架也提供了非常方便的方式来实现这个功能。

Angular2提供了2种组件, Guard 和 Resolve 。 Guard 顾名思义就是用来保护一个路径。可以用来判断用户只有在满足一定的条件的情况下才能打开这个路径对应的页面。 Resolve 用来在进入某个路径之前先获取数据。

Guard

Guard 其实是一系列接口,只要你实现了它的方法,配置了这些 Guard ,Angular路由框架就会根据这个方法返回的 true 或 false 来判断是否激活这个路由。它包括几种类型:

1、CanActivate

这种类型的 Guard 用来控制是否允许进入当前的路径。

2、CanActivateChild

这种类型的 Guard 用来控制是否允许进入当前路径的所有子路径。

3、CanDeactivate

用来控制是否能离开当前页面进入别的路径

4、CanLoad

用于控制一个异步加载的子模块是否允许被加载。

以 CanActivate 为例,这个接口的定义如下:

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

这个接口定义了一个方法,当你实现这个接口,并把它配置到某一个路由上以后,当用户进入这个路由的路径之前,就会调用它里面的 canActivate() 方法,它第一个参数,就是将要激活的路由,第二个参数是路由器当前的状态。它返回一个布尔型的结果,或者是布尔型的 Promise Observable

Resolve

这跟Angular1中ui-router库的 resolve 类似,就是用来在打开一个页面之前先获取数据,而不是进入页面以后再加载。这个接口中的方法,可以返回任意的对象,也可以返回一个 Promise ,或者 Observable

如果在一个路径上同时设置了 CanActivate 和 Resolve ,首先 CanActivate 接口的方法会被执行,当这个路由可以被激活时, Resolve 接口的方法才会被执行。

实例

下面,我们来通过一个比较完整的实例,来看看, CanActivate , CanActivateChild , CanDeactivate 和 Resolve 的用法。( CanLoad 将会在之后介绍子模块、异步加载的文章中再介绍)。这篇教程的源代码可以在这里 查看。

场景

我们还是用之前的教程 Angular2入门教程-2 实现TodoList App 中的实例。

我们先来看一看要解决的一些问题:

  1. 系统的默认页是home页面,这个页面不需要登录也可以打开。
  2. 登陆以后,管理员和用户分别进入不同的页面。
  3. 所有的todo模块的页面都需要用户角色,管理页面需要管理员角色
  4. 在进入任务列表页面之前,需要获取任务列表数据,而不是进入页面以后再获取数据。
  5. 当用户离开任务详情页时,提示是否确认要离开。

默认页面home

默认页面就是当用户直接打开你的网页域名,没有输入任何路径的情况下,默认打开的页面,在之前的教程已经讲过,这是在配置路由的时候,用 redirect 实现:

{
 path: '',
 redirectTo: '/home',
 pathMatch: 'full'
}

AuthService

首先我们需要一个权限验证的服务 AuthService ,除了用来进行登陆操作,还用于验证是否登陆,是否具有拥有某种角色。具体代码如下:

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 和它的一个方法 of 。这是由于我们的登陆操作一般都是去服务器端进行登陆验证,而使用 Http 服务从服务器端获取数据一般都是返回 Observable ,所以这里也使用 Observable 来返回登陆后的用户信息。我们引入 of 方法,是因为我们对 Observable 的操作都是需要什么操作符就引入什么,而不是直接引入所有的。

最后的 hasRole(role) 方法的用途是,我们可以在页面上通过 ngIf="hasRole('CUSTOMER')" 的方式来控制是否显示某个页面元素。

原先的todo路由定义

之前todo模块的路由是这样:

exportconstTodoRoutes: Route[] = [
 {
 path: 'todo',
 children: [
 {
 path: 'list',
 component: TodoListComponent
 },
 {
 path: 'detail/:id',
 component: TodoDetailComponent
 }
 ]
 }
];

在路径 todo 下面,有两个子路由,分别是列表和详情。

然后再针对下面的需求,一个个来解决:

  1. 所有的todo模块的页面都需要用户角色
  2. 离开详情页需要确认
  3. 进入列表页面之前需要先获取任务列表数据

控制所有todo模块的都需要用户角色

对于第一个,我们要保护所有的todo模块的页面,也就是'/todo'路径的所有子路径,所以,我们可以使用 CanActivateChild 。这样,在每进入一个todo的子路径的时候,都会先进行检查来判断能否进入。代码如下:

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

这个 Guard 的实现很简单,就是用 authService 来判断是否登陆,以及是否具有'CUSTOMER'角色。

注意这个 Guard 的实现也必须是 Injectable 的,因为我们需要Angular的依赖注入帮我们创建实例和自动注入。

离开详情页需要确认

接下来我们看怎么实现离开详情页时的确认,也很简单,就是使用 CanDeactivate ,并把它定义在详情页的路由定义上。

@Injectable()
exportclassCanLeaveTodoDetailGuardimplementsCanDeactivate<TodoDetailComponent>{
 canDeactivate(component: TodoDetailComponent, route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
returnconfirm('Confirm?');
 }
}

为了简单,上面的方法直接调用 confirm('confirm?') 并返回它的结果,它会返回一个布尔型的结果,表示用户是否确认。如果用户取消了,就不会离开详情页。

进入列表页面之前需要先获取数据

最后,再看看用 Resolve 来实现进入一个页面之前的数据初始化。

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

在这个 resolve() 方法中,直接返回调用 todoService getAllTodos() 方法的结果。对这个 getAllTodos() 方法我们做一些修改,让他返回一些测试数据:

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

在这个方法里我们创建了2个测试的任务,封装成 Observable 返回,并添加了一个3秒钟的延时,来模拟从服务器端获取数据的过程。

通过 Resolve 方式获取的数据,会放在被激活的当前路由的 data 属性里面,我们可以在组件中来获得。所以,需要修改 TodoListComponent ,从路由的数据 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'的路由上加了一个 canActivateChild 控制能否激活子路径, 在 list 的子路径上配置了一个 resolve 来获取数据,在 detail/:id 上配置了一个 canDeactivate 来控制能否离开。

最后,别忘了我们还需要在 todo 模块的定义 TodoModule 里面的 providers 里添加这些,这样依赖注入功能才能使用这些服务。

@NgModule({
 imports: [CommonModule, FormsModule ],
 declarations: [TodoListComponent, TodoDetailComponent, TodoItemComponent],
 providers: [TodoService, MyTodoResolver, MyTodoGuard, CanLeaveTodoDetailGuard]
})
exportclassTodoModule{}

通用的角色验证Guard

在上面的 MyTodoGuard 里面,我们判断当前的用户是否具有 CUSTOMER 角色,如果我们能够把这个需要判断的 CUSTOMER 角色通过一种方式来传递到这个方法里面,然后通过传递不同的参数,就可以用这个方法来判断进入任意页面的用户是否具有某个角色。我们可以使用Angular2路由里面的 data 属性来实现。

当我们定义一个路由时,可以通过 data 属性来给这个路由添加一些数据,如下:

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'这个路由添加了1个变量,角色,我们可以在这个路由定义的组件以及它所有的子组件中的当前路由中得到这些数据。而且在子路由里,都添加了一个 title 的变量。然后在 TodoListComponent 里面就可以使用这个变量,比如在页面上显示。

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'];
 }
// 省略其他
}

我们可以通过这种方式,在每个路由上配置title属性,然后就可以用一种通用的方式来实现在页面上显示面包屑导航栏的功能。

但是,在这个实例中,我们要用 data 上添加的 role: 'CUSTOMER' ,用它来表示当前的这个路径,需要有 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;
 }
}

在这里,我们从将要激活的路由的数据里面得到 role ,然后判断当前用户是否具有这个角色。这样,我们的这个 MyTodoGuard ,可以把它定义在根路径上,就可以作为一个通用的用户权限验证的 Guard 来使用。只要路径上存在这个值,就说明需要权限。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
单独使用CKFinder选择图片的方法
Aug 21 Javascript
深入理解JavaScript系列(35):设计模式之迭代器模式详解
Mar 03 Javascript
Javascript 闭包详解及实例代码
Nov 30 Javascript
canvas实现图像截取功能
Feb 06 Javascript
Vue.js实战之利用vue-router实现跳转页面
Apr 01 Javascript
前端开发不得不知的10个最佳ES6特性
Aug 30 Javascript
JavaScript实现焦点进入文本框内关闭输入法的核心代码
Sep 20 Javascript
Es6 Generator函数详细解析
Feb 24 Javascript
利用vue.js把静态json绑定bootstrap的table方法
Aug 28 Javascript
layui radio点击事件实现input显示和隐藏的例子
Sep 02 Javascript
微信小程序登陆注册功能的实现代码
Dec 10 Javascript
交互式可视化js库gojs使用介绍及技巧
Feb 18 Javascript
详解AngularJS 路由 resolve用法
Apr 24 #Javascript
详解AngularJS ui-sref的简单使用
Apr 24 #Javascript
详解在Angularjs中ui-sref和$state.go如何传递参数
Apr 24 #Javascript
JS实现获取图片大小和预览的方法完整实例【兼容IE和其它浏览器】
Apr 24 #Javascript
angular中实现控制器之间传递参数的方式
Apr 24 #Javascript
使用vue框架 Ajax获取数据列表并用BootStrap显示出来
Apr 24 #Javascript
JS实现加载和读取XML文件的方法详解
Apr 24 #Javascript
You might like
php的hash算法介绍
2014/02/13 PHP
浅析ThinkPHP中execute和query方法的区别
2014/06/13 PHP
Laravel 简单实现Ajax滚动加载示例
2019/10/22 PHP
js实现的网站首页随机公告随机公告
2007/03/14 Javascript
javascript学习笔记(十八) 获得页面中的元素代码
2012/06/20 Javascript
js几秒以后倒计时跳转示例
2013/12/26 Javascript
单击和双击事件的冲突处理示例代码
2014/04/03 Javascript
ie 7/8不支持trim的属性的解决方案
2014/05/23 Javascript
jQuery获取对象简单实现方法小结
2014/10/30 Javascript
对比分析json及XML
2014/11/28 Javascript
javascript实现回车键提交表单方法总结
2015/01/10 Javascript
JavaScript实现数组在指定位置插入若干元素的方法
2015/04/06 Javascript
浅谈jquery中delegate()与live()
2015/06/22 Javascript
js验证框架实现代码分享
2016/05/18 Javascript
jquery控制页面的展开和隐藏实现方法(推荐)
2016/10/15 Javascript
常用的javascript设计模式
2017/01/11 Javascript
Vue 过渡实现轮播图效果
2017/03/27 Javascript
详解Vue.js 2.0 如何使用axios
2017/04/21 Javascript
详解vue项目构建与实战
2017/06/27 Javascript
JS 中document.write()的用法和清空的原因浅析
2017/12/04 Javascript
vue 中swiper的使用教程
2018/05/22 Javascript
JavaScript实现动态添加、移除元素或属性的方法分析
2019/01/03 Javascript
React Native中ScrollView组件轮播图与ListView渲染列表组件用法实例分析
2020/01/06 Javascript
Python爬取成语接龙类网站
2018/10/19 Python
Python3.6+Django2.0以上 xadmin站点的配置和使用教程图解
2019/06/04 Python
使用python的pandas为你的股票绘制趋势图
2019/06/26 Python
python3实现往mysql中插入datetime类型的数据
2020/03/02 Python
CSS3实现div从下往上滑入滑出效果示例
2020/04/28 HTML / CSS
迪卡侬波兰体育用品商店:Decathlon波兰
2020/03/31 全球购物
公司联欢晚会主持词
2014/03/22 职场文书
《草原的早晨》教学反思
2014/04/08 职场文书
领导班子自我剖析材料
2014/08/16 职场文书
2015年班级元旦晚会活动总结
2014/11/28 职场文书
合同审查法律意见书
2015/06/04 职场文书
爱鸟护鸟的宣传语
2015/07/13 职场文书
Go语言 go程释放操作(退出/销毁)
2021/04/30 Golang