其实 RESTful API 实现权限控制的方法很多很多, 比如在每个 Handler 中进行判断, 但是这种写法会导致工作量无限增加, 万一增加了其他的角色还要不停的更改源码, 所以要以尽量优雅的方式来实现这个部分. 比如 Middleware 的方式.

资源的分类

/zoos 算一个资源 /employees 也是一个资源

但是这些资源又有一些不一样的地方.

栗子:

GET /zoos 想看动物园的列表, 所有人都可以看, 也不需要登录. PATCH /zoos/ID 更新某个动物园的信息, 只有员工才可以更改, 必须要登录. GET /employees 想看员工列表, 只有是员工, 且还是管理员的人才能看, 必须要登录.

所以资源是有分类的:

我归为以下两类.

  1. 角色资源 (Role Resources)
  2. 公共资源 (Public Resources)

角色资源: 属于某个角色所有, 只有访问的人属于这个角色才能进行访问. 例: 人事部门 才能对 /employees 资源进行增删改查. 公共资源: 游客,工作人员, 管理人员 都可以进行操作的资源.

角色 用户 权限 权限组之间的关系

用户: 有哪些角色. 角色: 有哪些权限组 权限. 权限组: 一部分权限的集合(可有可无的一部分, 如果前端每次操作都需要一个一个的去添加权限,为何不把权限打包成一个权限组呢?) 权限: 可以控制访问的资源.

用户与角色的关系: 一对多 一个用户可以拥有多个权限. 例如:一个用户既是动物园的员工, 也是动物园的管理者. 角色与权限权限组的关系: 一对多 一个角色可以有多个权限组 权限. 例如:管理员 拥有 employees权限组 zoos部分权限. 用户与权限权限组的关系: 一对多 一个用户也可以拥有角色之外的权限权限组. (毕竟有些人就是这么特殊,不考虑不行啊!!)

表的设计

以下列出的字段只是权限控制中必须的字段, 可以在原先表结构中添加即可.

Resource

NmaeTypeDescription
Namestring资源名称
Descriptionstring资源描述
Identitystring资源唯一标识符 (一般可直接使用URL作为唯一标识符后面细讲)

Permission

NmaeTypeDescription
ResourceIDstring资源ID
Namestring权限名称
Descriptionstring权限描述
MethodstringHTTP请求方法
Effectstring作用于自己还是全部 (Allow&Owner)

PermissionGroup

NmaeTypeDescription
Namestring权限组名称
Descriptionstring权限组描述
PermissionsID[]string权限集合

Role

NmaeTypeDescription
Namestring角色名称
Descriptionstring角色描述
PermissionsID[]string权限列表
PermissionGroupsID[]string权限组列表

User

NmaeTypeDescription
RolesID[]string角色列表
PermissionsID[]string权限列表

以上所有的表都设计完了, 如果你仔细看上面表的顺序你会发现一点, 他们都是一对多的每一个 Resource 都是根.

注: Resource 一对多 Permission 一对多 PermissionGroup 一对多 Role 一对多 Role

权限中间件

negroni 为例中间件执行是有顺序的, 根据加载的先后分别执行.

权限中间件一般位于验证中间件之后, 以下的流程图是以我当前项目为例画出的流程图.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
st=>start: Start
ed=>end: End
OPmiddlewareBefore=>operation: Before Middleware
OPmiddlewareAuth=>operation: Auth Middleware
1. QueryTokenAuth
2. HeaderTokenAuth
3. JWTTokenAuth
CDauth=>condition: Logined?
OPnoLolin=>operation: Redirect /login
OPmiddlewarePermission=>operation: Permission Middleware
CDpermission=>condition: Yes or No
OPnoPermission=>operation: Return HTTP Code 401
OPmiddlewareAfter=>operation: After Middleware

st->OPmiddlewareBefore->OPmiddlewareAuth->CDauth
CDauth(no)->OPnoLolin->OPmiddlewareBefore
CDauth(yes)->OPmiddlewarePermission->CDpermission
CDpermission(no)->OPnoPermission
CDpermission(yes)->OPmiddlewareAfter->ed

以下流程图是 Permission Middleware 内部的具体流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
st=>start: Start
ed=>end: End
OPgetResource=>operation: Get Resource
conditions:
1. url
OPgetPermission=>operation: Get Permission
conditions:
1. resourceID
2. http method
OPgetPermissionGroup=>operation: Get PermissionGroup
conditions:
1. permissionID
OPgetRole=>operation: Get Role
conditions:
1. permissionID || PermissionGroupID
CDResource=>condition: Yes or No
OPnoResource=>operation: Return HTTP Code 404
CDpermission=>condition: Yes or No
OPnoPermission=>operation: Return HTTP Code 500
CDpermissionGroup=>condition: Yes or No
OPnoPermissionGroup=>operation: Return HTTP Code 500
CDrole=>condition: Yes or No
OPnoRole=>operation: Return HTTP Code 500
OPvalidUserPermission=>operation: Valid User Permission
CDvalidUserPermission=>condition: Yes or No
OPuserNoPermission=>operation: Return HTTP Code 401

st->OPgetResource->CDResource
CDResource(no)->OPnoResource
CDResource(yes)->OPgetPermission->CDpermission
CDpermission(no)->OPnoPermission
CDpermission(yes)->OPgetPermissionGroup->CDpermissionGroup
CDpermissionGroup(no)->OPnoPermissionGroup
CDpermissionGroup(yes)->OPgetRole->CDrole
CDrole(no)->OPnoRole
CDrole(yes)->OPvalidUserPermission->CDvalidUserPermission
CDvalidUserPermission(no)->OPuserNoPermission
CDvalidUserPermission(yes)->ed

资源唯一标识符

这里讲一下为什么使用以及如何使用 URL 作为唯一标识符.

这里以 /users API 为例. 分别对应他的操作有

GET /users - 获取用户列表 GET /users/{id} - 获取具体用户的信息. POST /users - 创建一个用户 PATCH /users/{id} - 更新一个用户的信息 PATCH /users/{id}/password - 更新用户密码 DELETE /users/{id} - 删除一个用户

假设现在有两个角色分别是 普通用户, 管理员

资源表其实有两个 /users, /users/

/users/ 普通用户有权限 /users 普通用户没有权限

所以 URL 作为资源唯一标识符到 第一层就可以了. 后面的可变的值如 /users/{id}id 部分并不需要考虑.

以上, 如有疑问欢迎提出, 如果大神看出了缺陷也请告知哈~~

参考