RBAC全称是Role-Based Access Control,取各自的首字母。它要解决的是用户、角色、权限之间的关系,如下图所示:

image_thumb.png

        它们之间,用户与角色一般是多对多的关系,角色与权限也是多对多。

        ThinkPHP3.1.3中,类RBAC是在ThinkPHP/Extend/Library/ORG/Util/RBAC.class.php中,代码最上面有四张表:

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;
         除了上面的四张表外,还需一张保存用户信息的用户表。在教程里,要解决的就是各用户可以具有多种角色,各角色有不同的权限。建立RbacAction.class.php,有role、node、addUser、addRole、addNode等实现角色、节点、添加用户、添加角色、添加节点的后台函数。如下:
<?php
/**
 * 用户与角色关联模型
 */
class RbacAction extends CommonAction{
	//用户列表
	public function index(){
		//D关联模型函数,实例化Model,M是普通模型,一般用来处理一个表。关联模型和视图模型一般用来处理多表操作。
		//视图模型一般是处理join语句
		/**
		 mysql> select * from think_user u left join think_role_user ru on u.id=ru.user_id left join think_role r on ru.role_id=r.id\G;
		 */
		$this->user=D('UserRelation')->field('password',true)->relation('role')->select();
		//p($result);
		//die;
		$this->display();
	}
	//角色列表
	public function role(){
		$this->role=M('role')->select();
		$this->display();
	}
	//节点列表
	public function node(){
		$field=array('id','name','title','pid');
		$node=M('node')->field($field)->order('sort')->select();
		$this->node=node_merge($node);
		//p($node);
		//die;
		$this->display();
	}
	//添加用户
	public function addUser(){
		$this->role=M('role')->select();
		$this->display();
	}
	//添加用户表单处理
	public function addUserHandle(){
		//用户信息
		$user = array(
			'username' => I('username'),
			'password' => I('password','','md5'),
			'logintime' => time(),
			'loginip' => get_client_ip()
			);
		//所属角色
		$role=array();
		if($uid=M('user')->add($user)){
			foreach ($_POST['role_id'] as $v) {
				$role[]=array(
					'role_id'=>$v,
					'user_id'=>$uid
					);
			}
			//添加用户角色
			M('role_user')->add($role);
			$this->success('添加用户成功',U('Admin/Rbac/index'));
		}else{
			$this->error('添加用户失败');
		}
	}
	//添加角色
	public function addRole(){
		$this->display();
	}
	//添加角色表单处理
	public function addRoleHandle(){
		if(M('role')->add($_POST)){
			$this->success('添加角色成功',U('Admin/Rbac/role'));
		}else{
			$this->error('添加角色失败');
		}
	}
	//添加节点
	public function addNode(){
		$this->pid=I('pid',0,'intval');
		$this->level=I('level',1,'intval');
		switch ($this->level) {
			case 1:
				$this->type='应用';
				break;
			case 2:
				$this->type='控制器';
				break;
			case 3:
				$this->type='动作方法';
				break;
		}
		//p($_GET);die;
		$this->display();
	}
	//添加节点表单处理
	public function addNodeHandle(){
		if(M('node')->add($_POST)){
			$this->success('添加节点成功',U('Admin/Rbac/node'));
		}else{
			$this->error('添加节点失败');
		}
	}
	//配置权限
	public function access(){
		$rid=I('rid',0,'intval');
		$field=array('id','name','title','pid');
		$node=M('node')->order('sort')->field($field)->select();
		//原有权限
		$access = M('access')->where(array('role_id' => $rid))->getField('node_id',true);
		$this->node=node_merge($node,$access);
		$this->rid=$rid;
		$this->display();
	}
	//修改权限
	public function setAccess(){
		$rid=I('rid',0,'intval');
		$db=M('access');
		//清空原权限
		$db->where(array('role_id'=>$rid))->delete();
		//组全新权限
		$data=array();
		foreach ($_POST['access'] as $v) {
			$tmp = explode('_',$v);
			$data[]=array(
				'role_id' => $rid,
				'node_id' => $tmp[0],
				'level' => $tmp[1]
				);
		}
		//插入新权限
		if($db->addAll($data)){
			$this->success('修改权限成功',U('Admin/Rbac/role'));
		}else{
			$this->error('修改权限失败');
		}
		//p($data);
	}
}
?>
         另外还需要建立用户与角色的模型,在Model文件夹下建立UserRelationModel.class.php,代码如下:
<?php
/**
 * 用户与角色关联模型
 */
class UserRelationModel extends RelationModel{
    //定义主表名称
    protected $tableName = 'user';      //此变量默认为类名去掉Model,即UserRelation,但我的关联表是user,因此要配置一下
    //定义关联关系
    protected $_link=array(
        'role'=>array(
            'mapping_type'=>MANY_TO_MANY,    //HAS_ONE是一对一,HAS_MANY是一对多,MANY_TO_MANY是多对多
            'foreign_key'=>'user_id',       //主表外键名称。主表在中间表中的字段名称
            'relation_key'=>'role_id',       //副表外键名称,副表在中间表中的字段名称
            'relation_table'=>'think_role_user',   //中间表名称
            'mapping_fields'=>'id,name,remark'      //副表要读取的项
            )
        );
}
?>
        当然前台的Tpl下还要建立Rbac文件夹,建立role.html、node.html、addUser.html等对应后台的函数。

        还有Conf/config.php里面要配置,可以配置的有:

<?php
/**
 * Description: ThinkPHP中RBAC处理类的配置文件
 */
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',       //无需认证的控制器
    "NOT_AUTH_ACTION" => 'addRoleHandle,addUserHandle,addNodeHandle,setAccess',              //无需认证的方法
    'REQUIRE_AUTH_MODULE' =>  '',              //默认需要认证的模块
    'REQUIRE_AUTH_ACTION' =>  '',              //默认需要认证的动作
    'GUEST_AUTH_ON'   =>  false,               //是否开启游客授权访问
    'GUEST_AUTH_ID'   =>  0,                   //游客标记
    "RBAC_ROLE_TABLE" => 'think_role',            //角色表名称(必配)
    "RBAC_USER_TABLE" => 'think_role_user',       //用户角色中间表名称(必配)
    "RBAC_ACCESS_TABLE" => 'think_access',        //权限表名称(必配)
    "RBAC_NODE_TABLE" => 'think_node',            //节点表名称(必配)
);

        总的来说其实现的是:

用户(增、删、改、查)
角色(增、删、改、查)
节点(增、删、改、查)
配置权限(更新权限)

       网上查得,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 返回指定用户可访问的节点权限数组