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 相关文章推荐
Jquery Ajax学习实例5 向WebService发出请求,返回泛型集合数据的异步调用
Mar 17 Javascript
JavaScript中各种编码解码函数的区别和注意事项
Aug 19 Javascript
与jquery serializeArray()一起使用的函数,主要来方便提交表单
Jan 31 Javascript
纯js实现手风琴效果
Apr 17 Javascript
AngularJS 输入验证详解及实例代码
Jul 28 Javascript
JS使用onerror捕获异常示例
Aug 03 Javascript
基于对象合并功能的实现示例
Oct 10 Javascript
详解VueJS应用中管理用户权限
Feb 02 Javascript
JavaScript使用ul中li标签实现删除效果
Apr 15 Javascript
js面向对象之实现淘宝放大镜
Jan 15 Javascript
Vue组件间的通信pubsub-js实现步骤解析
Mar 11 Javascript
js实现全选和全不选功能
Jul 28 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
Smarty结合Ajax实现无刷新留言本实例
2007/01/02 PHP
关于在php.ini中添加extension=php_mysqli.dll指令的说明
2007/06/14 PHP
php的数组与字符串的转换函数整理汇总
2013/07/18 PHP
PHP查询并删除数据库多列重复数据的方法(利用数组函数实现)
2016/02/23 PHP
浅谈PHP发送HTTP请求的几种方式
2017/07/25 PHP
phpStudy中升级MySQL版本到5.7.17的方法步骤
2017/08/03 PHP
PHP高精确度运算BC函数库实例详解
2017/08/15 PHP
详解PHP字符串替换str_replace()函数四种用法
2017/10/13 PHP
jquery和javascript中如何将一元素的内容赋给另一元素
2014/01/09 Javascript
javascript中innerText和innerHTML属性用法实例分析
2015/05/13 Javascript
jQuery可见性过滤器:hidden和:visibility用法实例
2015/06/24 Javascript
JS实现列表的响应式排版(推荐)
2016/09/01 Javascript
常见的浏览器Hack技巧整理
2017/06/29 Javascript
vue proxyTable 接口跨域请求调试的示例
2017/09/12 Javascript
代码分析vue中如何配置less
2018/09/28 Javascript
Vue 实时监听窗口变化 windowresize的两种方法
2018/11/06 Javascript
js中的reduce()函数讲解
2019/01/18 Javascript
微信小程序swiper使用网络图片不显示问题解决
2019/12/13 Javascript
JS实现水平移动与垂直移动动画
2019/12/19 Javascript
探索node之事件循环的实现
2020/10/30 Javascript
[00:30]塑造者的传承礼包-戴泽“暗影之焰”套装展示视频
2014/04/04 DOTA
[47:46]完美世界DOTA2联赛 Magma vs GXR 第三场 11.07
2020/11/10 DOTA
Django框架下在URLconf中指定视图缓存的方法
2015/07/23 Python
python3 实现一行输入,空格隔开的示例
2018/11/14 Python
使用pycharm在本地开发并实时同步到服务器
2019/08/02 Python
对YOLOv3模型调用时候的python接口详解
2019/08/26 Python
python多进程并行代码实例
2019/09/30 Python
浅谈cv2.imread()和keras.preprocessing中的image.load_img()区别
2020/06/12 Python
Python+unittest+requests 接口自动化测试框架搭建教程
2020/10/09 Python
使用html5实现表格实现标题合并的实例代码
2019/05/13 HTML / CSS
BIBLOO波兰:捷克的一家在线服装店
2018/03/09 全球购物
电子商务专业在校生实习自我鉴定
2013/09/29 职场文书
采购经理岗位职责
2014/02/16 职场文书
防灾减灾宣传标语
2014/10/07 职场文书
优秀教师申报材料
2014/12/16 职场文书
亲戚关系证明
2015/06/24 职场文书