CI框架下权限管理,角色组,权限菜单实践

在后台开发中,对管理员的权限组(角色)的管理,以及对应的菜单按权限加载是基本的需求,之前基于TP5框架参考Auth类在上一家公司将这套系实践上线,目前由于更换工作,新公司使用的是CI框架,后台的这个需求依然存在,于是在CI上又实践一次,记录一下。
CI框架权限管理

预期

这套简易的权限系统达到的预期效果:对每个管理员可以指定多个角色,每个角色分配不同的权限,权限对应每个控制器的每个对外调用的方法,菜单跳转对应权限,按登录的管理员拥有的权限来加载。

环境:CodeIgniter 3.1.9 + PHP 7.2 + MariaDB 10.1.36

数据表:

管理员表:

CREATE TABLE `admin` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
  `username` varchar(100) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
  `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态',
  `createtime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '添加时间',
  `updatetime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='管理员表';

权限表:

CREATE TABLE `auth_rule` (
  `name` varchar(200) NOT NULL DEFAULT '' COMMENT '验证规则',
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(200) DEFAULT '' COMMENT '权限名称',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1正常0关闭',
  `cat_id` tinyint(2) NOT NULL DEFAULT '1' COMMENT '权限分类',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='权限表';

权限分类表:

CREATE TABLE `auth_rule_cat` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(200) DEFAULT '' COMMENT '权限分类名称',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1正常0删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='权限分类表';

角色组表:

CREATE TABLE `et_auth_group` (
  `id` int(4) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(200) NOT NULL DEFAULT '' COMMENT '角色名',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1正常0禁用',
  `rules` text NOT NULL COMMENT '权限id,英文逗号间隔',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='角色组';

用户角色关系表:

CREATE TABLE `auth_group_access` (
  `uid` int(10) unsigned NOT NULL COMMENT '管理员id',
  `group_id` int(10) unsigned NOT NULL COMMENT '角色组id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户角色关系表';

后台菜单表:

CREATE TABLE `et_auth_menu` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `icon` varchar(50) DEFAULT '' COMMENT '菜单字体图标icon',
  `title` varchar(100) NOT NULL DEFAULT '' COMMENT '菜单的中文名称',
  `rule_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '对应的权限id',
  `pid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '菜单pid所属分类父级id,0表示顶级',
  `url` varchar(100) DEFAULT '' COMMENT '菜单对应的uri',
  `et_order` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '排序',
  `status` tinyint(2) NOT NULL DEFAULT '1' COMMENT '1正常0删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='后台菜单';

实现

对应数据表的增删改查就是基本的条件,这里只讲一下权限验证以及菜单加载的实现。

添加需要验证的权限:

权限列表
这里需要将每个控制器里对外开放的方法都当作规则添加进来。

封装验证类

libraries/common下添加文件Authhandler.php,代码:

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * 权限验证
 * hsu1943 20181112
 */
class Authhandler
{
    protected $_CI;

    // 默认配置
    public $_config = array(
        'AUTH_ON'               =>  true,       // 验证开关
        'SUPER_ADMINISTRATOR'   =>  'admin',    // 超级管理员用户名
    );

    public function __construct()
    {
        $this->_CI =& get_instance();
    }

    /**
     * 权限验证
     * @param  int          $uid        用户id
     * @param  array        $names      待验证的权限,支持逗号分隔的权限规则或索引数组
     * @param  string       $relation   待验证权限是否都需要满足,'or'|'and'
     * @return boolean                  true|false
     */
    public function check($uid, $names, $relation = 'or')
    {
        // 关闭验证跳过
        if (!$this->_config['AUTH_ON']) {
            return true;
        }

        // 超管跳过
        if($_SESSION['admin']['username'] === $this->_config['SUPER_ADMINISTRATOR'])
        {
            return true;
        }

        // 获取用户权限
        $authList = $this->getAuthList($uid);

        // 待验证权限
        if (is_string($names)) {
            $names = strtolower($names);
            if (strpos($names, ',') !== false) {
                $names = explode(',', $names);
            } else {
                $names = array($names);
            }
        }

        // 通过验证的权限数组
        $list = [];
        foreach ($names as $name) {
            if(in_array($name, $authList))
            {
                $list[] = $name;
            }
        }

        if ('or' == $relation and !empty($list)) {
            return true;
        }
        $diff = array_diff($names, $list);
        if ('and' == $relation and empty($diff)) {
            return true;
        }
        return false;
    }

    /**
     * 获取用户所有角色组下的权限name集合
     * @param  int      $uid        管理员id
     * @return array    $authList   权限name集合
     */
    public function getAuthList($uid)
    {
        $this->_CI->load->library('mysql/authdb');
        $groups = $this->_CI->authdb->getGroupsByUID($uid);

        $ids = [];
        foreach ($groups as $g) {
           $ids = array_merge($ids, explode(',', trim($g['rules'], ',')));
        }
        $ids = array_unique($ids);
        $rules = $this->_CI->authdb->getRulesByIds($ids);
        $_SESSION['admin']['AUTH_'.$uid] = $rules;
        $authList = array_column($rules, 'name');
        return $authList;
    }
}

公共代码中添加验证

这里的公共代码在CI框架中有多重选择,比如post_controller_constructor钩子中验证,或者控制器的核心父类core/MY_admin_controller.php中。

我选择在后者,在公共父类中验证用户SESSION通过后做验证:

// 验证权限
    $url_name = 'admin/' . $this->router->fetch_class() . '/' . $this->router->fetch_method(); 
    $this->load->library('common/authhandler');
    $check = $this->authhandler->check($_SESSION['admin']['id'], $url_name);
    if(!$check){
        show_error('Sorry, Permission denied!');
    }

添加权限菜单

添加进来的权限菜单类似这样的:
根据权限加载菜单
菜单对应的跳转URL实际上跟权限中的规则是一一对应的,根据这个可以检索到对应的权限与菜单绑定。

根据用户权限加载对应菜单

用户登录成功,查询用户对应权限组,合并权限组拥有的权限,去重后得到该用户所拥有的全部权限,根据权限查询对应的菜单,然后在模版公共模块中循环加载出来就行了。

2条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注