ThinkPHP中RBAC權限帶菜單欄顯示和詳細權限操做

RBAC是什麼,能解決什麼難題?

RBAC是Role-Based Access Control的首字母,譯成中文即基於角色的權限訪問控制,說白了也就是用戶經過角色與權限進行關聯[其架構靈感來源於操做系統的GBAC(GROUP-Based Access Control)的權限管理控制]。簡單的來講,一個用戶能夠擁有若干角色,每個角色擁有若干權限。這樣,就構形成「用戶-角色-權限」的受權模型。在這種模型中,用戶與角色之間,角色與權限之間,通常者是多對多的關係。其對應關係以下:php

image

在許多的實際應用中,系統不僅是須要用戶完成簡單的註冊,還須要對不一樣級別的用戶對不一樣資源的訪問具備不一樣的操做權限。且在企業開發中,權限管理系統也成了重複開發效率最高的一個模塊之一。而在多套系統中,對應的權限管理只能知足自身系統的管理須要,不管是在數據庫設計、權限訪問和權限管理機制方式上均可能不一樣,這種不致性也就存在以下的憋端:html

  • 維護多套系統,重複造輪子,時間沒用在刀刃上
  • 用戶管理、組織機制等數據重複維護,數據的完整性、一致性很可貴到保障
  • 權限系統設計不一樣,概念理解不一樣,及相應技術差別,系統之間集成存在問題,單點登陸難度大,也複雜的企業系統帶來困難

RBAC是基於不斷實踐以後,提出的一個比較成熟的訪問控制方案。實踐代表,採用基於RBAC模型的權限管理系統具備如下優點:因爲角色、權限之間的變化比角色、用戶關係之間的變化相對要慢得多,減少了受權管理的複雜性,下降管理開銷;並且可以靈活地支持應用系統的安全策略,並對應用系統的變化有很大的伸縮性;在操做上,權限分配直觀、容易理解,便於使用;分級權限適合分層的用戶級形式;重用性強。node

ThinkPHP中RBAC實現體系

ThinkPHP中RBAC基於Java的Spring的Acegi安全系統做爲參考原型,並作了相應的簡化處理,以適應當前的ThinkPHP結構,提供一個多層、可定製的安全體系來爲應用開發提供安全控制。安全體系中主要有如下幾部分:ajax

  • 安全攔截器
  • 認證管理器
  • 決策訪問管理器
  • 運行身份管理器

安全攔截器

安全攔截器就比如一道道門,在系統的安全防禦系統中可能存在不少不一樣的安全控制環節,一旦某個環節你未經過安全體系認證,那麼安全攔截器就會實施攔截。正則表達式

認證管理器

 

防禦體系的第一道門就是認證管理器,認證管理器負責決定你是誰,通常它經過驗證你的主體(一般是一個用戶名)和你的憑證(一般是一個密碼),或者更多的資料來作到。更簡單的說,認證管理器驗證你的身份是否在安全防禦體系受權範圍以內。數據庫

訪問決策管理

 

雖然經過了認證管理器的身份驗證,可是並不表明你能夠在系統裏面肆意妄爲,由於你還須要經過訪問決策管理這道門。訪問決策管理器對用戶進行受權,經過考慮你的身份認證信息和與受保護資源關聯的安全屬性決定是是否能夠進入系統的某個模塊,和進行某項操做。例如,安全規則規定只有主管才容許訪問某個模塊,而你並無被授予主管權限,那麼安全攔截器會攔截你的訪問操做。 
決策訪問管理器不能單獨運行,必須首先依賴認證管理器進行身份確認,所以,在加載訪問決策過濾器的時候已經包含了認證管理器和決策訪問管理器。 
爲了知足應用的不一樣須要,ThinkPHP 在進行訪問決策管理的時候採用兩種模式:登陸模式和即時模式。登陸模式,系統在用戶登陸的時候讀取改用戶所具有的受權信息到 Session,下次再也不從新獲取受權信息。也就是說即便管理員對該用戶進行了權限修改,用戶也必須在下次登陸後才能生效。即時模式就是爲了解決上面的問題,在每次訪問系統的模塊或者操做時候,進行即便驗證該用戶是否具備該模塊和操做的受權,從更高程度上保障了系統的安全。json

運行身份管理器

 

運行身份管理器的用處在大多數應用系統中是有限的,例如某個操做和模塊須要多個身份的安全需求,運行身份管理器能夠用另外一個身份替換你目前的身份,從而容許你訪問應用系統內部更深處的受保護對象。這一層安全體系目前的 RBAC 中還沒有實現。api

ThinkPHP中RBAC認證流程

