AngularJs基于角色的前端访问控制的实现


Posted in Javascript onNovember 07, 2016

最近做的项目是使用Angular做一个单页应用,但因为用户有不同的角色(管理员、编辑、普通财务人员等),所以需要进行不同角色的访问控制。

因为后端访问控制的经验比较丰富,所以这里只记录了前端访问控制的实现。请注意,前端最多只能做到显示控制!并不能保证安全,所以后端是一定要做访问控制的!

基于角色的访问控制需要做到两个层面的访问控制:

  1. 控制页面路由的跳转,没有权限的用户不能跳转到指定url
  2. 页面元素的显示控制,没有对应权限的用户不能看到该元素

但在此之前,我们还有一项重要的事要做。

存储用户信息

首先我们要做的,并不是和访问控制有关的事,首先我们要保存好用户信息。包括用户的基本信息,如用户名、真实姓名;以及用户角色。下面是数据结构:

user = {
  username:"",
  realname:"",
  role:""
}

存储的时候就将整个user存储,但存在哪里呢?考虑到必须在任何页面都可以访问到,第一反应是存储到rootScope中,但我们应该尽量避免使用rootScope;除此之外,我们可以存储在顶级的controller或者是全局的constant中,这两种解决方案都可以,但它们的问题就是一旦页面刷新,就不管用了($rootScope也一样)。考虑到user这个变量的生命周期应该要与session相同,所以,我使用了SessionStorage。

在创建controller时,需要加入$sessionStorage:

app.controller('controller',['$sessionStorage', function($sessionStorage){}]);

在登录成功后,将user存储到SessionStorage中:

$sessionStorage.USER = user;

好了,之后通过$sessionStorage就可以获取到用户信息了。

user = $sessionStorage.USER;

控制页面路由的跳转

下面我们开始实现第一点:控制页面路由的跳转。

要做到第一点比较容易,Angular路由改变时会触发$stateChangeStart事件(我用的是stateProvider,所以监听stateChangeStart,如果是用的route或是location,应该监听它们对应的事件),监听此事件,在里面根据访问的url以及用户角色进行权限判断,比如登录的判断就可以在里面做,访问那个url需要登录就直接跳转到登录界面。

首先先写一个auth服务,用于权限认证:

/**
 * 基于角色的访问控制
 */
App.service("auth", ["$http","$sessionStorage", function($http, $sessionStorage){
  var roles = []; // 从后端数据库获取的角色表
  // 从后端获取的角色权限Url映射表,结构为{"role":["/page1", "/page2"……]}
  var urlPermissions = {};
  // 去后端获取
  (function(){
   // 此处为测试方便,直接赋值了,下面也仅以示例为目的,尽量简单了
   roles = ["admin", "user"]
   urlPermissions = {
    // 管理员可以访问所用页面
    "admin":["*"], 
    // 普通用户可以访问page路径下的所有界面(登录、注册等页面)以及系统主页
    "user":["page.*", "app.index", "app.detail"] 
   }
  })();
  function convertState(state) {
   return state.replace(".", "\\\.").replace("*", ".*");
  }
  return {
   // 是否有访问某url的权限
   isAccessUrl:function(url) {
    var user = $sessionStorage.USER;
    for(var role in roles) {
     if(user.role.toLowerCase() == roles[role].toLowerCase()) {
      console.log(urlPermissions[roles[role]])
      for(i in urlPermissions[roles[role]]) {
       var regx = eval("/"+convertState(urlPermissions[roles[role]][i])+"/");
       console.log(regx+ " "+ url)
       if(regx.test(url)) {
        return true;
       }
      }
     }
    }
    return false;
   }
  }

}])

roles是角色,从后台获取;urlPermissions是每个角色对应的能被其访问的url列表,也从后台获取,可通过后台配置。这样,每次新增角色,我们就可以动态为其配置访问权限。

最重要的是isAccessUrl方法,传入url后,isAccessUrl首先会通过$sessionStorage获取用户信息,取得用户角色,然后看用户角色是否在角色表中;若在角色表中,就看此角色是否有访问url的权限。我们在后台配置的时候,是直接指定状态,但如果没有通配符的话,那么每一个页面都得写一个url,所以,就增加了通配符 功能,然后将url列表中的每个url转化为正则表达式,再来验证,这样配置就灵活了很多。

最后是在run中监听事件$stateChangeStart :

App.run(["$rootScope",'$state', "auth", "$sessionStorage", function($rootScope, $state, auth, $sessionStorage){
 $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
  // 路由访问控制
  if(toState.name!="page.login" && !auth.isAccessUrl(toState.name)) { 
   // 查看是否需要登录:
   var user = $sessionStorage.USER;
   if(user == null) {
    event.preventDefault();
    $state.go("page.login");
    return;
   }
   event.preventDefault();
   $state.go("page.error"); 
  }
});
}])

好了,现在就实现了url的访问控制。

页面元素的显示控制

至于第二点,我的解决方案是自定义指令,下面是示例:

<div zg-access="TEST_ACCESS"></div>

注意,这里传入的不是角色,而是权限。因为用户角色是可以动态扩展的,如果这里写的是什么样的角色才可以访问这个元素,那以后每新增一个角色都将是一个很大很大的麻烦,因为你得一个个来修改代码。下面是自定义指令zg-access的代码:

/**
 * 元素级别的访问控制指令
 */

