37. 权限系统(下)

在权限系统的上篇中详细介绍了入站权限检查,在下篇中将介绍业务逻辑权限检查,也就是在管理后台中看到的账户、权限、角色以及涉及管理业务逻辑的权限检查等内容,让我们从这里开始:

账户、角色、权限:

Drupal的权限系统采用了基于角色的访问控制RBAC(Role-Based Access Control),许多现代系统均采用RBAC,它相较于传统的访问控制,就犹如oop相较于过程式编程带来的提升一样,为大型系统的设计简化了很多的事情,读者可以搜索一下RBAC了解更多内容,其实对权限系统的研究是一个可以很深入的话题,drupal实现了基础的账户、角色、权限概念,提供了一个通用基础,这通常已经够用了,面对复杂情况我们可以在此基础上搭建出复杂的权限系统,比如涉及权限继承、动态权限等问题,这里介绍基本概念:

权限:面对某操作能与不能的属性,在代码层面一个权限用一个id来代表,该id是一个全局唯一的字符串。

角色:是一种身份表示,在代码层面是一些权限的合集

账户:一个具体的使用者,账户和角色是多对多的关系,也就是说一个账户可以被分配很多角色(拥有很多身份),一个角色也可以有许多账户(某种身份可以有许多个人),就犹如一个公司的组织架构:一个人可以同时是客服和网站编辑,客服岗位可以有许多个人,这里人就是账户,职位就是角色。

特殊角色与账户:

在drupal中有两类特殊角色:

未登录的用户(匿名游客)属于匿名角色,在游客访问网站时系统也会建立账户对象,该账户对象属于匿名角色,账户id为0,匿名角色是不能被用于账户分配的(也就是说匿名角色和其他角色是互斥的,一个账号不能同时拥有匿名角色和其他某角色),但可以为它分配权限以控制游客的默认权限。

系统安装初始化后会建立管理员角色,该角色拥有所有权限,其实在系统内部只要是具备管理员角色,根本就不会去执行权限检查逻辑,直接返回允许,该角色不能进行权限分配,但可以进行账户分配,因此系统可以有多个超级管理员,也称为维护账号,一旦分配这个角色后,就没有必要分配其他角色了,在实际操作中由于这个角色权限太大建议不要用这样的账号去登录避免安全风险,平时用时使用专门的账号,即使被盗号,破坏也是有限的。

除以上两个特殊角色外,所有登录的账号都属于已登录角色,自动分配且不可取消,使用该角色可以为已登录用户配置基本的权限,它和匿名角色、管理员角色是系统安装后默认建立的三大角色。

通过权限来控制能否访问某页面:

也可以说是通过权限来判断能否进入某个路由,这是在路由定义里面设置权限检查器来实现的(见上篇):

用户权限检查器access_check.permission:

检查标识: _permission,方法:access,无需请求对象

类:Drupal\user\Access\PermissionAccessCheck

检查标识的值代表用户权限,账户对象满足即可进入,反之拒绝,多个权限使用分隔符分割,分隔符“+”表示OR关系,“,”表示AND关系,这两种分隔符不可一起使用,复杂判断可以使用自定义回调检查器来做。

OR和AND不能一起使用为什么呢?权限又是怎么来的呢?如何判断用户是否具备某权限?带着这些问题来看看drupal是怎么实现的。

用户权限判断:

判断用户是否具备某权限是从用户账户对象中得知的,默认账户对象为:

Drupal\Core\Session\UserSession

在上篇中已经说明了它是如何初始化,以及如何在系统中获取,实例化账户对象是在:

Drupal\user\Authentication\Provider\Cookie的getUserFromSession(SessionInterface $session)方法中

可以看出使用了两张数据库表:

users_field_data:保存用户的基本资料

user__roles:保存用户的角色资料,同一个用户有多少个角色那么在该表中就会有多少条记录(匿名角色和已注册角色不会存放),当用户存在且没有被禁用的情况下,会查询该表,将角色信息合并到基本资料数组中,一起传给账户对象,这里需要注意虽然有些字段在账户对象中没有属性声明,但依然会被注入,比如:

$user = \Drupal::currentUser()->getAccount();

echo $user->pass;

来看一看这两个表的设计:

users_field_data表:

uid:用户id,长度为10的整型,可以看出能注册十亿级别数量的用户

langcode:语言代码,比如中文为zh-hans,英文为en

preferred_langcode:首选语言的语言代码

preferred_admin_langcode:管理界面中首选语言的语言代码

name:用户名,长度为60英文字符,为账户名,而不是在页面中显示的昵称

pass:加盐处理的密码hash,长度可以为255英文字符

mail:用户邮件

timezone:用户时区标识符

status:用户状态,用以表示用户是否被封锁禁用,1为正常,0为禁用

created:账户创建时间,unix时间戳格式

changed:账户修改时间

access:账户最后一次访问的时间

login:用户最后登录的时间

init:创建账号时使用的用户邮件

default_langcode:默认语言代码

用户在管理后台添加的账户扩展字段不会存放在该表中,而是每个字段都独立产生一张数据库表

user__roles表:

bundle:数据属于哪个模块

deleted:表明数据是否已经被删除,bool值

entity_id:数据所属的实体id,也就是uid

revision_id:数据所属实体版本,对于用户账户实体来说不存在实体版本,它和实体id一样

langcode:语言代码

delta:多值时排序使用

roles_target_id:目标实体的id,角色其实是一个实体

你可能已经意识到了角色所具备的权限信息并没有存放在这两表中,实际上它由配置系统和实体系统来负责储存和提供使用接口,由于这两个系统涉及内容较多,在系统中处于很重要的位置,不仅使用在权限系统中,还使用在别的地方,所以本系列将分别单独讲解,这里暂时仅提供使用的接口,不必深入了解具体细节。

在用户账户对象中有两个和权限相关的方法,是权限系统提供的开发接口,可以供我们来进行权限判断:

获取用户具备的角色:

public function getRoles($exclude_locked_roles = FALSE)

在控制器中的使用方法:\Drupal::currentUser()->getRoles($exclude_locked_roles = FALSE);

得到用户所属的角色,返回一个数组,系统有两个特殊角色:匿名角色、已注册用户角色,参数为true时排除这两个特殊角色

判断用户是否具备某权限:

public function hasPermission($permission)

在控制器中的使用方法:\Drupal::currentUser()->hasPermission($permission);

返回bool,表明用户是否有某权限,uid为1的用户账户为系统维护账户,在系统安装时设置,它拥有所有权限

思考:

当某账户同时具备两个角色,但角色定义的权限相冲突,系统怎么处理?比如针对某权限一个角色允许,另外一个不允许。

这只是个权限有无的问题,有权限就允许,反之不允许,只要具备的角色中有一个角色有权限,结果就有权限,在此方法中可以看出来:Drupal\user\RoleStorage:: isPermissionInRoles($permission, array $rids).

本篇就介绍到这里,待到配置系统和实体系统介绍完成,将对所有细节全然了解。

API官方参考:

https://api.drupal.org/api/drupal/core%21core.api.php/group/user_api/8.3.x

本书共94小节:

评论 (写第一个评论)