Laravel實現許可權控制
一、RBAC
RBAC: role base access control 基於角色的使用者訪問許可權控制許可權,就是許可權分配給角色,角色又分配給使用者。
即一個使用者對應一個角色,一個角色對應多個許可權,一個使用者對應使用者組,一個使用者組對應多個許可權。
二、認證授權邏輯
登入邏輯:
許可權控制邏輯:
三、具體實現
建立表的遷移檔案
使用者:
- 建立model和遷移檔案:
php artisan make:model Models/User -m
- 修改遷移檔案:
class CreateUsersTable extends Migration
{
/**
* 後臺使用者表
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
// 角色
$table->unsignedInteger('role_id')->default(0)->comment('角色ID');
$table->string('username', 50)->comment("賬號");
$table->string('truename', 20)->default('未知')->comment("賬號");
$table->string('password', 255)->comment('密碼');
$table->string('email', 50)->nullable()->comment('郵箱');
$table->string('phone', 15)->default('')->comment('手機號碼');
$table->enum('sex', ['先生','女士'])->default('先生')->comment('性別');
$table->char('last_ip', 15)->default('')->comment('登入IP');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
角色:
- 建立model和遷移檔案:
php artisan make:model Models/Role -m
- 修改遷移檔案:
class CreateRolesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name', 20)->comment('角色名稱');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('roles');
}
}
許可權:
- 建立model和遷移檔案:
php artisan make:model Models/Node -m
- 修改遷移檔案:
class CreateNodesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('nodes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name',50)->comment('節點名稱');
$table->string('route_name', 100)->nullable()->comment('路由別名,許可權認證標識');
$table->unsignedInteger('pid')->default(0)->comment('上級ID');
$table->enum('is_menu', ['0','1'])->comment('是否是選單');
$table->softDeletes();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('nodes');
}
}
- 新增使用者、角色一對一關聯關係
class User extends Authenticatable
{
public function role()
{
return $this->belongsTo(Role::class,'role_id');
}
}
- 新增角色、節點多對多關聯關係
class Role extends Model
{
public function nodes()
{
// 參1 關聯模型
// 參2 中間表的表名,沒有字首
// 參3 本模型對應的外來鍵ID
// 參4 關聯模型對應的外來鍵ID
return $this->belongsToMany(
Node::class,
'role_node',
'role_id',
'node_id');
}
}
- 新增節點展示方法
class Node extends Model
{
/**
* 獲取所有許可權節點
*
* @return array
*/
public function getAllList(){
$data = self::get()->toArray();
return $this->treeLevel($data);
}
/**
* 陣列的合併,並加上html標識字首
*
* @param array $data
* @param int $pid
* @param string $html
* @param int $level
* @return array
*/
public function treeLevel(array $data, int $pid = 0, string $html = '--', int $level = 0) {
static $arr = [];
foreach ($data as $val) {
if ($pid == $val['pid']) {
// 重複一個字元多少次
$val['html'] = str_repeat($html, $level * 2);
$val['level'] = $level + 1;
$arr[] = $val;
$this->treeLevel($data, $val['id'], $html, $val['level']);
}
}
return $arr;
}
/**
* 資料多層級
*
* @param array $data
* @param int $pid
* @return array
*/
public function subTree(array $data, int $pid = 0) {
// 返回的結果
$arr = [];
foreach ($data as $val) {
// 給定的PID是當前記錄的上級ID
if ($pid == $val['pid']) {
// 遞迴
$val['sub'] = $this->subTree($data,$val['id']);
$arr[] = $val;
}
}
return $arr;
}
}
建立控制器
php artisan make:controller Api/Admin/NodeController
php artisan make:controller Api/Admin/UserController
php artisan make:controller Api/Admin/RoleController
修改路由檔案
新增角色、節點、使用者列表
Route::post('/admin/user/login', 'AuthController@login')->name('admin.index');
Route::get('/admin/user/logout', 'AuthController@logout')->name('admin.logout');
Route::group(['prefix' => '/admin','middleware' => 'adminAuth','as' => 'admin.'], function () {
// 角色列表
Route::group(['prefix' => 'role','as' => 'role.'], function () {
Route::get('', 'RoleController@search')->name('search');
Route::get('/{id}', 'RoleController@show')->where('id', '\d+')->name('show');
Route::put('/{id}', 'RoleController@update')->where('id', '\d+')->name('update');
Route::post('/', 'RoleController@store')->name('store');
Route::delete('/{id}', 'RoleController@destroy')->where('id', '\d+')->name('destroy');
// 獲取某一角色的許可權列表
Route::get('/node/{id}', 'RoleController@nodeList')->name('node');
// 更新某一角色的許可權列表
Route::post('/node/{role}', 'RoleController@saveNode')->name('node');
});
// 節點列表
Route::group(['prefix' => 'node','as' => 'node.'], function () {
Route::get('', 'NodeController@search')->name('search');
Route::get('/{id}', 'NodeController@show')->where('id', '\d+')->name('show');
Route::put('/{id}', 'NodeController@update')->where('id', '\d+')->name('update');
Route::post('/', 'NodeController@store')->name('store');
Route::delete('/{id}', 'NodeController@destroy')->where('id', '\d+')->name('destroy');
});
// 使用者列表
Route::group(['prefix' => 'user','as' => 'user.'], function () {
Route::get('', 'UserController@search')->name('search');
Route::get('/{id}', 'UserController@show')->where('id', '\d+')->name('show');
Route::put('/{id}', 'UserController@update')->where('id', '\d+')->name('update');
Route::post('/', 'UserController@store')->name('store');
Route::delete('/{id}', 'UserController@destroy')->where('id', '\d+')->name('destroy');
});
});// end-auth
路由別名
命名規則: 格式為XXX.YYY.ZZZ
,以角色介面為例,假設路由介面為 api/admin/role/search ,則設定該路由的別名為 admin.role.search
,以此類推, admin.node.search
、admin.role.update
等
設定路由別名主要是為了許可權鑑定的時候方便處理對路由的鑑權。
擴充FormRequest驗證
使用自定義 FormRequest 類,該類整合自 Http\Request,但是針對每一種請求都要定義一個 FormRequest,比較麻煩,因此在控制器方法裡只注入一個 Request,根據模板設計模式,針對不同的場景,擴充不同的驗證規則。
- 第一步:建立AbstractRequest類
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Str;
class AbstractRequest extends FormRequest
{
public $scenes = [];
public $currentScene; //當前場景
public $autoValidate = false; //是否注入之後自動驗證
public $extendRules;
public $messages; //錯誤訊息提示
public function authorize()
{
return true;
}
/**
* 設定場景
*
* @param $scene
* @return $this
*/
public function scene($scene)
{
$this->currentScene = $scene;
return $this;
}
/**
* 使用擴充套件rule
*
* @param string $name
* @return AbstractRequest
*/
public function with($name = '')
{
if (is_array($name)) {
$this->extendRules = array_merge($this->extendRules[], array_map(function ($v) {
return Str::camel($v);
}, $name));
} else if (is_string($name)) {
$this->extendRules[] = Str::camel($name);
}
return $this;
}
/**
* 覆蓋自動驗證方法
*/
public function validateResolved()
{
if ($this->autoValidate) {
$this->handleValidate();
}
}
/**
* 驗證方法
*
* @param string $scene
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Illuminate\Validation\ValidationException
*/
public function validate($scene = '')
{
if ($scene) {
$this->currentScene = $scene;
}
$this->handleValidate();
}
/**
* 重寫返回錯誤資訊格式
*
* @param Validator $validator
*/
public function failedValidation($validator)
{
$error= $validator->errors()->all();
// $error = $validator;
throw new HttpResponseException(response()->json(['code'=>404,'message'=>$error[0]]));
}
/**
* 根據場景獲取規則
*
* @return array|mixed
*/
public function getRules()
{
$rules = $this->container->call([$this, 'rules']);
$newRules = [];
if ($this->extendRules) {
$extendRules = array_reverse($this->extendRules);
foreach ($extendRules as $extendRule) {
if (method_exists($this, "{$extendRule}Rules")) { //合併場景規則
$rules = array_merge($rules, $this->container->call(
[$this, "{$extendRule}Rules"]
));
}
}
}
if ($this->currentScene && isset($this->scenes[$this->currentScene])) {
$sceneFields = is_array($this->scenes[$this->currentScene])
? $this->scenes[$this->currentScene] : explode(',', $this->scenes[$this->currentScene]);
foreach ($sceneFields as $field) {
if (array_key_exists($field, $rules)) {
$newRules[$field] = $rules[$field];
}
}
return $newRules;
}
return $rules;
}
/**
* 覆蓋設定 自定義驗證器
*
* @param $factory
* @return mixed
*/
public function validator($factory)
{
return $factory->make(
$this->validationData(), $this->getRules(),
$this->messages, $this->attributes()
);
}
/**
* 最終驗證方法
*
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Illuminate\Validation\ValidationException
*/
protected function handleValidate()
{
if (!$this->passesAuthorization()) {
$this->failedAuthorization();
}
$instance = $this->getValidatorInstance();
if ($instance->fails()) {
$this->failedValidation($instance);
}
}
}
- 第二步:
- 建立
UserRequest
繼承AbstractRequest
class UserRequest extends AbstractRequest
{
function __construct()
{
$this->messages = $this->messages();
}
// 不同驗證場景
public $scenes = [
'login' => 'username,password',
];
/**
* 獲取已定義驗證規則的錯誤訊息
*
* @return array
*/
public function messages()
{
return [
'name.required' => '使用者名稱稱不得為空',
'name.unique' => '使用者名稱稱不得重複',
'password.required' => '密碼不得為空',
];
}
/**
* 全部的驗證規則
*
* @return array
*/
public function rules()
{
$id = $this->route('id'); //獲取當前需要排除的id,這裡的 id 是 路由 {} 中的引數
$rules = [
'password' => 'required|string'
];
// 修改節點時的驗證規則
if($id){
$rules['username'] = 'required|string|unique:users,username,'.$id;
return $rules;
}
// 新增節點時的驗證規則
$rules['username'] = 'required|string|unique:users,username';
return $rules;
}
public function loginRules(){
return [
'password' => 'required|string',
'username' => 'required|string',
];
}
}
- 建立
NodeRequest
繼承AbstractRequest
class NodeRequest extends AbstractRequest
{
function __construct()
{
$this->messages = $this->messages();
}
/**
* 獲取已定義驗證規則的錯誤訊息
*
* @return array
*/
public function messages()
{
return [
'name.required' => '節點名稱不得為空',
'name.unique' => '節點名稱不得重複',
'route_name.required' => '路由別名不得為空',
'route_name.unique' => '路由別名不得重複',
'pid.required' => '上級節點不得為空',
'pid.numeric' => '上級節點必須為數字',
'is_menu.required' => '是否是選單不得為空',
'is_menu.numeric' => '是否是選單型別必須為數字',
'is_menu.in' => '是否是選單值必須為0或1',
];
}
/**
* 全部的驗證規則
*
* @return array
*/
public function rules()
{
$id = $this->route('id'); //獲取當前需要排除的id,這裡的 id 是 路由 {} 中的引數
$rules = [
'pid' => 'required|numeric',
'is_menu' => 'required|numeric|in:0,1',
'status' => 'required|numeric|in:0,1'
];
// 修改節點時的驗證規則
if($id){
$rules['name'] = 'required|string|unique:nodes,name,'.$id;
$rules['route_name'] = 'required|string|unique:nodes,route_name,'.$id;
return $rules;
}
// 新增節點時的驗證規則
$rules['name'] = 'required|string|unique:nodes,name';
$rules['route_name'] = 'required|string|unique:nodes,route_name';
return $rules;
}
}
- 建立
RoleRequest
繼承AbstractRequest
class RoleRequest extends AbstractRequest
{
function __construct()
{
$this->messages = $this->messages();
}
/**
* 獲取已定義驗證規則的錯誤訊息
*
* @return array
*/
public function messages()
{
return [
'name.required' => '角色名稱不得為空',
'name.unique' => '角色名稱不得重複',
];
}
/**
* 全部的驗證規則
*
* @return array
*/
public function rules()
{
$id = $this->route('id'); //獲取當前需要排除的id,這裡的 id 是 路由 {} 中的引數
$rules = [];
// 修改節點時的驗證規則
if($id){
$rules['name'] = 'required|string|unique:roles,name,'.$id;
return $rules;
}
// 新增節點時的驗證規則
$rules['name'] = 'required|string|unique:roles,name';
return $rules;
}
}
至此,驗證規則全部寫完。
參考部落格:https://learnku.com/laravel/t/31215
中介軟體過濾
- 建立中介軟體
php artisan make:middleware AdminAuthenticated
在 Kernel.php
檔案裡$routeMiddleware
新增
// 後臺
'adminAuth' => \App\Http\Middleware\AdminAuthenticated::class,
- 白名單
考慮到業務本身的原因,這裡新增一個許可權白名單,在config
目錄下建立rbac.php檔案,配置使用者白名單以及路由白名單,以便後續業務的延申擴充。
<?php
return [
// 超級管理員
"super" => 'admin',
// 預設允許通過的路由
"allow_route" => [
'admin.index',
'admin.logout'
]
];
需要使用時,config('rbac.super')、config('rbac.allow_route')
讀取白名單的資訊即可
- 中介軟體過濾:
public function handle($request, Closure $next, $guard = null)
{
if (!auth()->check()){
return response()->json(['code'=>401, 'msg' => '您未登入!']);
}
$allow_node = session('admin.auth');
$auths = is_array($allow_node) ? array_filter($allow_node):[];
// 合併陣列
$auths = array_merge($auths, config('rbac.allow_route'));
// 當前訪問的路由
$currentRoute = $request->route()->getName();
$request->auths = $auths;
// 許可權判斷
if (auth()->user()->username != config('rbac.super') &&
!in_array($currentRoute, $auths)){
return response()->json(['code' => 400, 'msg' => '您沒有許可權訪問']);
}
return $next($request);
}
控制器方法
- 登入
public function login(UserRequest $request)
{
$request->scene('login') ->with('login')->validate();
$data = $request->input();
$user = User::where('username', $data['username'])->first();
if (!$user)
return $this->json_output(404, '此使用者不存在!');
if (auth()->attempt(['username' => $data['username'], 'password' => $data['password']])) {
$user->last_login_ip = $request->ip();
$user->save();
// config配置rbac白名單
if (config('rbac.super') != $data['username']){
$userModel = auth()->user();
$roleModel = $userModel->role;
$nodeArr = $roleModel->nodes()->pluck('name','nodes.id')->toArray();
// 許可權保持到session中
session(['admin.auth' => $nodeArr]);
}else{
session(['admin.auth' => true]);
}
return $this->json_output(200, '登入成功', ['user' => $user]);
}
// 登入失敗
return $this->json_output(403, '賬號或者密碼錯誤');
}
- 退出登入
public function logout()
{
auth()->logout();
return $this->json_output(200, '登出成功');
}
- 角色控制器RoleController
class RoleController extends Controller{
// 此處省略增刪查改的方法 ...
public function nodeList($id)
{
$role = Role::find($id);
// 獲取所有節點許可權
$nodeAll = (new Node)->getAllList();
$nodes = $role->nodes()->pluck('nodes.id')->toArray();
return $this->json_output(200, '許可權資訊',compact('nodeAll', 'nodes', 'role'));
}
public function saveNode(Request $request, Role $role)
{
// 關聯模型的資料同步
// sync 方法接收一個 ID 陣列以替換中間表的記錄。
// 中間表記錄中,所有未在 ID 陣列中的記錄都將會被移除。
// 所以該操作結束後,只有給出陣列的 ID 會被保留在中間表中:
$role->nodes()->sync($request->get('node'));
return $this->json_output(200, '更新許可權成功');
}
}
- 使用者控制器UserController
class UserController extends Controller
{
// 此處省略部分增刪查改的方法 ...
public function store(UserRequest $request)
{
$data = $request->input();
$request->validate();
$user = new User;
foreach ($data as $key => $value) {
if (is_null($value)) continue;
if ($key == 'password') {
// 此處加密為了跟attempt()方法對應
$user->$key = bcrypt($value);
continue;
}
$user->$key = $value;
}
$user->save();
return $this->json_output(200, '建立成功', $user);
}
}
- 節點控制器NodeController
class NodeController extends Controller{
// 此處省略增刪查改的方法 ...
}
相關文章
- 如何用 Vue 實現前端許可權控制(路由許可權 + 檢視許可權 + 請求許可權)Vue前端路由
- 許可權控制
- SpringBoot(一) 如何實現AOP的許可權控制Spring Boot
- 前端許可權控制系統的實現思路前端
- Laravel——使用者角色許可權控制包 Laravel-permissionLaravel
- shiro許可權控制
- spring aop實現簡單的許可權控制功能Spring
- Atlas 2.1.0 實踐(4)—— 許可權控制
- springcloud-gateway整合jwt+jcasbin實現許可權控制SpringGCCloudGatewayJWT
- Spring Security實現統一登入與許可權控制Spring
- springboot + shiro 實現登入認證和許可權控制Spring Boot
- Laravel中使用路由控制許可權(不限於Laravel,只是一種思想)Laravel路由
- Laravel 中使用路由控制許可權 (不限於 Laravel,只是一種思想)Laravel路由
- Linux的許可權控制Linux
- Nestjs RBAC 許可權控制管理實踐 (二)JS
- Nestjs RBAC 許可權控制管理實踐(一)JS
- Vue | 自定義指令和動態路由實現許可權控制Vue路由
- HIVE的許可權控制和超級管理員的實現Hive
- 安裝laravel許可權包Laravel
- Laravel 許可權 Policy 學習Laravel
- [WCF許可權控制]透過擴充套件自行實現服務授權套件
- Vue2-利用自定義指令實現按鈕許可權控制Vue
- Vue 前端應用實現RBAC許可權控制的一種方式Vue前端
- 基於VUE自定義指令實現按鈕級許可權控制Vue
- 許可權維持專題:域控制器許可權維持
- 使用者角色許可權控制包 Laravel-permission 使用筆記(Laravel5+)Laravel筆記
- 使用者角色許可權控制包 Laravel-permission 使用說明Laravel
- 從0實現RBAC許可權模型模型
- Vue許可權路由實現總結Vue路由
- 基於RBAC實現許可權管理
- 許可權控制及AOP日誌
- springboot-許可權控制shiro(二)Spring Boot
- etcd套路(四)auth許可權控制
- 資料分析的許可權控制
- Java 訪問許可權控制(6)Java訪問許可權
- vue-router控制路由許可權Vue路由
- Spring MVC 整合 Shiro 許可權控制SpringMVC
- Laravel 日誌有時候有許可權有時候沒有許可權?Laravel