Home > Article > Backend Development > RBAC permissions in ThinkPHP with menu bar display and detailed permission operations
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:
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:
Table name | Field name | Field type | Function |
---|---|---|---|
id | INT | User ID (unique identification number) | |
VARCHAR(16) | Username | ||
VARCHAR(32) | Password | ||
VARCHAR(100) | user email | ||
TIMESTAMP | Creation time (timestamp) | ||
TIMESTAMP | Latest login time (timestamp) | ||
VARCHAR(15) | Last logged in IP address | ||
TINYINT(1) | Enabled status: 0: means disabled; 1: means enabled | ||
VARCHAR(255) | Remark information | ||
id | INT | Role ID | |
VARCHAR(20) | Role name | ||
SMALLINT(6) | Parent role corresponding ID | ||
TINYINT(1) | Enabled status (same as above) | ||
VARCHAR (255) | Remark information | ||
id | SMALLINT(6) | Node ID | |
VARCHAR(20) | Node name (English name, corresponding to application controller, application, method name) | ||
VARCHAR(50) | Chinese name of the node (easy to understand) | ||
TINYINT(1) | Enabled status (same as above) | ||
VARCHAR(255) | Remark information | ||
SMALLINT(6) | Sort value (default is 50) | ||
SMALLINT(6) | Parent node ID (such as: method pid corresponds to the corresponding controller) | ||
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 | |
role_id | SMALLINT(6) | role ID | |
SMALLINT(6) | Node 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('USER_AUTH_ON') && !$notAuth) { import('ORG.Util.RBAC'); //使用了项目分组,则必须引入GROUP_NAME RBAC::AccessDecision(GROUP_NAME) || $this->error("你没有对应的权限"); }
在完成用户登录,角色创建,节点增删改查的工作后,就只剩下了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('?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()
方法。
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!