0%

业务建模六脉神剑之认证授权

名词术语

  • 资源(Resource:受保护的数据资产或系统功能。例如:某数据报表,禁用某账号等。有时候资源又称受保护资源Protected Resource)。

  • 权限(Permission:对资源的各种操作。按RESTful API来说,GET /employeesPUT /employees/:id都包含了资源和操作,它们可以理解为一个权限。

  • 主体(Subject:权限赋予的对象。也就是通常大家说的 用户User)。

  • 授权(Authorization:将权限Permission)赋予主体Subject)和权限鉴定的过程。Authorization常缩写成Authz

  • 认证(Authentication: 确认主体Subject)身份的过程。Authentication常缩写称Authc

  • 角色(Role权限的集合(不是用户的集合,尽管它常常会授权给一组用户)。当我们需要把 一组 权限赋予某个主体时,同时很多主体都需要相同的一组权限,我们可以把这组权限定义为一个集合,这个集合叫做角色,它是一种简化授权的手段。

  • RBAC:表示Role-Based Access Control,中文基于角色的访问控制

需求界定

  • 支持多租户:支持企业内部ERP账号体系下,不同子系统的,权限管理。
  • 不支持多主体体系:暂不支持脱离企业内部ERP账号体系外的权限管理。

概要设计

多租户设计

上图有三个模块,分别是:

  • auth-center: 权限中心。它一方面接收管理员的指令,另一方面以API的形式对外提供权限判断逻辑,伪代码样例是isPermitted(User, App.Permission | App.URL)

  • App Admin: 应用管理员。首先需要说明的是这里的App不是ios移动端的应用,而是一种应用场景的意思。权限中心会给各个子系统使用,每个子系统之间是相互 隔离 的。即:各个子系统是独立定义自己空间内的权限和角色。比如财务系统可以定义管理员角色,CRM系统也可以定义自己的管理员角色,两者互不影响。

  • App Client: 确切说是auth Client running in App。当App(就是前文将的Protected Resource)收到一个访问请求时,它需要询问auth-center是否有权限。

三模块之间的交互过程:

  1. 创建App: 应用管理员接入权限中心前,需要登录权限中心的Web控制台,创建一个App。比如财务系统申请一个叫财务App,CRM系统申请一个叫CRM-App的应用。
  2. 定义权限: 权限以资源及其操作的形式来定义。形如:Orders:Create表示创建订单,Orders:Update表示更新订单。尽管系统不限制权限命名规范,但是推荐按RESTful的思想命名。有时候某用户虽然允许修改订单,但是只允许修改某些订单,我们用形如Orders:Update:123456表示只能更新订单号为123456的订单。
  3. 定义角色: 创建角色名称,加入若干权限。比如:定义一个订单管理员角色,里面包含Order:CreateOrder:Update权限。
  4. 角色授权: 将某人赋予某些角色。一个人可以授予多个角色。当一个人被授予多个角色时,也就是多个权限集合,那么这个用户拥有的最终权限集是这几个集合的并集。当我们合并两个权限集的时候,会不会存在语义冲突呢?比如角色A表示允许做事情X,角色B表示禁止做事情X,那么合并的时候还需要一个冲突解决机制,比如谁先命中,谁胜出。当然我们也可以设计成,只含赋予权限,不含禁止权限的,这样就可以简单的求并集。
  5. 用户鉴权: 当App(就是前文将的Protected Resource)收到一个访问请求时,它需要询问auth-center是否有权限。询问有两种可能的方式:
    • 权限级isPermitted(User, App.Permission),直接询问用户是否有某个权限。
    • URL级isPermitted(User, App.URL),前提是应用管理员提前在Web控制台关联了权限与URL的关系,同时应用层协议是HTTP(而不是RPC)。比如Orders:Update权限关联PUT /App/OrdersGET /App/Orders/Update(一个权限可能有多个URL形式)。

多重表现形式

URLApp.Permission的映射关系,可以交给App的开发人员维护,也可以在Web控制台预关联。前者的好处,有两点:

  • 方便灵活: 因为程序员最清楚当前的URL(或说SpringMVC Controller),需要什么权限。以及如果没有权限,该返回什么。
  • 解耦HTTP: 不依赖于HTTP协议,可以用于RPC等自定义协议。

不足之处是:管理员无法知道权限背后表征的具体地址,比如URL地址或者RPC的地址。

鉴权触发模式

鉴权的触发点大体有两种模式。一种是要求接入方主动询问权限中心,为减少接入方的开发,权限中心可以提供SDK;另一种是以反向代理的方式拦截接入方的请求。前者叫SDK模式,后者叫Proxy模式

  • SDK模式

流程说明:

  1. 客户端(例如Web浏览器或RCP Client)尝试访问Protected Resource(例如财务系统或CRM系统等)。
  2. Auth-SDK调用SSO-SDK(公司的ERP统一认证的SDK),提取当前登陆者信息(ERP账号)。
  3. Auth-SDK询问权限中心,咨询某用户,是否拥有某权限,伪代码形如isPermitted(User, App.Permission)。对于HTTP协议的,并且预先在Web控制台将URL关联了权限的,可以直接询问URL级的授权,伪代码形如isPermitted(User, App.URL)。需要提醒的是,无论哪种级别的鉴权,请求时都必须携带App信息,因为权限中心是多租户的。
  4. 权限中心回复yes|no。对于HTTP协议,只通过HTTP Status和HEADER返回信息,不用包体信息。对于RPC协议,按RPC要求走。这里假设内网通信是安全的,不考虑权限中心的相应被篡改和欺骗的情况。
  • Proxy模式

注意

proxy模式的前提必须走HTTP协议:Protected Resource对外服务是以HTTP形式提供的,不包含公司内部RPC框架形式。主要原因是RPC缺乏反向代理基础设施(RPC协议未分层)。

流程说明:

  1. 客户端(例如Web浏览器或HTTP Client)尝试访问Protected Resource(例如财务系统或CRM系统等)。发往Protected Resource的请求,被权限中心拦截。这里的拦截可能是要求客户端修改Protected Resource的URL地址,改成权限中心,并在权限中心针对该URL配置backend servers,以便转发其你去。也可能修改内部DNS,把IP解析到权限中心或者类似CDN那样,把Protected Resource的域名CNAME到权限中心。这里需要看下公司的基础设施的情况。
  2. Auth-SDK调用SSO-SDK(公司的ERP统一认证的SDK),提取当前登陆者信息(ERP账号)。
  3. 依据URL询问是否被授权,伪代码形如isPermitted(User, App.URL)注意: 这种情况,需要预关联URL与权限。
  4. 如果确认有权限,则转发请求到Protected Resource;否则,直接返回 403 ,表示权限认证失败。这里通常不允许让Protected Resource在包体报文(比如JSON)中表征鉴权失败,因为这样不符合HTTP分层标准。但是,对于已存在的老系统,或已经滥用了在包体中表征鉴权失败,如果迁移到权限中心,依然需要转发请求给Protected Resource,并在转发请求的Header里面表明是否有权限,Protected Resource依据Header状态指示,生成HTTP Response Body。这个行为,需要在App上做配置说明。

形式化语法

对上面的描述,我们用一个形式化的方式来举例说明下:

  • 权限配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[users]
; granting syntax
;userName=password,role1,role2

zhangsan = yYMWOIx3, master
lisi = LS1234, developer
wanger = 123456, guest

[roles]
; role definition syntax
;roleName=Resource:Operation:Instance,Resource:Operation:Instance

master = *
developer = orders:view,orders:update
guest = orders:view

注意:每个App都有单独的一个配置,以相互隔离。

  • 权限判定

鉴权时,一个伪代码描述,体现程序员直接管理URL与权限的映射关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Path("/Orders")
public class OrderController {

@GET
@Path("/view")
public List<Order> view() {
if (! authzClient.isPermitted(SsoContext.getUser(), "orders:view")) {
// 没权限时,处理逻辑
}
}

@PUT
@Path("/update")
public String update() {
if (! authzClient.isPermitted(SsoContext.getUser(), "orders:update")) {
// 没权限时,处理逻辑
}
}

}

其中:authzClient表示authz-sdkSsoContext.getuser()表示sso-sdk。顺便说一下,上述只是伪代码,开发者完全可以把鉴权逻辑做成AOP拦截器。注意:authzClient对象初始化的时候,携带了App信息。

权限粒度定义的要点

权限是一个很抽象的概念,它的粒度定义十分讲究。回顾下前面讨论到的要点:

  1. 多角色权限融合: 当一个用户被授予多个角色时,每个角色对应一个权限集合,那么这个用户就拥有多个权限集合。一种融合机制是简单的求并集;另一种是如果有语义冲突,采用什么冲突仲裁机制。
  2. 多重表现形式: 权限是一个抽象的概念,它表征的是资源及对资源的标准操作。然而现代多渠道应用场景,往往导致资源的内容与形式产生了分离。比如Order:Create是订单创建权限,如果在PC Web渠道提供服务,它的表现形式则是POST http://example.org/Order/Create.html。在移动端渠道提供服务,它的表现形式则是POST http://example.org/Order/Create.json;在内部微服务提供服务,它的表现形式则是基于RPC的Java类,比如org.example.Order.create()远程调用方法。它们有多重表现形式,然而它们的内容语义都是相同的,都是订单创建。我们可能会想着把它们统一抽象为Order:Create,但是我们有没有思考过,渠道本身可能做细分控制呢?比如我们对某某客户,只能通过移动端渠道创建订单呢?
  3. 数据下钻细分管控: 在复杂的业务场景里,资源及对资源的标准操作对权限的刻画粒度依然太粗了。比如Order:View表示订单信息查询权限。但是订单信息是高敏信息,通常会进一步细分管控,比如只能看自己部门的订单数据,不能跨部门看数据。也就是说,被授权用户,可以看Order:View:123456订单,但是不能看Order:View:654321订单。

分层架构设计

架构设计上,我们把认证授权划分了3个层次:

unified-authz-arc

  • 应用场景层:支持多账户体系,多应用场景。请注意,它这里定位是通用的认证授权,不仅支持不同的应用,而且支持不同的账号体系。
  • RBAC层: 基于角色的访问控制层,它的核心是抽象了Subject用户、Permission权限、Role角色等概念。当一个用户被授予多个角色时,提供权限融合的计算和查询机制。
  • 协议关联层: 它将权限进行了抽象,把内容与形式进行了分离。比如它既可以支持HTTP协议,还支持RPC协议,HTTP协议内部进一步支持页面和JSON等。

概括说,它定位的是认证授权的SaaS平台,支持多账号、多应用和多渠道/协议模式。做这样的事情,也能做出一家上市公司 OKTA

okta