本篇文章基于慕课网上一部关于 RBAC打造通用web管理项目 的视频教程,链接:http://www.imooc.com/learn/799 。于个人理解进行整理,仅做个人笔记使用,若不明白自行前往教程进行观摩学习。
常见权限管理模型有四种:ACL、RBAC、…(具体都有什么特点自行搜索引擎查看),这里只对RBAC进行介绍。
RBAC有三种对象:用户、角色、权限;
对应表则有五个:用户表——>用户角色关系表<—— 角色表——>角色权限关系表<——权限表.(三个对象及其相互之间的关系映射)。
本篇技术实现基于yii2框架,其他框架实现原理无差异,自行根据本语言框架特点具体根据场景做出逻辑处理。
对于一个安全的操作系统,除了登录页面,其他都要进行登录校验。另外,根据不同的用户级别,要做出访问权限合法性校验。具体使用表现形式,统一在基类控制器中进行逻辑处理(步骤二和三的校验统一放置于方法beforeAction()进行,原因不解释):
步骤一:
登录成功,进行相关信息创建存储等处理:
/**
* 登录时候设置登录相关cookie
*/
protected function createLoginStatus($user)
{
$auth_token = $this->createAuthToken($user['id'], $user['name'], $user['email'], $_SERVER['HTTP_USER_AGENT']);
$cookies = \Yii::$app->response->cookies;
$cookies->add(new Cookie([
'name' => $this->auth_cookie_name,
'value' => $auth_token . '#' . $user['id'],
]));
}
步骤二:
登录判断,其中checkLoginStatus方法是登录校验逻辑:
/*
* 检验是否登录或当期访问页面uri地址是否处于允许访问的页面集中
* 注意:::::下面判断逻辑两种情况用&&,否则会发生频繁重定向
* 只有未登录且当前请求链接地址处于允许不登录页可访问的链接集中才去跳转登陆页面
*/
if (!$this->checkLoginStatus() && !in_array($action->getUniqueId(), $this->allowAllAction)) {
if (\Yii::$app->request->isAjax) //ajax请求则为局部刷新显示提示信息即可
$this->renderJSON([], '未登录,请前往登录', 302);
else //直接跳往登录页面
$this->redirect(UrlService::buildUrl('/user/login'));
return false;
}
步骤三:
登录校验通过后对于当前访问链接权限合法性进行校验:
//判断当前用户访问的链接是否拥有对应的权限,若无则显示权限错误提示页面
if (!$this->checkPrivilege($action->getUniqueId())) {
$this->redirect(UrlService::buildUrl('/error/forbidden'));
return false;
}
return true;
从逻辑上来说,对于一个安全的系统来说就这么三步:默认访问处于非登录状态则登录;登录成功生成登录信息;后期的每一个访问请求都要进行登录校验、访问合法性校验——一个完整流程。
当然正常情况下在每一次操作请求都应该生成记录日志,这个同样可以放置于beforeAction方法中。具体这个控制器基类如下:
<?php
namespace app\controllers\base;
use app\models\AccessModel;//权限模型
use app\models\AppAccessLogModel;//操作记录日志模型
use app\models\RoleAccessModel;//角色权限关系模型
use app\models\UserModel;//用户模型
use app\models\UserRoleModel;//用户角色关系模型
use app\services\UrlService;//
use yii\web\Controller;
use yii\web\Cookie;
class BaseController extends Controller{
protected $auth_cookie_name = 'default_001'; //cookie存储当前登录用户名
protected $current_user = null; //当前登录用户信息
protected $allowAllAction = [
'user/login',
'user/vlogin',
]; //允许访问的页面url地址
protected $ignore_url = [
'error/forbidden',
'user/login',
'user/vlogin',
]; //不需要进行权限校验的页面url地址
private $privilege_urls = []; //保存当前用户拥有的权限链接集
/**
* 后台系统大部分页面都要进行登录校验,在框架中加入统一验证方法
*/
public function beforeAction($action){
/*
* 检验是否登录或当期访问页面uri地址是否处于允许访问的页面集中
* 注意:::::下面判断逻辑两种情况用&&,否则会发生频繁重定向
* ----------只有未登录且当前请求链接地址处于允许不登录页可访问的链接集中才去跳转登陆页面
*/
if (!$this->checkLoginStatus() && !in_array($action->getUniqueId(), $this->allowAllAction)) {
if (\Yii::$app->request->isAjax) //ajax请求则为局部刷新显示提示信息即可
$this->renderJSON([], '未登录,请前往登录', 302);
else //直接跳往登录页面
$this->redirect(UrlService::buildUrl('/user/login'));
return false;
}
//将本次访问记录存入数据库日志表中
$get_params = $this->get(null);
$post_params = $this->post(null);
$model_log = new AppAccessLogModel();
$model_log->uid = $this->current_user ? $this->current_user['id'] : 0; //当前访问用户id,未登录则 0
$model_log->target_url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';//访问url
$model_log->query_params = json_encode(array_merge($get_params, $post_params)); //json形式存储请求参数
$model_log->ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; //浏览器信息
$model_log->ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; //请求者ip地址
$model_log->created_time = date('Y-m-d H:i:s');
$model_log->save();
//判断当前用户访问的链接是否拥有对应的权限,若无则显示权限错误提示页面
if (!$this->checkPrivilege($action->getUniqueId())) {
$this->redirect(UrlService::buildUrl('/error/forbidden'));
return false;
}
return true;
}
/**
* 统一获取get参数的方法
*/
protected function get($key, $default = ''){
return \Yii::$app->request->get($key, $default);
}
/**
* 统一获取post参数的方法
*/
protected function post($key, $default = ''){
return \Yii::$app->request->post($key, $default);
}
/**
* @param $data 返回数据信息
* @param $msg 提示信息
* @param int $code 状态码,默认200表示成功
*/
protected function renderJSON($data, $msg, $code = 200){
header('Content-type: application/json');//设置头部内容格式
echo json_encode([
"code" => $code,
"msg" => $msg,
"data" => $data,
"req_id" => uniqid(),
]);
return \Yii::$app->end();//终止请求直接返回
}
/**
* 校验当前是否处于登录状态
*/
private function checkLoginStatus(){
$cookies = \Yii::$app->request->cookies;
$auth_cookie = $cookies->get($this->auth_cookie_name);
if (!$auth_cookie) //获取登录用户信息cookie,不存在则校验登录失败
return false;
list($auth_token, $uid) = explode('#', $auth_cookie); //以#拆分获取到的登录cookie信息
if (!$auth_token || !$uid) //拆分出来的token值或uid任何一个不存在则校验登录失败
return false;
if ($uid && preg_match("/^\d+$/", $uid)) {
$user = UserModel::find()->where(['status' => 1, 'id' => $uid])->one();
if (!$user) //校验uid对应的用户信息不存在则登录失败
return false;
if ($auth_token != $this->createAuthToken($user))
return false; //若拆分得到的token值与根据拆分拿到的uid对应的用户信息生成的token不一致则登录校验失败
$this->current_user = $user; //都校验通过则将该用户信息赋值
\Yii::$app->view->params['current_user'] = $user; //将该用户信息赋值给共享属性参数集
return true; //这些逻辑处理完毕则登录校验通过
}
return false;
}
/**
* @param $url 判断该链接是否拥有访问权限
* 取出当前用户所属角色,通过角色取出其所属权限关系集,比对该链接是否处于取出的权限关系集中
*/
protected function checkPrivilege($url){
//若当前用户是超级管理员,则不需要判断权限
if ($this->current_user && $this->current_user['is_admin'])
return true;
//若该链接处于忽略权限检查的集合中则不需要判断
if (in_array($url, $this->ignore_url))
return true;
return in_array($url, $this->getRolePrivilege());
}
/**
* 取出当前用户所拥有的访问权限链接集
*/
protected function getRolePrivilege($uid = 0){
if (!$uid && $this->current_user)
$uid = $this->current_user->id;
if (!$this->privilege_urls) {
//取出uid对应角色id集
$role_ids = UserRoleModel::find()->where(['uid' => $uid])->select('role_id')->asArray()->column();
if ($role_ids) { //取出角色集对应的权限id集
$access_ids = RoleAccessModel::find()->where(['role_id' => $role_ids])->select('access_id')->asArray()->column();
//根据权限id集取出对应的权限链接集
$access_list = AccessModel::find()->where(['id' => $access_ids])->all();
if ($access_list) {
foreach ($access_list as $item) {
$urls = @json_decode($item['urls'], true);
$this->privilege_urls = array_merge($this->privilege_urls, $urls);
}
}
}
}
return $this->privilege_urls;
}
/**
* 登录时候设置登录相关cookie
*/
protected function createLoginStatus($user){
$auth_token = $this->createAuthToken($user);
$cookies = \Yii::$app->response->cookies;
$cookies->add(new Cookie([
'name' => $this->auth_cookie_name,
'value' => $auth_token . '#' . $user['id'],
]));
}
/**
* 生成用户加密校验token值,可以自行根据需要进行不同安全程度的加密生成值
*/
private function createAuthToken($user){
return md5($user['id'] . $user['name'] . $user['email'] . $_SERVER['HTTP_USER_AGENT']);
}
}
具体应用场景的使用根据框架及需求进行适当的植入。