Home >Backend Development >PHP Tutorial >RBAC permissions in ThinkPHP with menu bar display and detailed permission operations

RBAC permissions in ThinkPHP with menu bar display and detailed permission operations

藏色散人
藏色散人forward
2019-04-16 10:13:565023browse

What is RBAC and what problems can it solve?

RBAC is the acronym for Role-Based Access Control. Translated into Chinese, it means role-based permission access control. To put it bluntly, users are associated with permissions through roles [source of inspiration for its architecture Permission management control based on GBAC (GROUP-Based Access Control) of the operating system]. Simply put, a user can have several roles, and each role has several permissions. In this way, a "user-role-permission" authorization model is constructed. In this model, there is generally a many-to-many relationship between users and roles, and between roles and permissions. The corresponding relationship is as follows:

RBAC permissions in ThinkPHP with menu bar display and detailed permission operations

In many practical applications, the system not only requires users to complete simple registration, but also needs to have access to different resources for different levels of users. Different operating permissions. And in enterprise development, the rights management system has become one of the most efficient modules for repeated development. In multiple systems, the corresponding permission management can only meet the management needs of the own system, and may be different in terms of database design, permission access and permission management mechanisms. This inconsistency also has the following consequences:

• Maintaining multiple systems, reinventing the wheel repeatedly, time is wasted on the cutting edge
• Repeated maintenance of data such as user management and organizational mechanisms makes it difficult to guarantee data integrity and consistency
• Due to different permission system designs, different conceptual understandings, and corresponding technical differences, there are problems with integration between systems, single sign-on is difficult, and complex enterprise systems also bring difficulties

RBAC was proposed based on continuous practice A relatively mature access control scheme. Practice has shown that using a permission management system based on the RBAC model has the following advantages: Since changes between roles and permissions are relatively much slower than changes between roles and user relationships, the complexity of authorization management is reduced and management overhead is reduced. ; And it can flexibly support the security policy of the application system and has great scalability to changes in the application system; In terms of operation, permission distribution is intuitive, easy to understand, and easy to use; hierarchical permissions are suitable for hierarchical user-level forms; reuse Strong sex.

RBAC implementation system in ThinkPHP

RBAC in ThinkPHP uses Spring's Acegi security system based on Java as a reference prototype, and has made corresponding simplifications to adapt to the current ThinkPHP structure provides a multi-layered, customizable security system to provide security control for application development. The security system mainly has the following parts:

• Security interceptor

• Authentication manager

• Decision-making access manager

• Running identity management Device

Security interceptor

Security interceptor is like a door. There may be many different security control links in the system's security protection system. Once a certain link If you have not passed the security system certification, the security interceptor will intercept it.

Authentication Manager

The first door of the protection system is the authentication manager. The authentication manager is responsible for determining who you are. Generally, it verifies your subject ( Usually a username) and your credentials (usually a password), or more information to do this. To put it more simply, the authentication manager verifies that your identity is within the authorization scope of the security protection system.

Access decision management

Although you have passed the authentication of the authentication manager, it does not mean that you can do whatever you want in the system, because you still need to pass the access decision Manage this door. The access decision manager authorizes users and determines whether you can enter a certain module of the system and perform a certain operation by considering your identity authentication information and the security attributes associated with the protected resources. For example, if the security rules stipulate that only supervisors are allowed to access a certain module, and you are not granted supervisor permissions, the security interceptor will intercept your access operation.
The decision-making access manager cannot run alone and must first rely on the authentication manager for identity confirmation. Therefore, the authentication manager and decision-making access manager are already included when loading the access decision filter.

In order to meet the different needs of applications, ThinkPHP adopts two modes when conducting access decision management: login mode and immediate mode. In login mode, the system reads and changes the user's authorization information to the Session when the user logs in, and does not re-obtain the authorization information next time. In other words, even if the administrator modifies the user's permissions, the changes will only take effect after the user logs in next time. The immediate mode is to solve the above problem. Every time a module or operation of the system is accessed, it is immediately verified whether the user has the authorization for the module and operation, thus ensuring the security of the system to a higher degree.

Run Identity Manager

The usefulness of the running identity manager is limited in most application systems. For example, a certain operation and module requires the security requirements of multiple identities. The running identity manager can replace your current identity with another identity, thereby allowing You access protected objects deeper within the application system. This layer of security system is not currently implemented in RBAC.

RBAC authentication process in ThinkPHP

Corresponding to the above security system, the RBAC authentication process of ThinkPHP is roughly as follows:

1. Determine the current module Whether the current operation requires authentication

2. If authentication is required and you have not logged in yet, jump to the authentication gateway. If you have logged in, perform 5

