權限管理是無線運營系統中的核心模塊,經過訪問控制策略的配置,來約定人與資源的訪問關係。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代碼,享受編程和技術所帶來的快樂。