前提

在查看RBAC权限控制之前,请先阅读并理解容器、依赖注入、服务和门面章节的文档。尤其要理解容器和门面,因为在Laytp中RBAC权限控制基本上就是使用了门面和中间件来实现RBAC权限控制的。关于中间件,请自行阅读ThinkPHP6官方文档进行理解。

RBAC权限控制

本章节主要介绍Laytp框架 RBAC权限控制 的PHP部分代码。
我们暂时不要去管数据是怎么经过前端的各种交互进入到数据库的。这个在后续 框架LayUI部分说明 会进行详细介绍。我们只看关于 RBAC权限控制 牵涉到哪几张数据表,通过数据表中的数据,框架是如何进行权限控制的。

牵涉到的数据表

Laytp框架 RBAC权限控制 使用的是经典数据库设计,牵涉到六张表。

表名 说明
lt_user_token 管理员登录凭证管理
lt_admin_user 管理员管理
lt_admin_role_user 管理员与角色的关系
lt_admin_role 角色管理
lt_admin_menu_role 角色与菜单的关系
lt_admin_menu 菜单管理

牵涉到的PHP代码文件

  1. laytp
  2. ├─ app
  3. | ├─ middleware
  4. | | └─ admin
  5. | | └─ Auth.php // 后台权限控制中间件
  6. | └─ service
  7. | └─ admin
  8. | └─ AuthServiceFacade.php // 后台权限控制服务门面
  9. | └─ Auth.php // 后台权限控制服务
  10. └─ extend
  11. └─ laytp
  12. ├─ controller
  13. | └─ Backend.php // 后台控制器基类
  14. └─ library
  15. └─ Token.php // 用户登录凭证Token管理类库

代码执行流程以及详解

第一步

Http访问任何后台接口地址,都是访问后台的某个控制器的某个方法,那么必然会优先经过 后台控制器基类 的代码。既然是后台控制器基类,那么任何后台控制器都会 extend Backend 继承这个基类。

第二步

现在我们进入到后台控制器基类了,现在将关于权限控制的主要代码帖一下

  1. <?php
  2. namespace laytp\controller;
  3. /**
  4. * 后台应用admin控制器基类
  5. * @package laytp\controller
  6. */
  7. class Backend extends BaseController
  8. {
  9. use \laytp\traits\Backend;
  10. protected $noNeedLogin = [];//无需登录,也无需鉴权的方法名列表,支持*通配符定义所有方法
  11. protected $noNeedAuth = [];//需要登录,但是无需鉴权的方法名列表,支持*通配符定义所有方法
  12. ......
  13. /**
  14. * 中间件
  15. * @var array
  16. */
  17. protected $middleware = [
  18. Auth::class,
  19. ActionLog::class,
  20. ];
  21. /**
  22. * 基类初始化方法
  23. */
  24. protected function initialize()
  25. {
  26. //将无需登录的方法名数组设置到权限服务中
  27. AuthServiceFacade::setNoNeedLogin($this->noNeedLogin);
  28. //将无需鉴权的方法名数组设置到权限服务中
  29. AuthServiceFacade::setNoNeedAuth($this->noNeedAuth);
  30. $this->_initialize();
  31. }
  32. ......
  33. }

帖这些代码出来,主要是为了说明,无需登录的方法名无需鉴权的方法名 是如何在 后台控制器基类 中注入到 后台权限控制服务 中的

经过测试,ThinkPHP6的控制器中间件的运行顺序是后于控制器的初始化方法 __construct() 的。而 后台控制器基类 继承了 所有控制器基类 BaseController,在 BaseController__construct() 方法里面,优先执行了 后台控制器基类initialize() 方法。所以,在进入 后台控制器基类 代码后,先执行了它的 initialize() 方法

  1. /**
  2. * 基类初始化方法
  3. */
  4. protected function initialize()
  5. {
  6. //将无需登录的方法名数组设置到权限服务中
  7. AuthServiceFacade::setNoNeedLogin($this->noNeedLogin);
  8. //将无需鉴权的方法名数组设置到权限服务中
  9. AuthServiceFacade::setNoNeedAuth($this->noNeedAuth);
  10. $this->_initialize();
  11. }

那这里注进去了有什么用呢?
这里有一个很重要的概念就是,什么是门面。
在ThinkPHP6中,简单点理解,门面就是使用静态类的方式,单例模式来调用类的方法。
AuthServiceFacade 是一个门面,他可以使用 :: 的静态类方式,调用注入在门面中的 Auth 类的方法,并且这种调用都是单例的。也就是说,现在注入到 AuthServiceFacade 门面中的 $this->noNeedLogin$this->noNeedAuth 这两个属性值,已经存储在了 AuthServiceFacade 门面中。后面再继续使用门面的方式调用 Auth 类的方法时,他里面的值依旧存在。这里要注意的是,门面不能直接调用类的属性,需要通过类提供的方法进行调用。如果你需要详细了解什么是门面,可以查阅 容器、依赖注入、服务和门面 章节

