热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

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

我们在开发web应用时,在服务器端都会控制某种或某个用户是否有权限调用某个接口。在前端,我们除了根据用户的角色或其他特性来控制一些页面元素是

我们在开发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 | Promise | 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 {
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{
 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{

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 {
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 来使用。只要路径上存在这个值,就说明需要权限。

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


推荐阅读
  • 使用虚拟机配置服务器
    本文详细介绍了如何使用虚拟机配置服务器,包括购买云服务器的操作步骤、系统默认配置以及相关注意事项。通过这些步骤,您可以高效地配置和管理您的服务器。 ... [详细]
  • 本文深入解析了 Kubernetes 控制平面(特别是 API 服务器)与集群节点之间的通信机制,并对其通信路径进行了详细分类。旨在帮助用户更好地理解和定制其安装配置,从而增强网络安全性,确保集群的稳定运行。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 如何有效防御网站中的SQL注入攻击
    本期文章将深入探讨网站如何有效防御SQL注入攻击。我们将从技术层面详细解析防范措施,并结合实际案例进行阐述,旨在帮助读者全面了解并掌握有效的防护策略。希望本文能为您的网络安全提供有益参考。 ... [详细]
  • Spring框架的核心组件与架构解析 ... [详细]
  • 隐藏的威胁:你的供应链是否真正安全?
    在网络环境日益复杂的当下,诸如网络钓鱼、DNS欺骗、勒索软件和中间人(MITM)攻击等威胁手段已司空见惯。这些攻击手段无孔不入,对供应链的安全构成了严重挑战。企业必须加强安全意识,采取多层次的防护措施,以确保供应链的每一个环节都得到有效保护。 ... [详细]
  • 陕西师范大学计算机科学学院的研究生教育具有显著的优势。学院拥有一流的师资队伍和先进的科研平台,致力于培养具备扎实理论基础和创新能力的高层次人才。近年来,该学院在人工智能、大数据处理和网络安全等领域取得了多项重要科研成果,为学生提供了丰富的学术资源和实践机会。此外,学院还注重学生的综合素质培养,通过多种途径提升学生的创新能力和实践能力,为学生的未来发展奠定了坚实的基础。 ... [详细]
  • Bagels:融合挖矿、借贷、跨链与预言机功能的多合一创新项目即将启动
    Bagels:融合挖矿、借贷、跨链与预言机功能的多合一创新项目即将启动 ... [详细]
  • 距离服贸会开幕仅剩3天,期待与您相约共襄盛会 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • 在PHP中使用`exec`函数执行Shell命令时,需要特别注意安全性问题。当使用来自远程的数据构建命令时,极易引发命令注入漏洞。本文详细探讨了如何避免使用污染数据,确保系统命令的安全执行,并提供了多种防护措施和最佳实践。 ... [详细]
  • 《Spring in Action 第4版:全面解析与实战指南》
    《Spring in Action 第4版:全面解析与实战指南》不仅详细介绍了Spring框架的核心优势,如简洁易测试、低耦合特性,还深入探讨了其轻量级和最小侵入性的设计原则。书中强调了声明式编程的优势,并通过基于约定的方法简化开发流程。此外,Spring的模板机制有效减少了重复代码,而依赖注入功能则由容器自动管理,确保了应用的灵活性和可维护性。 ... [详细]
  • 在使用SSH框架进行项目开发时,经常会遇到一些常见的问题。例如,在Spring配置文件中配置AOP事务声明后,进行单元测试时可能会出现“No Hibernate Session bound to thread”的错误。本文将详细探讨这一问题的原因,并提供有效的解决方案,帮助开发者顺利解决此类问题。 ... [详细]
  • 每日学术推荐:异质图神经网络在抽取式文档摘要中的应用研究
    在抽取式文档摘要任务中,学习跨句子关系是至关重要的一步。本文探讨了利用异质图神经网络来捕捉句子间复杂关系的有效方法。通过构建包含不同类型节点和边的图结构,模型能够更准确地识别和提取关键信息,从而生成高质量的摘要。实验结果表明,该方法在多个基准数据集上显著优于传统方法。 ... [详细]
  • SQL注入防御:深入解析绕过addslashes技术的方法与策略 ... [详细]
author-avatar
手机用户2602883105
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有