3. User identity authentication through delegated authentication

4. Obtain the user's decision-making access list

5. Determine whether the current user has access permission

The specific implementation process of permission management

RBAC related database introduction

The ThinkPHP complete package includes the RBAC processing class RBAC.class.php file, located in Extend/Library/ORG/Util. Open the file, which contains the four tables necessary for using RBAC. The SQL statements are as follows (please replace the table prefix after copying):

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;

The following is an introduction to the database tables and fields related to RBAC:

##ly_useridINTUser ID (unique identification number) usernameVARCHAR(16)UsernamepasswordVARCHAR(32)PasswordemailVARCHAR(100)user emailcreate_timeTIMESTAMPCreation time (timestamp)logintimeTIMESTAMPLatest login time (timestamp)loginipVARCHAR(15)Last logged in IP addressstatusTINYINT(1)Enabled status: 0: means disabled; 1: means enabled remarkVARCHAR(255)Remark informationly_roleidINTRole IDnameVARCHAR(20)Role namepidSMALLINT(6)Parent role corresponding IDstatusTINYINT(1)Enabled status (same as above) remarkVARCHAR (255)Remark informationly_nodeidSMALLINT(6)Node IDnameVARCHAR(20)Node name (English name, corresponding to application controller, application, method name)titleVARCHAR(50)Chinese name of the node (easy to understand)status TINYINT(1)Enabled status (same as above)remarkVARCHAR(255)Remark informationsortSMALLINT(6)Sort value (default is 50)pidSMALLINT(6)Parent node ID (such as: method pid corresponds to the corresponding controller)##ly_accessrole_idSMALLINT(6)role IDnode_idSMALLINT(6)Node ID##levelmodule

以下是数据库表各字段的关联关系:

RBAC permissions in ThinkPHP with menu bar display and detailed permission operations

实现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, //验证方式(1、登录验证;2、实时验证)
 
"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处理类提供给我们很多相关的静态方法如下:

Table name Field name Field type Function
level TINYINT(1) Node type: 1: Represents application (module); 2: Represents controller; 3: Represents method
ly_role_user user_id INT User ID
role_id SMALLINT( 6) role ID
方法名 接收参数说明 返回值 说明
RBAC::authenticate(map,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,authId=null,module='');

$authId:用户识别号(可不传)

$module:当前操作的模块名称

Array 返回一个包含权限的ID的数组
RBAC::checkAccess() 返回true或false 检查当前操作是否需要认证(根据配置中需要认证和不需要评论的模块或方法得出)
RBAC::checkLogin() true 如果当前操作需要认证且用户没有登录,继续检测是否开启游客授权。如果开启游客授权,则写入游客权限;否则跳到登录页
RBAC::AccessDecision($appName=APP_NAME) $appName:选传,有默认值

true:表示有操作权限

false:无操作权限

AccessDecision(appName=APPNAME)方法,检测当前项目模块操作,是否存在于appName=APPNAME)方法,检测当前项目模块操作,是否存在于_SESSION['_ACCESS_LIST']数组中$_SESSION['_ACCESS_LIST']['当前操作']['当前模块']['当前操作']是否存在。如果存在表示有权限,返回true;否则返回flase。
RBAC::getAccessList($authId) $authId:用户识别号(选传,程序自动获取) Array 通过数据库查询取得当前认证号的所有权限列表
RBAC::getModuleAccessList(authId,authId,module) $authId:用户识别号(必)$module:对应的模块(必) Array 返回指定用户可访问的节点权限数组

注意:在使用RBAC::AccessDecision()方法时,如果你开启了项目分组,则必须传入当前分组,代码如下:

//权限验证
if(C(&#39;USER_AUTH_ON&#39;) && !$notAuth) {
import(&#39;ORG.Util.RBAC&#39;);
//使用了项目分组,则必须引入GROUP_NAME
RBAC::AccessDecision(GROUP_NAME) || $this->error("你没有对应的权限");
}

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'] 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(&#39;?name&#39;); 相当于:isset($_SESSION[&#39;name&#39;]);
//     */
    public function _initialize(){
        /***************************************网站开关****************************************************/
        if(!C(&#39;WEB_STATE&#39;)) exit(&#39;网站维护中&#39;);

        /***********************************没有登录时候时需要执行的代码**************************************/
        if(!isset($_SESSION[C(&#39;USER_AUTH_KEY&#39;)])) 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()方法。

The above is the detailed content of RBAC permissions in ThinkPHP with menu bar display and detailed permission operations. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:cnblogs.com. If there is any infringement, please contact admin@php.cn delete