|
| 1 | +# 一个简易高效的 ACL 权限设计系统的实现思路 |
| 2 | +--- |
| 3 | + |
| 4 | +要在 laravel 上设计一个 acl 权限系统,调研了一下 Entrust 等相关权限包,发现效率太低,对于每一次 QueryPrmission、QueryRole 都需要进行连表查询,对于一个控制台菜单来说 sql 量居然上了 50+,这是不能忍受的。 |
| 5 | + |
| 6 | +干脆自己做一套简易的 user-role-permission 权限结构。 |
| 7 | + |
| 8 | +## 思路: |
| 9 | + |
| 10 | +1. 缓存用户权限 |
| 11 | +2. 批量判断权限 |
| 12 | +3. 实现权限拦截中间件 |
| 13 | + |
| 14 | +## 缓存权限 |
| 15 | + |
| 16 | +一般来说,除更改用户权限逻辑代码,其他时候用户的权限是不变的,所以可以缓存用户所有的角色和权限信息。 |
| 17 | + |
| 18 | +同时为了提供刷新用户权限的行为,添加了 flush permission 的功能。 |
| 19 | + |
| 20 | +```php |
| 21 | +function cacheUserRolesAndPermissions($user_id , $flash = false){ |
| 22 | + if ($flash){ |
| 23 | + Cache::forget('user_r_p_' . $user_id); |
| 24 | + return cacheUserRolesAndPermissions($user_id , false); |
| 25 | + }else{ |
| 26 | + return Cache::remember('user_r_p_' . $user_id , 60 ,function() use($user_id){ |
| 27 | + $res = collect(DB::table('role_user') |
| 28 | + ->where('role_user.user_id' , $user_id) |
| 29 | + ->join('roles' , 'roles.id' , '=' , 'role_user.role_id') |
| 30 | + ->join('permission_role' , 'permission_role.role_id' , '=' ,'role_user.role_id') |
| 31 | + ->join('permissions' , 'permissions.id' , '=' , 'permission_role.permission_id') |
| 32 | + ->select(['permissions.name as p_name' , 'roles.name as r_name']) |
| 33 | + ->get()); |
| 34 | + $roles = $res->pluck('r_name')->unique(); |
| 35 | + $pers = $res->pluck('p_name')->unique(); |
| 36 | + $vals = [ |
| 37 | + 'roles' => $roles->values()->all(), |
| 38 | + 'pers' => $pers->values()->all() |
| 39 | + ]; |
| 40 | + return $vals; |
| 41 | + }); |
| 42 | + } |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +## 批量判断权限的思路 |
| 47 | + |
| 48 | +对于批量判断权限时候,需要有方法批量返回判断数组。这里借鉴了 Entrust 的 getAbility 思路。 |
| 49 | + |
| 50 | +``` |
| 51 | +/** |
| 52 | + * @param $pers [] |
| 53 | + * @param $option [] valid_all 是否判断全部权限 return_type boolean/array |
| 54 | + */ |
| 55 | +function hasPermission($pers , $option = []){ |
| 56 | + $option = array_merge(['valid_all' => false , 'return_type' => 'boolean'] , $option); //return_type boolean|array|both |
| 57 | + if (!is_array($pers)) $pers = [$pers]; |
| 58 | + $gates = cacheUserRolesAndPermissions(Auth::id()); |
| 59 | +
|
| 60 | + if ($option['return_type'] == 'boolean'){ |
| 61 | + foreach ($pers as $per){ |
| 62 | + if (in_array($per , $gates['pers'])){ |
| 63 | + if (!$option['valid_all']){ |
| 64 | + return true; |
| 65 | + } |
| 66 | + }else{ |
| 67 | + if ($option['valid_all']){ |
| 68 | + return false; |
| 69 | + } |
| 70 | + } |
| 71 | + } |
| 72 | + if ($option['valid_all']) return true; |
| 73 | + else return false; |
| 74 | + }else if ($option['return_type'] == 'array'){ |
| 75 | + $res = []; |
| 76 | + foreach ($pers as $per){ |
| 77 | + $res[$per] = in_array($per , $gates['pers']); |
| 78 | + } |
| 79 | + return $res; |
| 80 | + }else{ |
| 81 | + return null; |
| 82 | + } |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +## 路由拦截中间件 |
| 87 | + |
| 88 | +批量判断权限高效实现了页面渲染时候权限判断问题,为了进一步增强系统安全性,需要对路由进行权限匹配拦截。 |
| 89 | + |
| 90 | +这里就不贴代码了,主要的意思是对 route group 内每一次的请求进行权限检查,这里需要注意有些请求含有参数,所以需要路径通配符判断,其次需要对路由方法进行检测。拦截规则类似于: |
| 91 | + |
| 92 | +``` |
| 93 | +$rules = [ |
| 94 | + '/admin/post/{id}' => ['method' => 'DELETE' , 'permission' => 'post_delete'], |
| 95 | + '/admin/post/{id}/edit' => 'post_edit', //default any http method |
| 96 | +] |
| 97 | +``` |
| 98 | + |
| 99 | +利用这三个思路实现的权限系统简洁、高效,缓存权限之后,基本不会查表即可实现批量权限判断,大大改善了之前权限系统的性能问题。 |
| 100 | + |
| 101 | +参考:https://segmentfault.com/a/1190000008447113 |
| 102 | + |
| 103 | +[Laravel中使用路由控制权限(不限于Laravel,只是一种思想)](https://segmentfault.com/a/1190000014012338) |
0 commit comments