ホームページ >バックエンド開発 >PHPチュートリアル >メニューバー表示と詳細な権限操作を使用した ThinkPHP の RBAC 権限
RBAC とは何ですか?また、RBAC でどのような問題を解決できますか?
RBAC は、Role-Based Access Control の頭字語です。中国語に翻訳すると、ロールベースの権限アクセス制御を意味します。率直に言うと、ユーザーはロールを通じて権限に関連付けられます [出典]オペレーティング システムの GBAC (GROUP-Based Access Control) に基づいた権限管理制御がそのアーキテクチャのインスピレーションの源です。簡単に言うと、ユーザーは複数のロールを持つことができ、各ロールには複数の権限があります。このようにして、「ユーザー-ロール-権限」認可モデルが構築されます。このモデルでは、通常、ユーザーとロールの間、およびロールと権限の間に多対多の関係が存在します。対応関係は次のとおりです。
多くの実際のアプリケーションでは、システムはユーザーに簡単な登録を完了させるだけでなく、さまざまなレベルのさまざまなリソースにアクセスする必要があります。ユーザーの数、操作権限が異なります。また、エンタープライズ開発において、権利管理システムは、繰り返し開発を行うための最も効率的なモジュールの 1 つとなっています。複数のシステムでは、対応するアクセス許可管理は、独自のシステムの管理ニーズのみを満たすことができ、データベース設計、アクセス許可、およびアクセス許可管理メカニズムの点で異なる場合があります。この不一致は、次の結果ももたらします:
• 複数のシステムを維持し、車輪の再発明を繰り返し、最先端で時間が無駄になる
• ユーザー管理や組織メカニズムなどのデータのメンテナンスを繰り返すと、データの整合性と一貫性を保証することが困難になる
• 権限システムが異なるため設計、異なる概念理解、および対応する技術的な違いにより、システム間の統合に問題があり、シングル サインオンが難しく、複雑なエンタープライズ システムにも困難が伴います
RBAC は継続的な実践に基づいて提案されました 比較的成熟したアクセス制御スキーム。実際に、RBAC モデルに基づく権限管理システムを使用すると、次の利点があることがわかっています。 役割と権限間の変更は、役割とユーザー関係間の変更よりも比較的遅いため、権限管理の複雑さが軽減され、管理オーバーヘッドが軽減されます。 ; アプリケーションシステムのセキュリティポリシーに柔軟に対応でき、アプリケーションシステムの変更に対する拡張性が高い; 操作面では、権限の配布が直観的で分かりやすく、使いやすい; 階層型権限は階層化されたユーザーに適している-レベルフォーム; 強いセックスを再利用します。
ThinkPHP の RBAC 実装システム
ThinkPHP の RBAC は、Java ベースの Spring の Acegi セキュリティ システムを参照プロトタイプとして使用し、現在の ThinkPHP に適応するために対応する簡素化を行っています。この構造は、アプリケーション開発のセキュリティ制御を提供する、多層のカスタマイズ可能なセキュリティ システムを提供します。セキュリティ システムは主に次の部分で構成されます。
• セキュリティ インターセプタ
#• 認証マネージャー
#• 意思決定アクセス マネージャー
#• 実行中の ID 管理デバイス
セキュリティ インターセプター
セキュリティ インターセプターはドアのようなものです。システムのセキュリティ保護システムには、さまざまなセキュリティ制御リンクが存在する可能性があります。特定のリンクが確立されていない場合は、セキュリティ システムの認証に合格すると、セキュリティ インターセプターがそれを傍受します。
認証マネージャー
保護システムの最初のドアは認証マネージャーです。認証マネージャーは、あなたが誰であるかを判断する責任があります。通常、それはあなたの主題を検証します(通常はユーザー名)と資格情報(通常はパスワード)、またはこれを行うための詳細情報。より簡単に言うと、認証マネージャーは、ユーザーの ID がセキュリティ保護システムの承認範囲内にあることを確認します。
アクセス意思決定管理
認証マネージャーの認証に合格したとしても、システム内でやりたいことが何でもできるわけではありません。このドアを管理するアクセス決定を通過する必要があります。アクセス決定マネージャーはユーザーを認可し、ID 認証情報と保護されたリソースに関連付けられたセキュリティ属性を考慮して、ユーザーがシステムの特定のモジュールに入って特定の操作を実行できるかどうかを決定します。たとえば、セキュリティ ルールでスーパーバイザのみが特定のモジュールにアクセスできると規定されているのに、スーパーバイザのアクセス許可が付与されていない場合、セキュリティ インターセプタがアクセス操作を傍受します。
意思決定アクセス マネージャーは単独で実行することはできず、ID 確認のために最初に認証マネージャーに依存する必要があります。そのため、認証マネージャーと意思決定アクセス マネージャーは、アクセス決定フィルターをロードするときにすでに組み込まれています。
アプリケーションのさまざまなニーズを満たすために、ThinkPHP はアクセス決定管理を実行する際に、ログイン モードとインスタント モードという 2 つのモードを採用します。ログインモードでは、ユーザーのログイン時にシステムがユーザーの認可情報を読み取ってセッションに変更し、次回からは認可情報を再取得しません。つまり、管理者がユーザーの権限を変更した場合でも、その変更はユーザーが次回ログインした後にのみ有効になります。イミディエイトモードは、上記の問題を解決するもので、システムのモジュールや操作にアクセスするたびに、そのモジュールや操作に対する権限がユーザーにあるかどうかを即座に検証し、システムのセキュリティをより高いレベルで確保します。
Identity Manager の実行
実行中の ID マネージャーの有用性は、ほとんどのアプリケーション システムでは制限されています。たとえば、特定の操作とモジュールには、複数の ID のセキュリティ要件が必要です。実行中の ID マネージャーは、現在の ID を別の ID に置き換えることで、ユーザーのアクセスを許可します。アプリケーション システムの奥深くにあるオブジェクトを保護します。このセキュリティ システム層は現在 RBAC に実装されていません。
ThinkPHP の RBAC 認証プロセス
上記のセキュリティ システムに対応して、ThinkPHP の RBAC 認証プロセスは大まかに次のとおりです。現在のモジュール 現在の操作に認証が必要かどうか
2。認証が必要でログインしていない場合は、認証ゲートウェイにジャンプします。ログインしている場合は、5
3を実行します。委任認証によるユーザー ID 認証
4. ユーザーの意思決定アクセス リストを取得する
##5. 現在のユーザーにアクセス許可があるかどうかを確認する##具体的な実装権限管理のプロセスRBAC 関連データベースの紹介
##ThinkPHP 完全パッケージには、RBAC 処理クラス RBAC.class.php ファイル ## が含まれています#Extend/Library/ORG/Util にあります。 RBAC を使用するために必要な 4 つのテーブルを含むファイルを開きます。SQL ステートメントは次のとおりです (コピー後にテーブルの接頭語を置き換えてください):
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;
以下は、RBAC に関連するデータベース テーブルとフィールドの紹介です。 RBAC: <table class="table">
<thead><tr class="firstRow">
<th style="border-color: rgb(221, 221, 221);">テーブル名</th>
<th style="border-color: rgb(221, 221, 221);">フィールド名</th>
<th style="border-color: rgb(221, 221, 221);">フィールド タイプ</th>
<th style="border-color: rgb(221, 221, 221);">関数</th>
</tr></thead>
<tbody> <tr>##ly_user<td rowspan="9" style="border-color: rgb(221, 221, 221);"></td>id<td style="border-color: rgb(221, 221, 221);"></td>INT<td style="border-color: rgb(221, 221, 221);"></td>ユーザー ID (固有の識別番号) <td style="border-color: rgb(221, 221, 221);"></td>
</tr>
<tr>ユーザー名 <td style="border-color: rgb(221, 221, 221);"># #VARCHAR(16)</td>
<td style="border-color: rgb(221, 221, 221);">ユーザー名</td>
<td style="border-color: rgb(221, 221, 221);"></td>
</tr>パスワード<tr>
<td style="border-color: rgb(221, 221, 221);">VARCHAR(32)</td>
<td style="border-color: rgb(221, 221, 221);">パスワード</td>
<td style="border-color: rgb(221, 221, 221);"> </td>
</tr>email<tr>
<td style="border-color: rgb(221, 221, 221);">VARCHAR(100)</td>
<td style="border-color: rgb(221, 221, 221);">ユーザーメールアドレス</td>
<td style="border-color: rgb(221, 221, 221);"></td>
</tr>create_time<tr>
<td style="border-color: rgb(221, 221, 221);">TIMESTAMP</td>
<td style="border-color: rgb(221, 221, 221);">作成時間 (タイムスタンプ)</td>
<td style="border-color: rgb(221, 221, 221);"></td>
</tr>logintime<tr>
<td style="border-color: rgb(221, 221, 221);">TIMESTAMP</td>
<td style="border-color: rgb(221, 221, 221);">最終ログイン時間 (タイムスタンプ)</td>
<td style="border-color: rgb(221, 221, 221);"></td> </tr>loginip<tr>
<td style="border-color: rgb(221, 221, 221);">VARCHAR(15)</td>
<td style="border-color: rgb(221, 221, 221);">最後にログインした IP アドレス</td>
<td style="border-color: rgb(221, 221, 221);"></td>
</tr>ステータス<tr>
<td style="border-color: rgb(221, 221, 221);">TINYINT(1)</td>
<td style="border-color: rgb(221, 221, 221);">有効ステータス: 0: 無効であることを意味します; 1: 有効であることを意味します </td>
<td style="border-color: rgb(221, 221, 221);"></td>
</tr>備考<tr>
<td style="border-color: rgb(221, 221, 221);">VARCHAR(255)</td>
<td style="border-color: rgb(221, 221, 221);">備考情報</td> <td style="border-color: rgb(221, 221, 221);"></td>
</tr>ly_role<tr>
<td rowspan="5" style="border-color: rgb(221, 221, 221);">id</td>
<td style="border-color: rgb(221, 221, 221);">INT</td>
<td style="border-color: rgb(221, 221, 221);">ロール ID</td>
<td style="border-color: rgb(221, 221, 221);"></td>
</tr>name<tr>
<td style="border-color: rgb(221, 221, 221);">VARCHAR(20) </td>#ロール名<td style="border-color: rgb(221, 221, 221);"></td>
<td style="border-color: rgb(221, 221, 221);"></td>pid</tr>
<tr>SMALLINT(6)<td style="border-color: rgb(221, 221, 221);"></td>親ロールに対応するID<td style="border-color: rgb(221, 221, 221);"></td>
<td style="border-color: rgb(221, 221, 221);"> </td>status</tr>
<tr>TINYINT(1)<td style="border-color: rgb(221, 221, 221);"></td>有効ステータス (上記と同じ) <td style="border-color: rgb(221, 221, 221);"></td>
<td style="border-color: rgb(221, 221, 221);"></td>remark</tr>
<tr>VARCHAR (255) <td style="border-color: rgb(221, 221, 221);"></td>備考情報<td style="border-color: rgb(221, 221, 221);"></td>
<td style="border-color: rgb(221, 221, 221);"></td>ly_node</tr>
<tr>id<td rowspan="8" style="border-color: rgb(221, 221, 221);"></td>SMALLINT(6)<td style="border-color: rgb(221, 221, 221);"></td>ノードID<td style="border-color: rgb(221, 221, 221);"></td>
<td style="border-color: rgb(221, 221, 221);"></td>name</tr>
<tr>VARCHAR(20)<td style="border-color: rgb(221, 221, 221);"></td>ノード名 (アプリケーション コントローラー、アプリケーション、メソッド名に対応する英語名)<td style="border-color: rgb(221, 221, 221);"></td> <td style="border-color: rgb(221, 221, 221);"> </td>title</tr>
<tr>VARCHAR(50)<td style="border-color: rgb(221, 221, 221);"></td>ノードの中国語名 (わかりやすい)<td style="border-color: rgb(221, 221, 221);"></td>
<td style="border-color: rgb(221, 221, 221);"></td>status</tr>
<tr> TINYINT (1)<td style="border-color: rgb(221, 221, 221);"></td>有効状態(同上)<td style="border-color: rgb(221, 221, 221);"></td>
<td style="border-color: rgb(221, 221, 221);"></td>備考</tr>
<tr>VARCHAR(255)<td style="border-color: rgb(221, 221, 221);"></td>備考情報<td style="border-color: rgb(221, 221, 221);"> </td>
<td style="border-color: rgb(221, 221, 221);"></td>sort</tr>
<tr>SMALLINT(6)<td style="border-color: rgb(221, 221, 221);"></td>ソート値 (デフォルトは 50)<td style="border-color: rgb(221, 221, 221);"></td>
<td style="border-color: rgb(221, 221, 221);"></td>pid</tr>
<tr>SMALLINT(6)<td style="border-color: rgb(221, 221, 221);"></td>親ノード ID (例: メソッド pid は対応するコントローラーに対応します)<td style="border-color: rgb(221, 221, 221);"></td>
</tr>
<tr>
<td style="border-color: rgb(221, 221, 221);">level</td>
<td style="border-color: rgb(221, 221, 221);">TINYINT(1)</td>
<td style="border-color: rgb(221, 221, 221);">ノード タイプ: 1: アプリケーション (モジュール) を表します; 2: コントローラーを表します; 3: メソッドを表します </td>
</tr>
<tr>#ly_role_user<td rowspan="2" style="border-color: rgb(221, 221, 221);"></td>user_id<td style="border-color: rgb(221, 221, 221);"></td>INT<td style="border-color: rgb(221, 221, 221);"></td>ユーザー ID<td style="border-color: rgb(221, 221, 221);"></td>
</tr>
<tr>role_id<td style="border-color: rgb(221, 221, 221);"></td>SMALLINT( 6 )<td style="border-color: rgb(221, 221, 221);"></td>ロール ID<td style="border-color: rgb(221, 221, 221);"></td>
</tr>##ly_access<tr>
<td rowspan="4" style="border-color: rgb(221, 221, 221);">role_id</td>
<td style="border-color: rgb(221, 221, 221);">SMALLINT(6)</td>
<td style="border-color: rgb(221, 221, 221);">ロール ID</td>
<td style="border-color: rgb(221, 221, 221);"></td>
</tr>node_id<tr>
<td style="border-color: rgb(221, 221, 221);">SMALLINT(6)</td>
<td style="border-color: rgb(221, 221, 221);">ノード ID</td>
<td style="border-color: rgb(221, 221, 221);"></td>##レベル</tr>
<tr> <td style="border-color: rgb(221, 221, 221);"></td> <td style="border-color: rgb(221, 221, 221);"></td>
<td style="border-color: rgb(221, 221, 221);"></td>モジュール</tr>
<tr> <td style="border-color: rgb(221, 221, 221);"></td> <td style="border-color: rgb(221, 221, 221);"></td>
<td style="border-color: rgb(221, 221, 221);"></td>
<p>以下是数据库表各字段的关联关系:</p>
<p class="img_wrapper"><img class="loaded" title="メニューバー表示と詳細な権限操作を使用した ThinkPHP の RBAC 権限" src="https://img.php.cn/upload/article/000/000/020/6bce41a2d99287a65cf142f07d031954-1.png" alt="メニューバー表示と詳細な権限操作を使用した ThinkPHP の RBAC 権限" style="max-width:90%" style="max-width:90%" border="0"></p>
<p class="img_wrapper"><strong>实现RBAC管理的前导性工作</strong></p>
<p>基于ThinkPHP实现RBAC的权限管理系统中,首先要做一些前导性的工作(系统数据库设计TP已经为我们完成了),主要分以下几个方面:</p>
<p>• 用户(增、删、改、查)<br>• 角色(增、删、改、查)<br>• 节点(增、删、改、查)<br>• 配置权限(更新权限)</p>
<p>具体实现的代码如下(相关解释均在注释之中):</p>
<pre class="brush:php;toolbar:false"><?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');
}
}</pre>
<p><strong>ThinkPHP中RBAC类的详解</strong><br></p>
<p>在ThinkPHP处理权限管理中,真正的难点并不是在RBAC类的使用上,上面相关的处理(权限配置,节点管理等);所以当完成以上工作,其实RBAC系统已经完成了90%。下面先从ThinkPHP中RBAC的配置说起(详细请参看对应的注释内容):</p>
<pre class="brush:php;toolbar:false"><?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', //节点表名称(必配)
);</pre>
<p><strong>注意:</strong></p>
<p>• 以上有的配置项并非必须的,但标有“必配”,则必须配置</p>
<p>• 无需认证的权限和方法与需要认证的模块和动作可以根据需要选择性配置,还有部分权限相关配置并未列表出</p>
<p><strong>RBAC处理类提供静态的方法</strong></p>
<p>ThinkPHP的RBAC处理类提供给我们很多相关的静态方法如下:</p>
<table><tbody>
<tr class="firstRow">
<td width="190" valign="top" style="word-break: break-all;"><strong>方法名</strong></td>
<td width="190" valign="top" style="word-break: break-all;"><strong>接收参数说明</strong></td>
<td width="190" valign="top" style="word-break: break-all;"><strong>返回值</strong></td>
<td width="190" valign="top" style="word-break: break-all;"><strong>说明</strong></td>
</tr>
<tr>
<td width="190" valign="top" style="word-break: break-all;">RBAC::authenticate(map,map,model='');</td>
<td width="190" valign="top" style="word-break: break-all;">
<p>$map:ThinkPHP数据库处理类的where条件参</p>
<p>数$model:用户表名,默认取配置文件C('USER_AUTH_MODEL')</p>
</td>
<td width="190" valign="top" style="word-break: break-all;">array</td>
<td width="190" valign="top" style="word-break: break-all;">
<p>RBAC::authenticate(array("username"=>"admin","userpwd" => I('POST.pwd','', 'md5')));</p>
<p>返回值是在用户表中,以$map为条件where的查阅结果集</p>
</td>
</tr>
<tr>
<td width="190" valign="top" style="word-break: break-all;">0RBAC::saveAccessList($authId=null);</td>
<td width="190" valign="top" style="word-break: break-all;">$authId:用户识别号,默认取C('USER_AUTH_KEY');</td>
<td width="190" valign="top" style="word-break: break-all;">返回一个空值</td>
<td width="190" valign="top" style="word-break: break-all;">如果验证方式为登录验证,则将权限写入session中,否则不作任何处理</td>
</tr>
<tr>
<td width="190" valign="top" style="word-break: break-all;">RBAC::getRecordAccessList(authId=null,authId=null,module='');</td>
<td width="190" valign="top" style="word-break: break-all;">
<p>$authId:用户识别号(可不传)</p>
<p>$module:当前操作的模块名称</p>
</td>
<td width="190" valign="top" style="word-break: break-all;">Array</td>
<td width="190" valign="top" style="word-break: break-all;">返回一个包含权限的ID的数组</td>
</tr>
<tr>
<td width="190" valign="top" style="word-break: break-all;">RBAC::checkAccess()</td>
<td width="190" valign="top" style="word-break: break-all;">无</td>
<td width="190" valign="top" style="word-break: break-all;">返回true或false</td>
<td width="190" valign="top" style="word-break: break-all;">检查当前操作是否需要认证(根据配置中需要认证和不需要评论的模块或方法得出)</td>
</tr>
<tr>
<td width="190" valign="top" style="word-break: break-all;">RBAC::checkLogin()</td>
<td width="190" valign="top" style="word-break: break-all;">无</td>
<td width="190" valign="top" style="word-break: break-all;">true</td>
<td width="190" valign="top" style="word-break: break-all;">如果当前操作需要认证且用户没有登录,继续检测是否开启游客授权。如果开启游客授权,则写入游客权限;否则跳到登录页</td>
</tr>
<tr>
<td width="190" valign="top" style="word-break: break-all;">RBAC::AccessDecision($appName=APP_NAME)</td>
<td width="190" valign="top" style="word-break: break-all;">$appName:选传,有默认值</td>
<td width="190" valign="top" style="word-break: break-all;">
<p>true:表示有操作权限</p>
<p>false:无操作权限</p>
</td>
<td width="190" valign="top" style="word-break: break-all;">AccessDecision(appName=APPNAME)方法,检测当前项目模块操作,是否存在于appName=APPNAME)方法,检测当前项目模块操作,是否存在于_SESSION['_ACCESS_LIST']数组中$_SESSION['_ACCESS_LIST']['当前操作']['当前模块']['当前操作']是否存在。如果存在表示有权限,返回true;否则返回flase。</td>
</tr>
<tr>
<td width="190" valign="top" style="word-break: break-all;">RBAC::getAccessList($authId)</td>
<td width="190" valign="top" style="word-break: break-all;">$authId:用户识别号(选传,程序自动获取)</td>
<td width="190" valign="top" style="word-break: break-all;">Array</td>
<td width="190" valign="top" style="word-break: break-all;">通过数据库查询取得当前认证号的所有权限列表</td>
</tr>
<tr>
<td width="190" valign="top" style="word-break: break-all;">RBAC::getModuleAccessList(authId,authId,module)</td>
<td width="190" valign="top" style="word-break: break-all;">$authId:用户识别号(必)$module:对应的模块(必)</td>
<td width="190" valign="top" style="word-break: break-all;">Array</td>
<td width="190" valign="top" style="word-break: break-all;">返回指定用户可访问的节点权限数组</td>
</tr>
</tbody></table>
<p class="alert"><strong>注意</strong>:在使用<code>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()
方法。
以上がメニューバー表示と詳細な権限操作を使用した ThinkPHP の RBAC 権限の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。