权限管理是无线运营系统中的核心模块,经过访问控制策略的配置,来约定人与资源的访问关系。php
本文着重讲解如何经过PHP来构建一个灵活、通用、安全的权限管理系统。html
首先咱们来聊聊权限。git
权限系统一直以来是咱们应用系统不可缺乏的一个部分,若每一个应用系统都从新对系统的权限进行设计,以知足不一样系统用户的需求,将会浪费咱们很多宝贵时间,因此花时间来设计一个相对通用的权限系统是颇有意义的。github
系统目标:对应用系统的全部对象资源和数据资源进行权限控制,好比 应用系统的功能菜单、各个界面的按钮、数据显示的列以及各类行级数据 进行权限的操控。shell
设计初期,咱们学习了Amazon的 IAM ,通过对比分析,最终咱们选用 RBAC3模型 来指导系统的设计工做。数据库
RBAC认为权限受权其实是Who、What、How的问题。在RBAC模型中,who、what、how构成了访问权限三元组,也就是“Who对What(Which)进行How的操做”。编程
Who:权限的拥用者或主体(如Principal、User、Group、Role、Actor等等)json
What:权限针对的对象或资源(Resource、Class)。bootstrap
How:具体的权限(Privilege,正向受权与负向受权)。设计模式
Operator:操做。代表对What的How操做。也就是Privilege+Resource
Role:角色,必定数量的权限的集合。权限分配的单位与载体,目的是隔离User与Privilege的逻辑关系.
「如何用PHP构建咱们的权限中心」
接下来咱们将从 编码规范、依赖管理、数据源架构、数据处理、单元测试 等方面来体验一把PHP的神奇之旅。
好的编码规范能够改善软件的可读性,能够促进团队成长,能够减小Bug,能够下降维护成本,能够。。。(这么X,咱们必需要推广)
PHP社区一直百花齐放,拥有大量的函数库、框架和组件,于是PHP代码遵循或尽可能接近同一个代码风格就很是重要。
框架互操做组(即PHP标准组)发布了一系列推荐风格。
权限中心的目录结构:
-- /tuniu/rbac |-- src | |-- App //应用建模层 | | |-- City.php | | |-- Cms.php | | |-- Menu.php | |-- App.php | |-- Auth.php | |-- Orm //ActiveRecord层 | | |-- App | | | |-- Resource | | | | |-- Map.php | | | |-- Resource.php | | | |-- User.php | | |-- App.php | | |-- Log.php | | |-- Role | | | |-- User.php | | |-- Role.php | | |-- Rule.php | | |-- User.php | |-- Orm.php | |-- Utils.php |-- tests //测试 | |-- bootstrap.php | |-- fixtures | | |-- null.yml | | |-- rbac | | | |-- app.yml | | | |-- auth.yml | | | |-- role.yml | |-- rbac | | |-- appTest.php | | |-- authTest.php | | |-- roleTest.php |-- vendor |-- composer.json |-- composer.lock |-- phpunit.xml |-- README.md
PSR-2,权限应用资源类:
namespace Tuniu\Rbac\Orm\App; use Tuniu\Rbac\Orm; use Tuniu\Rbac\Orm\App; use Tuniu\Rbac\Orm\App\Resource\Map; use Tuniu\Rbac\Orm\Rule; use Tuniu\Rbac\Orm\User; class Resource extends Orm {}
PSR-4,命名空间的约定:
\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
类名 | 文件路径 |
---|---|
\Tuniu\Rbac\Orm\App\Resource |
/tuniu/rbac/src/Orm/App/Resource.php |
\Tuniu\Rbac\App\Cms |
/tuniu/rbac/src/App/Cms.php |
Composer 是PHP中用来管理依赖(dependency)关系的工具。你能够在本身的项目中声明所依赖的外部工具库(libraries),Composer会帮你安装这些依赖的库文件。
权限中心的依赖声明:
{ "name": "tuniu/rbac", "require": { //声明依赖关系 "php": ">=5.3.0", "squizlabs/php_codesniffer": "2.*", //PHP_CodeSniffer检查代码规范 "php-activerecord/php-activerecord": "dev-master" //ActiveRecord }, "require-dev": { //声明开发依赖 "phpunit/phpunit": "~4.6", "phpunit/dbunit": ">=1.2" }, "autoload": { "psr-4": { "Tuniu\\Rbac\\": "src/" //命名空间 } } }
检查代码规范,执行单元测试。
$./vendor/bin/phpcs --config-set default_standard PSR2 $./vendor/bin/phpcs src $./vendor/bin/phunit
^^^^^^ 眼涩,眼酸,眼疲劳,怎么办。。。
骚年,若是舒服了,就使劲往下滑动吧。。。
争渡,争渡,惊起一滩鸥鹭。
基于权限中心各表的关系(各类关联,各类回调),采用传统的模式,必将把精力耗在无尽的循环中。
因而乎,开始寻觅一种数据源的架构模式,Active Record很靠谱的出现了。
Active Record(中文名:活动记录)是一种领域模型模式,特色是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。
PHP ActiveRecord 是一个基于ActiveRecord设计模式开发的开源PHP/ORM库。它旨在大大简化与数据库的交互和减小手写SQL语句。它不一样于其余的ORM,你不须要使用任何的代码生成器,也不费劲去手写、维护模型层的表映射文件。这个库的灵感来自Ruby on Rails,所以它也借鉴Ruby on Rails的想法和实现。(谁用谁知道)
下面介绍下这个小伙伴给编程带来的快乐:
Validation(数据验证)
validates_presence_of
validates_inclusion_of
场景(角色表数据约定)
/** * 1:约定角色类型的范围 * 2:约定角色状态的范围 */ public static $validates_inclusion_of = array( array('f_type', 'in' => array('role', 'group', 'department', 'member')), array('f_status', 'in' => array(1,2)) ); /** * 设定角色名称、角色状态、角色类型、角色描述不能为空 */ public static $validates_presence_of = array( array('f_name'), array('f_status'), array('f_type'), array('f_desc') ); //录入数据不知足约定条件,就没法保存,不再用担忧那些脏脏的数据。
Callback(回调)
before_save
before_create
before_update
before_destroy
after_save
after_create
after_update
after_destroy
场景1(角色表操做记录)
//定义回调函数 public static $before_save = array('setMisc'); //每当角色表保存以前,都默默的把数据格式好,好开心。。。 public function setMisc() { //建立时间,建立人 $this->is_new_record() && ($this->f_create_at = date("Y-m-d H:i:s")); $this->is_new_record() && ($this->f_create_by = $this->op); //更新时间,更新人 $this->f_update_by = $this->op; //操做人 $this->f_update_at = date("Y-m-d H:i:s"); //操做时间 }
场景2(新增资源,推送资源映射表):
//定义回调函数 public static $after_save = array('syncRelations'); //每当资源保存以后,自动把资源数据中的映射字段值,推送给资源映射表。 public function syncRelations() { //获取资源的映射字段 $mapFiledValue = $this->getMapFiledValue(); $mapFiledValue ? $this->addMap($mapFiledValue) : Map::removeAllByResourceId($this->id); }
场景3(角色删除):
//定义回调函数 public static $after_destroy = array('deleteRelations'); //每当角色删除时,自动把系统中角色的成员和角色的资源规则清空,并且是事务的。 public function deleteRelations() { UserRelations::removeAllByRoleId($this->id); RuleRelatinos::removeAllByRoleId($this->id); }
Association(关联)
has_many
场景(新增角色用户)
//定义角色表和角色用户表的关系 public static $has_many = array( array( 'relations', 'foreign_key' => 'f_role_id', 'class_name' => "\\Tuniu\\Rbac\\Orm\\Role\\User", ) ); //新增角色用户,默默的把role_id传递给了角色用户表,此处若是用SQL,简直不忍直视。 Role::first()->create_relation( array( 'f_user_id' => $uid ) );
事务(一致性与安全性)
权限系统中数据一致性和数据安全性的重要性是不言而喻,不用事务会被BS的。
在此咱们郑重承诺,权限系统中每一次数据增删改请求,都是事务处理的。
好比角色保存:
self::transaction( function () use ($params, &$role) { $role->f_name = $params['name']; $role->f_status = $params['status']; $role->f_type = $params['type']; $role->f_desc = $params['desc']; if ($role->is_invalid()) { throw new \Exception('角色相关操做失败', '900202'); } $role->save(); } );
篇幅有限,这个小伙伴的战斗力场景远甚于此。
坦白的说,用AR是一种编程享受。
一切都如此的完美,没有测试,又如何能够证实这件事情的完美,又如何能够保障交付的质量。
PHPUnit 是一个轻量级的PHP测试框架。它是在PHP5下面对JUnit3系列版本的完整移植,是xUnit测试框架家族的一员(它们都基于模式先锋Kent Beck的设计)。
单元测试是一种提升软件质量很是有效的方法,但很重要的是咱们要去实践和体会。
简单的介绍下权限管理中的角色行为测试用例:
角色行为测试
数据集(YAML)
t_rbac_user: - f_id: 1 f_name: "zhaoyang2" f_create_at: "2013-10-10 17:04:05" t_rbac_role_user: - f_id: 1 f_user_id: 1 f_role_id: 1 t_rbac_role: - f_id: 1 f_name: "运营研发部-1" f_status: 1, f_type: "department" f_desc: "咱们是运营研发部-1" f_create_at: "2013-10-10 17:04:05" f_create_by: "zhaoyang2" f_update_by: "zhaoyang2"
测试代码:
//预设数据集 public function getDataSet() { return new \PHPUnit_Extensions_Database_DataSet_YamlDataSet( fixture('rbac/role.yml') ); } /** * 权限操做-异常验证 * @expectedException Exception * @expectedExceptionMessage 您无权运营当前数据 */ public function testRoleDeleteException() { Role::first()->remove(); } /** * 权限操做-删除验证 */ public function testRoleDelete() { Role::first()->remove("zhaoyang2"); //验证数据行 $this->assertEquals(1, $this->getConnection()->getRowCount(Role::$table_name)); $this->assertEquals(1, $this->getConnection()->getRowCount(UserRelation::$table_name)); }
鉴权用例和应用管理用例,远比这个复杂,感兴趣的同窗能够去 Fork 一把,了解下PHPUNIT的魅力。
PHPUNIT很强大,想合理运用的话,没有任何捷径,开始写测试用例吧。。
其实说架构算上下,就是和你们分享下权限中心的PHP之道。
高效便捷的使用PHP服务咱们的工做。
多交流,多分享,书写更好的PHP代码,享受编程和技术所带来的快乐。