App.directive("zgAccess", function($sessionStorage, $http){
 var roles = []; // 角色
 var elemPermissions = {}; // 角色元素权限映射表,如{ "role":{"SEARCH"}},role有这个搜索权限

 // 后台获取
 (function(){
  // 简便起见,这里直接生成
  roles = ["admin", "user", "visitor"];
  elemPermission = {
   "admin":["*"],
   "user":["SEARCH"],
   "visitor":[]
  }
 })();
 console.log("zg-access");
 return {
  restrict: 'A',
  compile: function(element, attr) {
    // 初始为不可见状态none,还有 禁用disbaled和可用ok,共三种状态
    var level = "none";
    console.log(attr)
    if(attr && attr["zgAccessLevel"]) {
     level = attr["zgAccessLevel"];
    }
    switch(level) {
     case "none": element.hide(); break;
     case "disabled": 
      element.attr("disabled", "");
      break;
    }
    // 获取元素权限
    var access = attr["zgAccess"];
    // 将此权限上传到后端的数据库
    (function(){
     //upload 
    })();
    return function(scope, element) {
     // 判断用户有无权限
     var user = $sessionStorage.USER;
     if(user==null||angular.equals({}, user)) {
      user = {};
      user.role = "visitor";
     }
     var role = user.role.toLowerCase();
     console.log(roles);
     for(var i in roles) {
      var tmp = roles[i].toLowerCase();
      if(role == tmp) {
       tmp = elemPermission[role];
       console.log(tmp)
       for(var j in tmp){
        console.log(tmp[j]+" "+access);
        if(access.toLowerCase() == tmp[j].toLowerCase()) {
         element.removeAttr("disabled");
         element.show();
        } 
       }
      }
     }
    };
   }
 }
})

zgAccessLevel是一个属性,用来控制级别,如果是none(默认为none),就不显示元素;如果是disbaled,就是元素不可用(如Button不可用)。

下面是元素示例:

<button ng-click="" zg-access="SEARCH" zg-access-level="disabled">Search</button>

此时,若以admin角色或者user角色登录,Search按钮将不可用。

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

Javascript 相关文章推荐
11款基于Javascript的文件管理器
Oct 25 Javascript
理解jQuery stop()方法
Nov 21 Javascript
JQuery页面地址处理插件jqURL详解
May 03 Javascript
jQuery 获取select选中值及清除选中状态
Dec 13 Javascript
Angular 4.x 动态创建表单实例
Apr 25 Javascript
全面解析vue中的数据双向绑定
May 10 Javascript
简单实现js进度条加载效果
Mar 25 Javascript
jquery在启动页面时,自动加载数据的实例
Jan 22 jQuery
Angular angular-file-upload文件上传的示例代码
Aug 23 Javascript
vue.js单文件组件中非父子组件的传值实例
Sep 13 Javascript
vue-router的钩子函数用法实例分析
Oct 26 Javascript
ES2020 新特性(种草)
Jan 12 Javascript
easyui取消表单实时验证,提交时统一验证的简单实例
Nov 07 #Javascript
AngularJS动态绑定HTML的方法分析
Nov 07 #Javascript
基于JS分页控件实现简单美观仿淘宝分页按钮效果
Nov 07 #Javascript
easyui form validate总是返回false的原因及解决方法
Nov 07 #Javascript
jquery文字填写自动高度的实现方法
Nov 07 #Javascript
JS 全屏和退出全屏详解及实例代码
Nov 07 #Javascript
easyui tree带checkbox实现单选的简单实例
Nov 07 #Javascript
You might like
给海燕B411配件机起死回生配上件
2021/03/02 无线电
PHP中的CMS的涵义
2007/03/11 PHP
javascript编程起步(第二课)
2007/02/27 Javascript
在html页面上拖放移动标签
2010/01/08 Javascript
javascript整除实现代码
2010/11/23 Javascript
jQuery 1.5.1 发布,全面支持IE9 修复大量bug
2011/02/26 Javascript
使用documentElement正确取得当前可见区域的大小
2014/07/25 Javascript
jQuery中:radio选择器用法实例
2015/01/03 Javascript
JavaScript针对网页节点的增删改查用法实例
2015/02/02 Javascript
jQuery实现分隔条左右拖动功能
2015/11/21 Javascript
Javascript 实现简单计算器实例代码
2016/10/23 Javascript
Bootstrap中定制LESS-颜色及导航条(推荐)
2016/11/21 Javascript
canvas实现绘制吃豆鱼效果
2017/01/12 Javascript
js cookie实现记住密码功能
2017/01/17 Javascript
webpack 样式加载的实现原理
2018/06/12 Javascript
在vue中利用全局路由钩子给url统一添加公共参数的例子
2019/11/01 Javascript
vue+vant-UI框架实现购物车的复选框全选和反选功能
2019/11/05 Javascript
解决Vue @submit 提交后不刷新页面问题
2020/07/18 Javascript
scrapy spider的几种爬取方式实例代码
2018/01/25 Python
Python中栈、队列与优先级队列的实现方法
2019/06/30 Python
Python 实现文件读写、坐标寻址、查找替换功能
2019/09/11 Python
Pycharm 2019 破解激活方法图文详解
2019/10/11 Python
使用django自带的user做外键的方法
2020/11/30 Python
详解三种方式实现平滑滚动页面到顶部的功能
2019/04/23 HTML / CSS
Linux操作面试题
2012/05/16 面试题
优秀大学生推荐信范文
2013/11/28 职场文书
小学国旗下的演讲稿
2014/08/28 职场文书
运动会铅球比赛加油稿
2014/09/26 职场文书
一般基层干部群众路线教育实践活动个人对照检查材料
2014/11/04 职场文书
2015大学生自我评价范文
2015/03/03 职场文书
2015年前台接待工作总结
2015/05/04 职场文书
女儿满月酒致辞
2015/07/29 职场文书
2016年春季运动会通讯稿
2015/11/25 职场文书
2016反腐倡廉警示教育心得体会
2016/01/13 职场文书
vue里使用create, mounted调用方法
2022/04/26 Vue.js
html原生table实现合并单元格以及合并表头的示例代码
2023/05/07 HTML / CSS