注意点:后台控制器基类中只是定义了

  1. protected $noNeedLogin = [];//无需登录,也无需鉴权的方法名列表,支持*通配符定义所有方法
  2. protected $noNeedAuth = [];//需要登录,但是无需鉴权的方法名列表,支持*通配符定义所有方法

这两个属性,而真正对这两个属性进行赋值的地方是后台控制器文件。具体举例,你可以查阅下框架的管理员管理控制器app/controller/admin/User.php文件内容,顶部有默认的代码

  1. protected $noNeedLogin = ['login', 'logout'];
  2. protected $noNeedAuth = ['loginInfo', 'singleEdit'];

这里标识意味着,app/controller/admin/User.phplogin登录方法和logout登出方法无需用户登录即可进行访问,loginInfo获取用户登录信息和singleEdit修改个人信息两个方法仅需登录,无需鉴权就可以访问。

第三步

开始执行 后台权限中间件 代码了。后台权限中间件的代码不多,我就一次性全部贴出来

  1. <?php
  2. namespace app\middleware\admin;
  3. use app\service\admin\AuthServiceFacade;
  4. use app\service\admin\UserServiceFacade;
  5. use laytp\BaseMiddleware;
  6. use think\Request;
  7. class Auth extends BaseMiddleware
  8. {
  9. /**
  10. * 执行中间件
  11. * @param Request $request
  12. * @param \Closure $next
  13. * @return mixed
  14. */
  15. public function handle(Request $request, \Closure $next)
  16. {
  17. if (AuthServiceFacade::needLogin()) {
  18. $initUser = UserServiceFacade::init($request->header('laytp-admin-token', $request->cookie('laytpAdminToken')));
  19. if (!$initUser) return $this->error(UserServiceFacade::getError(), 10401);
  20. if (!UserServiceFacade::isLogin()) {
  21. if ($request->isAjax()) {
  22. return $this->error('登录信息已过期', 10401);
  23. } else {
  24. return redirect('/admin/login.html');
  25. }
  26. }
  27. if (AuthServiceFacade::needAuth()) {
  28. $user = UserServiceFacade::getUser();
  29. if ($user->is_super_manager !== 1) {
  30. $userId = $user->id;
  31. $plugin = defined('LT_PLUGIN') ? LT_PLUGIN : '';
  32. $controller = strtolower(str_replace("\\", ".", $request->controller()));
  33. if ($plugin) {
  34. $node = 'plugin/' . $plugin . '/' . $controller . '/' . $request->action();
  35. } else {
  36. $node = trim(app('http')->getName() . '/' . $controller . '/' . $request->action(),'/');
  37. }
  38. if (!AuthServiceFacade::hasAuth($userId, $node)) {
  39. return $this->error('无权请求:/' . $node);
  40. }
  41. }
  42. }
  43. }
  44. return $next($request);
  45. }
  46. }

后台权限中间件 做了如下几个事情:

  • 判断了当前方法是否不需要登录就可以访问
  • 如果需要登录,就先通过获取前端在 header 中传过来的 laytp-admin-token 初始化用户信息,初始化用户信息同样使用的是门面 $initUser = UserServiceFacade::init($request->header('laytp-admin-token', $request->cookie('laytpAdminToken'))); ,这样,在后续控制器中,依旧可以使用用户的门面 UserServiceFacade::getUser(); 来获取用户信息了
  • 判断了当前方式是否不需要鉴权就可以访问
  • 如果需要鉴权,那么就通过 UserServiceFacade::getUser(); 获取到当前登录者的信息,得到用户ID,然后通过用户ID去看他是什么角色,他的角色有哪些菜单权限,跟他当前访问的菜单来进行对比,看他是否有权访问这个菜单。

结语

到此为止,所有关于PHP部分的权限控制代码基本介绍完毕了。这里解决了一个问题,就是,header里携带了laytp-admin-token请求后台api时,如何被系统进行了权限控制。还有页面上的权限控制,比如页面上的添加按钮,编辑按钮,他们也需要进行权限控制,控制他们的展示、隐藏,这个部分到 框架LayUI部分进阶说明 里面进行详解。这里只初略的提一嘴。就是后台提供了一个接口,根据用户ID获取用户所有的访问权限路由,前端自行定义添加按钮权限路由,并调用方法判断用户是否有这个路由访问权限来控制按钮的隐藏和显示。

  • 评论列表0