對應上面的安全體系,ThinkPHP 的 RBAC 認證的過程大體以下:數組

  1. 判斷當前模塊的當前操做是否須要認證
  2. 若是須要認證而且還沒有登陸,跳到認證網關,若是已經登陸 執行5
  3. 經過委託認證進行用戶身份認證
  4. 獲取用戶的決策訪問列表
  5. 判斷當前用戶是否具備訪問權限

權限管理的具體實現過程

RBAC相關的數據庫介紹

在ThinkPHP完整包,包含了RBAC處理類RBAC.class.php文件,位於Extend/Library/ORG/Util。打開該文件,其中就包含了使用RBAC必備的4張表,SQL語句以下(複製後請替換表前綴):安全

複製
CREATE TABLE IF NOT EXISTS `think_access` (
  `role_id` smallint(6) unsigned NOT NULL,
  `node_id` smallint(6) unsigned NOT NULL,
  `level` tinyint(1) NOT NULL,
  `module` varchar(50) DEFAULT NULL,
  KEY `groupId` (`role_id`),
  KEY `nodeId` (`node_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `think_node` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `title` varchar(50) DEFAULT NULL,
  `status` tinyint(1) DEFAULT '0',
  `remark` varchar(255) DEFAULT NULL,
  `sort` smallint(6) unsigned DEFAULT NULL,
  `pid` smallint(6) unsigned NOT NULL,
  `level` tinyint(1) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `level` (`level`),
  KEY `pid` (`pid`),
  KEY `status` (`status`),
  KEY `name` (`name`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `think_role` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `pid` smallint(6) DEFAULT NULL,
  `status` tinyint(1) unsigned DEFAULT NULL,
  `remark` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `pid` (`pid`),
  KEY `status` (`status`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 ;

CREATE TABLE IF NOT EXISTS `think_role_user` (
  `role_id` mediumint(9) unsigned DEFAULT NULL,
  `user_id` char(32) DEFAULT NULL,
  KEY `group_id` (`role_id`),
  KEY `user_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

 

下面對RBAC相關的數據庫表及字段做一下介紹:

表名 字段名 字段類型 做用
ly_user id INT 用戶ID(惟一識別號)
username VARCHAR(16) 用戶名
password VARCHAR(32) 密碼
email VARCHAR(100) 用戶郵箱
create_time TIMESTAMP 建立時間(時間戳)
logintime TIMESTAMP 最近一次登陸時間(時間戳)
loginip VARCHAR(15) 最近登陸的IP地址
status TINYINT(1) 啓用狀態:0:表示禁用;1:表示啓用
remark VARCHAR(255) 備註信息
ly_role id INT 角色ID
name VARCHAR(20) 角色名稱
pid SMALLINT(6) 父角色對應ID
status TINYINT(1) 啓用狀態(同上)
remark VARCHAR(255) 備註信息
ly_node id SMALLINT(6) 節點ID
name VARCHAR(20) 節點名稱(英文名,對應應用控制器、應用、方法名)
title VARCHAR(50) 節點中文名(方便看懂)
status TINYINT(1) 啓用狀態(同上)
remark VARCHAR(255) 備註信息
sort SMALLINT(6) 排序值(默認值爲50)
pid SMALLINT(6) 父節點ID(如:方法pid對應相應的控制器)
level TINYINT(1) 節點類型:1:表示應用(模塊);2:表示控制器;3:表示方法
ly_role_user user_id INT 用戶ID
role_id SMALLINT(6) 角色ID
ly_access role_id SMALLINT(6) 角色ID
node_id SMALLINT(6) 節點ID
level    
module    

如下是數據庫表各字段的關聯關係:

image

實現RBAC管理的前導性工做

基於ThinkPHP實現RBAC的權限管理系統中,首先要作一些前導性的工做(系統數據庫設計TP已經爲咱們完成了),主要分如下幾個方面:

  • 用戶(增、刪、改、查)
  • 角色(增、刪、改、查)
  • 節點(增、刪、改、查)
  • 配置權限(更新權限)

具體實現的代碼以下(相關解釋均在註釋之中):

<?php
/**
 *
 */
namespace Home\Controller;

use Home\Controller\BaseController;
use Home\Model\AdminUserModel;
use Org\Util\Tree;
use Think\Page;

class RbacController extends BaseController
{
    //初始化操做
    public function _initialize()
    {
        if (!IS_AJAX) $this->error('你訪問的頁面不存在,請稍後再試');
    }

    public function userIndex()
    {
        if (IS_POST) {
            $condition['username'] = array('like', "%" . trim(I('keybord')) . "%");
            $model = D('AdminUser');
            $count = $model->where($condition)->count();
            $Page = new Page($count, 3);
            $Page->setConfig('theme', '%FIRST% %UP_PAGE% %LINK_PAGE% %DOWN_PAGE% %END%');
            $show = $Page->show();
            //select search
            $list = $model->where($condition)->field('password', true)->relation(true)->limit($Page->firstRow . ',' . $Page->listRows)->select();
            $this->show = $show;
            $this->list = $list;
            $this->display('AdminUser/index');
        } else {
            $model = D('AdminUser');
            $count = $model->count();
            $Page = new Page($count, 6);
            $Page->setConfig('header', '共%TOTAL_ROW%條');
            $Page->setConfig('first', '首頁');
            $Page->setConfig('last', '共%TOTAL_PAGE%頁');
            $Page->setConfig('prev', '上一頁');
            $Page->setConfig('next', '下一頁');
            $Page->setConfig('link', 'indexpagenumb');
            $Page->setConfig('theme', '%FIRST% %UP_PAGE% %LINK_PAGE% %DOWN_PAGE% %END%');
            $show = $Page->show();
            //select search
            $list = $model->field('password', true)->relation(true)->limit($Page->firstRow . ',' . $Page->listRows)->select();
            $this->show = $show;
            $this->list = $list;
            $this->display('Rbac/userIndex');
        }

    }

    /*
    *  平臺用戶異步驗證
    */
    public function checkUser()
    {
        $username = trim(I('username'));
        $conditions = array('username' => ':username');
        $result = M('AdminUser')->where($conditions)->bind(':username', $username)->find();
        //若是不存在,則能夠建立,也就是返回的是true
        if (!$result) {
            echo 'true';
        } else {
            echo 'false';
        }
        exit();
    }

    /*
     *  建立平臺用戶,這裏開啓了服務器驗證
     */
    public function createAdminUser()
    {
        if (IS_POST) {
            /**
             * [實例化User對象]
             * D方法實例化模型類的時候一般是實例化某個具體的模型類,若是你僅僅是對數據表進行基本的CURD操做的話,
             * 使用M方法實例化的話,因爲不須要加載具體的模型類,因此性能會更高。
             */
            $model = D('AdminUser');
            /**
             * 若是建立失敗 表示驗證沒有經過 輸出錯誤提示信息
             * $model->create() 會自動調用驗證規則
             */
            if (!$model->create()) return $this->error($model->getError());
            //$username = $model->username;
            //$model->add() 插入數據後會自動的摧毀數據
            if (!$uid = $model->add()) return $this->success('註冊失敗', U('Rbac/userIndex'));//獲取用戶id
            // 若是是註冊用戶的話
//            session('uid', $uid);
//            session('username', $username);
            //用戶添加成功後,給用戶角色表添加數據
            $role['role_id'] = I('role_id');
            $role['user_id'] = $uid;
            //添加該管理員操做到操做日誌中
            $desc = '給ID爲:[' . $_POST['role_id'] . ']的角色,新增用戶:[' . $_POST['username'] . '],密碼爲:[' . $_POST['password'] . ']' . '其餘參數' . $_POST;
            addOperationLog($desc);
            if (D('AdminRoleUser')->add($role)) {
                return $this->success('添加平臺用戶成功', U('Rbac/userIndex'));
            } else {
                return $this->error('添加平臺用戶失敗', U('Rbac/userIndex'));
            }
            return $this->success('添加平臺用戶成功', U('Rbac/userIndex'));
        }
        $this->role_list = M('AdminRole')->select();
        $this->display('Rbac/createAdminUser');
    }

    /**
     * 改變用戶角色
     * 能夠支持不執行SQL而只是返回SQL語句:$User->fetchSql(true)->add($data);
     */
    public function updateUser()
    {
        $userId = I('get.id');
        if (IS_POST) {
            $data['user_id'] = I('post.user_id');
            $data['role_id'] = I('post.role_id');
            $model = M('AdminRoleUser');
            if ($model->where(array('user_id' => $data['user_id']))->delete() == false) {
                return $this->error('用戶角色修改失敗', U('Rbac/updateUser', array('id' => $userId)));
            }
            if ($model->add($data) == false) {
                return $this->error('用戶角色修改失敗', U('Rbac/updateUser', array('id' => $userId)));
            }
            return $this->success('用戶角色修改爲功', U('Rbac/userIndex'));
        }
        $this->role_list = M('AdminRole')->select();
        $this->user = M('AdminUser')->join('tour_admin_role_user ON tour_admin_role_user.user_id = tour_admin_user.id')->where(array('id' => $userId))->field('user_id,username,role_id')->find();
        $this->display();
    }

    //刪除用戶
    public function delUser()
    {
        $user_id = I('post.id', '', 'int');
        $user = D('AdminUser');
        $result = $user->relation(true)->where(array('id' => $user_id))->delete();
        if ($result) {
            //添加該管理員操做到操做日誌中
            $desc = '刪除用戶ID:' . $user_id . '成功';
            addOperationLog($desc);
            $response = ['status' => 200, 'errmsg' => '刪除成功', 'dataList' => $result];
            return $this->ajaxReturn($response, 'JSON');
        }
        //添加該管理員操做到操做日誌中
        $desc = '刪除用戶ID:' . $user_id . '失敗';
        addOperationLog($desc);
        $response = ['status' => 500, 'errmsg' => '刪除失敗', 'dataList' => $result];
        return $this->ajaxReturn($response, 'JSON');

    }

    //設置用戶狀態
    public function userStatus()
    {
        $uid = I('post.id');
        $db = M('AdminUser');
        $status = $db->where(array('id' => $uid))->getField('status');
        $status = ($status == 1) ? 0 : 1;
        if ($db->where(array('id' => $uid))->setField('status', $status)) {
            $response = ['status' => 200, 'errmsg' => '改變成功', 'dataList' => $status];
            return $this->ajaxReturn($response, 'JSON');
        }
        //添加該管理員操做到操做日誌中
        $desc = '設置用戶狀態:' . $uid . '失敗';
        addOperationLog($desc);
        $response = ['status' => 500, 'errmsg' => '改變失敗', 'dataList' => $status];
        return $this->ajaxReturn($response, 'JSON');
    }

    /***********************************節點開始****************************************************/
    public function nodeIndex()
    {
        $db = M('AdminNode');
        $node = $db->order('id')->select();
        $this->nodelist = Tree::create($node);
        $this->display('Rbac/nodeIndex');
    }

    //建立權限表單處理
    public function createNode()
    {
        $db = M('AdminNode');

        //建立權限表單處理
        if (IS_POST) {
            $db->create();
            if (!$db->add()) {
                return $this->error("權限添加失敗", U('Rbac/nodeIndex'));
            }
            return $this->success('權限添加成功', U('Rbac/nodeIndex'));
        }
        $node = $db->where('level !=3')->order('sort')->select();
        $this->nodelist = Tree::create($node);
        $this->display();
    }

    /*
    *   刪除權限
    */
    public function delNode()
    {
        $result = M('AdminNode')->where(array('id' => I('post.id', '', 'int')))->delete();
        if ($result) {
            $response = ['status' => 200, 'errmsg' => '刪除成功', 'dataList' => $result];
            return $this->ajaxReturn($response, 'JSON');
        }
        $response = ['status' => 500, 'errmsg' => '刪除失敗', 'dataList' => $result];
        return $this->ajaxReturn($response, 'JSON');
    }

    /*
    *   設置權限狀態
    */
    public function NodeStatus()
    {
        $id = I('post.id');
        $db = M('AdminNode');
        $status = $db->where(array('id' => $id))->getField('status');

        $status = ($status == 1) ? 0 : 1;
        if ($db->where(array('id' => $id))->setField('status', $status)) {
            $response = ['status' => 200, 'errmsg' => '修改爲功', 'dataList' => $status];
            return $this->ajaxReturn($response, 'JSON');
        }
        $response = ['status' => 500, 'errmsg' => '修改失敗', 'dataList' => $status];
        return $this->ajaxReturn($response, 'JSON');
    }

    /*
   *   該節點是否在菜單欄顯示
   */
    public function showMenus()
    {
        $id = I('post.id');
        $db = M('AdminNode');
        $show = $db->where(array('id' => $id))->getField('menus');
        $menus = ($show == 1) ? 0 : 1;
        $result = $db->where(array('id' => $id))->setField('menus', $menus);
        if ($result) {
            $response = ['status' => 200, 'errmsg' => '修改爲功', 'dataList' => $result];
            return $this->ajaxReturn($response, 'JSON');
        }
        $response = ['status' => 500, 'errmsg' => '修改失敗', 'dataList' => $result];
        return $this->ajaxReturn($response, 'JSON');
    }

    /***********************************角色開始****************************************************/
    public function roleIndex()
    {
        $db = M('AdminRole');
        $this->rolelist = $db->select();
        $this->display();
    }

    /*
     *建立角色
     */
    public function createAdminRole()
    {
        if (IS_POST) {
            $name = I('post.name', '', 'strip_tags');
            $remark = I('post.remark', '', 'strip_tags');
            $pid = I('post.pid', '', 'strip_tags');  // 用strip_tags過濾$_GET['title']
            if (empty($name)) return $this->error('角色名稱不能爲空');
            $role = M('AdminRole');
            $where['name'] = ':name';
            $roleName = $role->where($where)->bind(':name', $name, \PDO::PARAM_STR)->getField('name');
            if ($roleName) return $this->error("角色名稱:'" . $name . "'已經存在", U('Rbac/roleIndex'));

            $role->name = $name;
            $role->remark = $remark;
            $role->pid = $pid;
            //create方法並不算是連貫操做,由於其返回值多是布爾值,因此必需要進行嚴格判斷。
            if ($role->create()) {
                // 若是主鍵是自動增加型 成功後返回值就是最新插入的值
                $result = $role->field('name,remark,pid')->add();
                //若是在add方法以前調用field方法,則表示只容許寫入指定的字段數據,其餘非法字段將會被過濾
                if (!$result) return $this->error("角色添加失敗", U('Rbac/createpartent'));
                return $this->success('角色添加成功', U('Rbac/roleIndex'));
            }
            return $this->success('角色添加成功', U('Rbac/roleIndex'));
        }
        $this->display();
    }

    /*
     *添加權限Node位權限表,Access爲權限-角色關聯表
     */
    public function addNode()
    {
        $rid = I('rid', '', 'int');
        if (!is_numeric($rid)) return $this->success('參數類型錯誤,必須是數字', U('Rbac/roleIndex'));
        //getFieldById針對某個字段(ID)查詢並返回某個字段(name)的值
        $roleModel = M('AdminRole');
        $roleWhere['id'] = ':id';
        $role_name = $roleModel->where($roleWhere)->bind(':id', $rid, \PDO::PARAM_INT)->getField('name');
        if ($role_name == false) return $this->success('沒有找到該角色', U('Rbac/roleIndex'));

        //根據角色遍歷全部權限
        $access = M('AdminAccess');
        if (IS_POST) {
            $actions = I('post.actions');
            try {
                $access->startTrans();
                $where['role_id'] = ':role_id';
                $mod1 = $access->where($where)->bind(':role_id', $rid)->delete();
                if (!$mod1) $access->rollback();
                $data = array();
                foreach ($actions as $value) {
                    $tmp = explode('_', $value);
                    $data[] = array(
                        'role_id' => $rid,
                        'node_id' => $tmp[0],
                        'level' => $tmp[1]
                    );
                }
                if (!($access->addAll($data))) {
                    $access->rollback();
                } else {
                    $access->commit();
                }
                return $this->success('權限設置成功', U('Rbac/addNode', array('rid' => $rid)));
            } catch (\Exception $e) {
                $access->rollback();
                return $this->success('權限設置異常', U('Rbac/addNode', array('rid' => $rid)));
            }

        }
        $node = M('AdminNode')->order('id')->select();
        $node_list = Tree::create($node);
        $node_arr = array();
        foreach ($node_list as $value) {
            $conditions['node_id'] = $value['id'];
            $conditions['role_id'] = $rid;
            $count = $access->where($conditions)->count();
            if ($count) {
                $value['access'] = '1';
            } else {
                $value['access'] = '0';
            }
            $node_arr[] = $value;
        }
        $this->role_name = $role_name;
        $this->node_list = $node_arr;
        $this->rid = $rid;
        $this->display();
    }

    /*
     *刪除角色以及角色所擁有的權限
     */
    public function delRole()
    {
        $role_id = I('post.role_id', '', 'int');
        $user = D('AdminRole');
        $result = $user->relation(true)->where(array('id' => $role_id))->delete();
        if ($result) {
            $response = ['status' => 200, 'errmsg' => '修改爲功', 'dataList' => $result];
            return $this->ajaxReturn($response, 'JSON');
        }
        $response = ['status' => 500, 'errmsg' => '修改失敗', 'dataList' => $result];
        return $this->ajaxReturn($response, 'JSON');
    }

    /*
    *   設置角色狀態
    */
    public function roleStatus()
    {
        $rid = I('post.rid');
        $db = M('AdminRole');
        $status = $db->where(array('id' => $rid))->getField('status');
        $status = ($status == 1) ? 0 : 1;
        if ($db->where(array('id' => $rid))->setField('status', $status)) {
            $response = ['status' => 200, 'errmsg' => '修改爲功', 'dataList' => $status];
            return $this->ajaxReturn($response, 'JSON');
        }
        $response = ['status' => 500, 'errmsg' => '修改失敗', 'dataList' => $status];
        return $this->ajaxReturn($response, 'JSON');
    }
}

 

ThinkPHP中RBAC類的詳解

在ThinkPHP處理權限管理中,真正的難點並非在RBAC類的使用上,上面相關的處理(權限配置,節點管理等);因此當完成以上工做,其實RBAC系統已經完成了90%。下面先從ThinkPHP中RBAC的配置提及(詳細請參看對應的註釋內容):

複製
<?php

 
return array(
 
"USER_AUTH_ON" => true, //是否開啓權限驗證(必配)
"USER_AUTH_TYPE" => 1, //驗證方式(一、登陸驗證;二、實時驗證)
 
"USER_AUTH_KEY" => 'uid', //用戶認證識別號(必配)
"ADMIN_AUTH_KEY" => 'superadmin', //超級管理員識別號(必配)
"USER_AUTH_MODEL" => 'user', //驗證用戶表模型 ly_user
'USER_AUTH_GATEWAY' => '/Public/login', //用戶認證失敗,跳轉URL
 
'AUTH_PWD_ENCODER'=>'md5', //默認密碼加密方式
 
"RBAC_SUPERADMIN" => 'admin', //超級管理員名稱
 
 
"NOT_AUTH_MODULE" => 'Index,Public', //無需認證的控制器
"NOT_AUTH_ACTION" => 'index', //無需認證的方法
 
'REQUIRE_AUTH_MODULE' => '', //默認須要認證的模塊
'REQUIRE_AUTH_ACTION' => '', //默認須要認證的動做
 
'GUEST_AUTH_ON' => false, //是否開啓遊客受權訪問
'GUEST_AUTH_ID' => 0, //遊客標記
 
"RBAC_ROLE_TABLE" => 'ly_role', //角色表名稱(必配)
"RBAC_USER_TABLE" => 'ly_role_user', //用戶角色中間表名稱(必配)
"RBAC_ACCESS_TABLE" => 'ly_access', //權限表名稱(必配)
"RBAC_NODE_TABLE" => 'ly_node', //節點表名稱(必配)
);

注意:

  • 以上有的配置項並不是必須的,但標有「必配」,則必須配置
  • 無需認證的權限和方法與須要認證的模塊和動做能夠根據須要選擇性配置,還有部分權限相關配置並未列表出

RBAC處理類提供靜態的方法

ThinkPHP的RBAC處理類提供給咱們不少相關的靜態方法以下:

方法名 接收參數說明 返回值 說明
RBAC::authenticate($map,$model='');
  • $map:ThinkPHP數據庫處理類的where條件參數
  • $model:用戶表名,默認取配置文件C('USER_AUTH_MODEL')
array

RBAC::authenticate(array("username"=>"admin","userpwd" => I('POST.pwd','', 'md5')));

返回值是在用戶表中,以$map爲條件where的查閱結果集

0RBAC::saveAccessList($authId=null);
  • $authId:用戶識別號,默認取C('USER_AUTH_KEY');
返回一個空值 若是驗證方式爲登陸驗證,則將權限寫入session中,不然不做任何處理
RBAC::getRecordAccessList($authId=null,$module='');
  • $authId:用戶識別號(可不傳)
  • $module:當前操做的模塊名稱
Array 返回一個包含權限的ID的數組
RBAC::checkAccess() 返回true或false 檢查當前操做是否須要認證(根據配置中須要認證和不須要評論的模塊或方法得出)
RBAC::checkLogin() true 若是當前操做須要認證且用戶沒有登陸,繼續檢測是否開啓遊客受權。若是開啓遊客受權,則寫入遊客權限;不然跳到登陸頁
RBAC::AccessDecision($appName=APP_NAME)
  • $appName:選傳,有默認值
  • true:表示有操做權限
  • false:無操做權限
AccessDecision($appName=APP_NAME)方法,檢測當前項目模塊操做,是否存在於$_SESSION['_ACCESS_LIST']數組中$_SESSION['_ACCESS_LIST']['當前操做']['當前模塊']['當前操做']是否存在。若是存在表示有權限,返回true;不然返回flase。
RBAC::getAccessList($authId)
  • $authId:用戶識別號(選傳,程序自動獲取)
Array 經過數據庫查詢取得當前認證號的全部權限列表
RBAC::getModuleAccessList($authId,$module)
  • $authId:用戶識別號(必)
  • $module:對應的模塊(必)
Array 返回指定用戶可訪問的節點權限數組

注意:在使用RBAC::AccessDecision()方法時,若是你開啓了項目分組,則必須傳入當前分組,代碼以下:

複製
  1. //權限驗證
  2. if(C('USER_AUTH_ON') && !$notAuth) {
  3. import('ORG.Util.RBAC');
  4. //使用了項目分組,則必須引入GROUP_NAME
  5. RBAC::AccessDecision(GROUP_NAME) || $this->error("你沒有對應的權限");
  6. }

RBAC處理類的實際應用

在完成用戶登陸,角色建立,節點增刪改查的工做後,就只剩下了RBAC如何在對應程序代碼中應用了。挻簡單的,只用在原來的代碼其餘上改動幾個地方便可。

  • 用戶登陸時,寫入用戶權限
  • 用戶操做時,進行權限驗證

下面是用戶登陸時的實現代碼:

複製
<?php
namespace Home\Controller;

use Think\Controller;
use Org\Util\Rbac;

class LoginController extends Controller
{
    public function index()
    {
        $this->display();
    }

    /*
     * 異步驗證帳號
    */
    public function checkUser()
    {
        $username = I('username');
        $conditions = array('username' => ':username');
        $result = M('User')->where($conditions)->bind(':username', $username)->find();
        //若是不存在,則能夠建立,也就是返回的是true
        if (!$result) {
            echo 'false';
        } else {
            echo 'true';
        }
        exit();
    }

    /*
     * 異步驗證密碼
    */
    public function checkPwd()
    {
        $username = I('post.username');
        $password = I('post.password');
        $conditions = array('username' => ':username');
        $result = M('User')->where($conditions)->bind(':username', $username)->find();
        if (md5($password) != $result['password']) {
            echo 'false';
        } else {
            echo 'true';
        }
        exit();
    }

    /*
     * 檢查登陸
    */
    public function checkLogin()
    {
        if (!IS_POST) $this->error('非法訪問');

        // 採用htmlspecialchars方法對$_GET['name'] 進行過濾,若是不存在則返回空字符串
        $username = I('post.username', '', 'htmlspecialchars');
        // 採用正則表達式進行變量過濾,若是正則匹配不經過的話,則返回默認值。
        //I('get.name','','/^[A-Za-z]+$/');
        $password = md5(I('post.password'));

        $user = D('AdminUser');
        $where = array('username' => $username);
        $fields = array('id', 'password', 'username', 'status', 'expire', 'logintime'); // 之查找須要的字段
        $result = $user->where($where)->field($fields)->find();

        if (!$result || $password != $result['password']) return $this->error('帳號或密碼錯誤',U('Home/Login/index'));

        if ($result['status'] == 0) return $this->error('該用戶被鎖定,暫時不可登陸',U('Home/Login/index'));

        // 是否記住個人登陸,設置一個Cookie,寫在客戶端
        if (isset($_POST['remember'])) {
            $value = $result['id'] . '|' . get_client_ip() . '|' . $result['username'];
            $value = encrytion($value, 1);
            @setcookie('remember', $value, C('AUTO_LOGIN_LIFETIME'), '/');
        }

        // 天天登陸增長經驗值
        $today = strtotime(date('Y-m-d')); // 獲取今天0時0分0秒的時間
        // 若是上次的登陸時間小於今天的時間,則增長經驗值
        $where2 = array('id' => $result['id']);
        if ($result['logintime'] < $today) {
            $user->where($where2)->setInc('expire', 10);
        }

        //更新登陸戶登陸信息
        $data_arr = array(
            'id' => $result['id'],
            'logintime' => time(),
            'loginip' => get_client_ip(),
        );
        if ($user->save($data_arr)) {
            // 獲取$_SESSION['user_id'] 若是不存在則默認爲0
            session('uid', 0);
            session('username', $result['username']);
            session('loginAccount', $result['username']);
            session('loginUserName', $result['username']);
            session('uid', $result['id']);
            //RBAC 開始,用戶認證SESSION標記 ,默認爲"authId"
            session(C('USER_AUTH_KEY'), $result['id']);

            //若是爲超級管理員,則無需驗證
            if ($_SESSION['username'] == C('RBAC_SUPERADMIN')) session(C('ADMIN_AUTH_KEY'), true);

            //用於檢測用戶權限的方法,並保存到Session中,讀取用戶權限
            Rbac::saveAccessList($result['id']);
            //添加操做日誌中
            $desc = '登錄成功';
            addOperationLog($desc);
            return $this->redirect('Index/index');
        } else {
            return $this->error('2222222222222');
        }

    }

    public function memberInfo()
    {
        $user_id = session('user_id');
        $user = D('User');
        $where = array('user_id' => $user_id);
        $result = $user->where($where)->select();
        $this->result = $result;
        $this->display();
    }

    /**
     * 獲取apikey_values
     */

    public function apikey()
    {
        $secret = "6JNVkTk4jHsgF0e1oOVLwOZDeq83pDXa";
        $user_id = I('user_id', '', int);
        // $where = array('user_id = %d ',array($user_id));
        $user = M('User');
        // $result = $user->where($where)->find();
        $result = $user->where("user_id = %d", array($user_id))->find();

        $hash = sha1($result['user_id'] . $result['password'] . $secret);
        $data = array(
            'apikey_value' => $hash,
            'apikey_time' => time()
        );
        $where = array('user_id' => $user_id);
        $user->where($where)->save($data);
        $this->ajaxReturn($hash);
    }

    public function hash()
    {
        if (!IS_POST) {
            $out_data = array(
                'err_msg' => 'request method is error.',
                'is_success' => 'Fail'
            );
            exit(json_encode($out_data));
        };

        $username = I('username');
        $password = I('password');
        $where = array('username' => $username);

        $user = M('User');
        $result = $user->where($where)->find();
        if (!$result || $password != $result['password']) {
            $out_data = array(
                'err_msg' => 'Username or password is incorrect.',
                'is_success' => 'Fail'
            );
            exit(json_encode($out_data));
        }

        /**
         * HASH生成規則
         */
        $secret = "6JNVkTk4jHsgF0e1oOVLwOZDeq83pDXa";
        $hash = sha1($result['user_id'] . $result['password'] . $secret);

        $where = array('user_id' => $result['user_id']);
        $data["apikey_value"] = $hash;
        $data["apikey_time"] = time();
        $user->where($where)->save($data);

        $out_data = array(
            'is_success' => 'Success',
            'hash' => $hash
        );
        exit(json_encode($out_data));
    }

    public function relationTest()
    {
        echo get_client_ip();
    }

    public function error1(){
        $this->display();
    }

    public function logout()
    {
        session('username', NULL);
        session_unset();
        session_destroy();
        return $this->redirect('Login/index');
    }
}

 

接着在控制器目錄建立一個CommonAction.class.php文件,而後改寫全部要權限驗證的類,讓其繼承自CommonAction。代碼以下:

複製
<?php
namespace Home\Controller;

use Think\Controller;
use Org\Util\Rbac;
class BaseController extends Controller
{
//    /**
//     * ThikPHP自動運行方法,每一次,都會自動運行這個方法
//     * 要判斷一個session值是否已經設置,可使用
//       session('?name'); 至關於:isset($_SESSION['name']);
//     */
    public function _initialize(){
        /***************************************網站開關****************************************************/
        if(!C('WEB_STATE')) exit('網站維護中');

        /***********************************沒有登陸時候時須要執行的代碼**************************************/
        if(!isset($_SESSION[C('USER_AUTH_KEY')])) return $this->redirect('Login/index');

        /***************************************自動登陸配置************************************************/
        if(isset($_COOKIE['remember']) && !isset($_SESSION['uid'])){
            // Cookie 解密
            $value = encrytion($_COOKIE['remember']);
            $result = explode('|',$value);
            // 判斷IP地址是否同樣
            if($result[1] == get_client_ip()){
                session('uid',$result[0]);
                session('uid',$result[2]);
            }
        }

        /***************************************權限認證****************************************************/
        $Public = in_array(MODULE_NAME,explode(',',C('NOT_AUTH_MODULE'))) || in_array(ACTION_NAME,explode(',',C('NOT_AUTH_ACTION')));
        // 若是不在公共模塊之中,同時開啓權限驗證的話,則開始認證過程
        if(C('USER_AUTH_ON') && !$Public)
        {
            if(!Rbac::AccessDecision()) //經過accessDecision獲取權限信息,true:表示有操做權限,false:無操做權限
            {
                return $this->error("你沒有對應的權限");  //沒有獲取到權限信息時須要執行的代碼
            }
        }
        /***************************************導航欄菜單顯示****************************************************/
        /*
        *   思路:
        *   1.取出全部權限節點。
        *   2.取出當前登陸用戶擁有的模塊權限(取英文名稱)和操做權限(取ID)
        *   3.對全部權限進行遍歷,先匹配模塊權限,不存在刪除,存在則匹配操做權限
        */
        // 超級管理員登陸
        if(session(C('ADMIN_AUTH_KEY')))
        {
            $menus = D('AdminNode')->where('level = 2')->relation(true)->order('sort desc')->select();//取出全部節點
        }else{
            /**
             * [1]取出全部的權限,是經過關聯模型從數據庫中獲取的
             */
            $menus = D('AdminNode')->where('level = 2')->relation(true)->order('sort desc')->select();
            $module = '';    //存放擁有的模塊
            $node_id = '';   //存放擁有的模塊
            /**
             * [2]獲取當前用戶的全部權限,這個是從RBAC中獲取的
             */
            $access_list = $_SESSION['_ACCESS_LIST'];   //當前用戶所擁有的權限
            foreach ($access_list as $key => $value) {
                foreach ($value as $key1 => $value1) {
                    $module = $module.','.$key1;    //字符串拼接模塊名稱
                    foreach ($value1 as $key2 => $value2) {
                        $node_id = $node_id.','.$value2;    //字符串拼操做id
                    }
                }
            }
            /**
             * [3]去除沒有權限的節點,經過全部權限和用戶已經擁有的權限比較
             */
            foreach ($menus as $key => $value) {
                $all_node[] = $value['name'];
                if(!in_array(strtoupper($value['name']), explode(',', $module))){
                    unset($menus[$key]);    //刪除模塊
                }else{
                    //模塊存在,比較裏面的操做
                    foreach ($value['node'] as $key1 => $value1) {
                        if(!in_array($value1['id'], explode(',', $node_id))){
                            unset($menus[$key]['node'][$key1]);  // 刪除操做
                        }
                    }
                }
            }
        }
        $this->menus = $menus;
    }

}

ThinkPHP中提供了一個_initialize()方法,是在類初始化就會執行的,也就是隻要後面控制器繼承自CommonAction類,就會在做對應操做時,執行_initialize()方法。

相關文章
相關標籤/搜索