🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 一、RBAC 参考链接:([https://blog.csdn.net/a549654065/article/details/108883147](https://blog.csdn.net/a549654065/article/details/108883147)) RBAC: role base access control 基于角色的用户[访问权限](https://so.csdn.net/so/search?q=%E8%AE%BF%E9%97%AE%E6%9D%83%E9%99%90&spm=1001.2101.3001.7020)控制权限,就是权限分配给角色,角色又分配给用户。 即**一个用户对应一个角色,一个角色对应多个权限**,一个用户对应用户组,一个用户组对应多个权限。 ![](https://img.kancloud.cn/f8/1c/f81c2845bf279cfe9093345fa35d999f_576x209.png) ## 二、认证授权逻辑 登录逻辑: ![](https://img.kancloud.cn/12/10/1210d1e7e8b94aa3fdd84ca50100e782_443x612.png) 权限控制逻辑:![](https://img.kancloud.cn/5e/5b/5e5bf11b13a695d947b57bcc99f70ad8_593x443.png) ## 三、具体实现 ### 创建表 ``` CREATE TABLE `users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `role_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '角色ID', `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '账号', `truename` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '未知' COMMENT '账号', `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码', `email` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱', `phone` varchar(15) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '手机号码', `photo` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像', `sex` enum('先生','女士') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '先生' COMMENT '性别', `last_ip` char(15) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '登录IP', `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE `roles` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称', `tag` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色标识', `status` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '0未引用,1已引用', `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE `nodes` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '节点名称', `route_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '路由别名,权限认证标识', `route_tag` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `pid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '上级ID', `is_menu` enum('0','1') COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '是否是菜单', `sort` int(11) DEFAULT NULL COMMENT '排序', `status` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '0未引用,1已引用', `deleted_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE `role_nodes` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `role_id` int(11) DEFAULT NULL COMMENT '角色', `node_id` int(11) DEFAULT NULL COMMENT '节点', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ``` Model文件 ``` 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; } } ``` ### 修改路由文件 新增角色、节点、用户列表 ``` 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等 设置路由别名主要是为了权限鉴定的时候方便处理对路由的鉴权。 ### 中间件过滤 创建中间件 ``` 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' ] ]; ``` 中间件过滤: ``` //第一种 public function handle($request, Closure $next, $guard = null) { if (!auth()->check()){ return response()->json(['code'=>401, 'msg' => '您未登录!']); } $allow_node = session('admin.auth.'.Auth::id()); $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 handle($request, Closure $next)     { $role\_id\=getRole(); //获取角色Id,公共方法 $info\=Role::with('nodes')->where('id',$role\_id)->first(); $info\=$info ? $info\->toArray() : \[\]; //当前角色的节点{ if(!empty($info)){ $nodeInfo\=$info\['nodes'\]; foreach($nodeInfo as $key\=>$val){ if($val\['is\_menu'\]==1){ $urls\[\]=$val\['route\_name'\];                 }             }         } // 合并数组 $auths = array\_merge($urls, config('rbac.allow\_route')); //dd( $auths); // 当前访问的路由 $currentRoute ='/'.request()->path(); //dd( $auths); // 权限判断 if ($info\['tag'\] != config('rbac.super') && !in\_array($currentRoute, $auths)){ //return view('admin.error'); // return JsonReturnError('400','您没有权限访问',''); return response()->json(\[ 'code' => 1003, 'message' => 'No Promission' , //token已过期 'data'\=>''             \]);         } return $next($request);     } ```