Qu'est-ce que le RBAC et quels problèmes peut-il résoudre ?
RBAC est l'acronyme de Role-Based Access Control. Traduit en chinois, cela signifie contrôle d'accès basé sur les rôles. Pour parler franchement, les utilisateurs sont associés à des autorisations via des rôles [source de. inspiration architecturale Contrôle de gestion des autorisations basé sur le GBAC (GROUP-Based Access Control) du système d'exploitation]. En termes simples, un utilisateur peut avoir plusieurs rôles, et chaque rôle dispose de plusieurs autorisations. De cette manière, un modèle d'autorisation « permission de rôle d'utilisateur » est construit. Dans ce modèle, il existe généralement une relation plusieurs-à-plusieurs entre les utilisateurs et les rôles, ainsi qu'entre les rôles et les autorisations. La relation correspondante est la suivante :
Dans de nombreuses applications pratiques, le système exige non seulement que les utilisateurs effectuent une simple inscription, mais nécessite également différents niveaux d'utilisateurs pour accéder à différentes ressources. Différentes autorisations d'exploitation. Et dans le développement des entreprises, le système de gestion des droits est devenu l'un des modules les plus efficaces pour le développement répété. Dans plusieurs systèmes, la gestion des autorisations correspondante ne peut répondre qu'aux besoins de gestion du propre système. Elle peut être différente en termes de conception de la base de données, d'accès aux autorisations et de mécanismes de gestion des autorisations. Cette incohérence a également les conséquences suivantes :
. • Maintenir plusieurs systèmes et réinventer la roue est une perte de temps
• La maintenance répétée des données telles que la gestion des utilisateurs et les mécanismes organisationnels rend difficile la garantie de l'intégrité et de la cohérence des données
• Différentes conceptions de systèmes d'autorisation, différentes compréhensions conceptuelles, et les différences techniques correspondantes, il existe des problèmes d'intégration entre les systèmes, l'authentification unique est difficile et les systèmes d'entreprise complexes posent des difficultés
RBAC est proposé sur la base d'une pratique continue Un schéma de contrôle d'accès relativement mature. La pratique a montré que l'utilisation d'un système de gestion des autorisations basé sur le modèle RBAC présente les avantages suivants : Étant donné que les changements entre les rôles et les autorisations sont relativement plus lents que les changements entre les rôles et les relations entre les utilisateurs, la complexité de la gestion des autorisations est réduite et les frais de gestion sont réduits. Et il peut prendre en charge de manière flexible la politique de sécurité du système d'application et a une grande évolutivité aux changements dans le système d'application ; en termes de fonctionnement, la distribution des autorisations est intuitive, facile à comprendre et les autorisations hiérarchiques conviennent aux utilisateurs hiérarchiques ; formes de niveau ; réutilisation du sexe fort.
Le système d'implémentation RBAC dans ThinkPHP
RBAC dans ThinkPHP utilise le système de sécurité Acegi de Spring basé sur Java comme prototype de référence et a apporté les simplifications correspondantes pour s'adapter au ThinkPHP actuel La structure fournit un système de sécurité multicouche et personnalisable pour fournir un contrôle de sécurité pour le développement d'applications. Le système de sécurité comprend principalement les parties suivantes :
• Intercepteur de sécurité
• Gestionnaire d'authentification
• Gestionnaire d'accès aux décisions
• Exécution d'un dispositif de gestion d'identité
Intercepteur de sécurité
L'intercepteur de sécurité est comme une porte. Il peut y avoir de nombreux liens de contrôle de sécurité différents dans le système de protection de sécurité du système. certification du système de sécurité, l'intercepteur de sécurité l'interceptera.
Gestionnaire d'authentification
La première porte du système de protection est le gestionnaire d'authentification. Le gestionnaire d'authentification est chargé de déterminer qui vous êtes. Généralement, il vérifie votre sujet (. Généralement un nom d'utilisateur) et vos informations d'identification (généralement un mot de passe), ou plus d'informations pour ce faire. Pour faire plus simple, le gestionnaire d'authentification vérifie que votre identité entre dans le périmètre d'autorisation du système de protection de sécurité.
Gestion des décisions d'accès
Bien que vous ayez réussi la vérification d'identité du gestionnaire d'authentification, cela ne signifie pas que vous pouvez faire ce que vous voulez dans le système, car vous il faut encore passer la décision d'accès Gérer cette porte. Le gestionnaire de décision d'accès autorise les utilisateurs et détermine si vous pouvez accéder à un certain module du système et effectuer une certaine opération en tenant compte de vos informations d'authentification d'identité et des attributs de sécurité associés aux ressources protégées. Par exemple, si les règles de sécurité stipulent que seuls les superviseurs sont autorisés à accéder à un certain module et que vous ne disposez pas des autorisations de superviseur, l'intercepteur de sécurité interceptera votre opération d'accès.
Le gestionnaire d'accès décisionnel ne peut pas fonctionner seul et doit d'abord s'appuyer sur le gestionnaire d'authentification pour la confirmation de l'identité. Par conséquent, le gestionnaire d'authentification et le gestionnaire d'accès décisionnel sont déjà inclus lors du chargement du filtre de décision d'accès.
Afin de répondre aux différents besoins des applications, ThinkPHP adopte deux modes lors de la gestion des décisions d'accès : le mode connexion et le mode instantané. En mode connexion, le système lit et modifie les informations d'autorisation de l'utilisateur sur la session lorsque l'utilisateur se connecte, et n'obtient pas à nouveau les informations d'autorisation la prochaine fois. C'est-à-dire que même si l'administrateur apporte des modifications aux autorisations de l'utilisateur, les modifications ne prendront effet qu'après la prochaine connexion de l'utilisateur. Le mode instantané vise à résoudre les problèmes ci-dessus. Chaque fois qu'un module ou une opération du système est accédé, il est immédiatement vérifié si l'utilisateur dispose de l'autorisation pour le module et l'opération, garantissant ainsi la sécurité du système à un degré plus élevé.
Exécuter Identity Manager
L'utilité du gestionnaire d'identité en cours d'exécution est limitée dans la plupart des systèmes d'application. Par exemple, si une certaine opération ou un certain module nécessite les exigences de sécurité de plusieurs identités, le gestionnaire d'identité en cours d'exécution peut remplacer votre identité actuelle par une autre identité, vous permettant ainsi de le faire. accéder aux objets protégés plus profondément dans le système d’application. Cette couche de système de sécurité n'est actuellement pas implémentée dans RBAC.
Processus d'authentification RBAC dans ThinkPHP
Correspondant au système de sécurité ci-dessus, le processus d'authentification RBAC de ThinkPHP est à peu près le suivant :
1. le module actuel Si l'opération en cours nécessite une authentification
2. Si l'authentification est requise et que vous n'êtes pas encore connecté, accédez à la passerelle d'authentification. Si vous êtes déjà connecté, exécutez 5
. 3. Authentification de l'identité de l'utilisateur par authentification déléguée
4. Obtenir la liste d'accès décisionnel de l'utilisateur
5 Déterminer si l'utilisateur actuel dispose des autorisations d'accès
Le. processus de mise en œuvre spécifique de la gestion des autorisations
L'introduction à la base de données liée au RBAC
est dans le package complet ThinkPHP, y compris la classe de traitement RBAC RBAC.class.php文件,
située dans Extend/Library/ORG/Util
. Ouvrez le fichier qui contient les 4 tables nécessaires à l'utilisation de RBAC. Les instructions SQL sont les suivantes (veuillez remplacer le préfixe de la table après la copie) :
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;
Faisons quelques tables et champs de base de données liés à RBAC. Introduction :
Nom de la table | Nom du champ | Type de champ | Fonction |
---|---|---|---|
ly_user | id | INT | ID utilisateur (numéro d'identification unique) |
nom d'utilisateur | VARCHAR(16) | nom d'utilisateur | |
mot de passe | VARCHAR(32) | mot de passe | |
VARCHAR(100) | E-mail de l'utilisateur | ||
create_time | TIMESTAMP | Heure de création (horodatage) | |
logintime | TIMESTAMP | Heure de la dernière connexion (horodatage) | |
loginip | VARCHAR(15) | Adresse IP récemment connectée | |
statut | TINYINT(1) | Statut activé : 0 : signifie désactivé ; 1 : signifie activé | |
remarque | VARCHAR(255) | Informations sur la remarque | |
ly_role | id | INT | ID de rôle |
nom | VARCHAR(20 ) | Nom du personnage | |
pid | SMALLINT(6) | ID correspondant au rôle parent | |
statut | TINYINT(1) | Statut activé (identique à ci-dessus) | |
remarque | VARCHAR (255 ) | Informations sur les remarques | |
ly_node | id | SMALLINT(6) | ID du nœud |
nom | VARCHAR(20) | Node du nœud (nom anglais, correspondant au contrôleur d'application, à l'application, au nom de la méthode) | |
titre | VARCHAR(50) | Nom chinois du nœud (facile à comprendre) | |
statut | TINYINT( 1) | Statut activé (identique à celui ci-dessus) | |
remarque | VARCHAR(255) | Informations sur la remarque | |
tri | SMALLINT(6) | Valeur de tri (la valeur par défaut est 50) | |
pid | SMALLINT(6) | ID du nœud parent (tel que : la méthode pid correspond au contrôleur correspondant) | |
niveau | TINYINT(1) | Type de nœud : 1 : représente l'application (module) ; 2 : représente le contrôleur ; 3 : représente la méthode | |
ly_role_user | user_id | INT | ID utilisateur |
role_id | SMALLINT( 6 ) | ID de rôle | |
ly_access | role_id | SMALLINT(6) | ID de rôle |
node_id | SMALLINT(6) | ID de nœud | |
niveau | |||
module |
以下是数据库表各字段的关联关系:
实现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处理类提供给我们很多相关的静态方法如下:
方法名 | 接收参数说明 | 返回值 | 说明 |
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'] < $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()
方法。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!