开放网关统一认证服务

背景

由于DEF工程体系的历史原因,很多工程服务并未注册至开放网关而是私自开放接口,每个服务都维护一个client身份表,同一个client在不同开放服务间同步身份数据困难。
在使用过程中,调用方申请client流程割裂、服务认证功能后置导致每个服务提供方认证逻辑同质化、无开放接口权限管控等功能影响服务的开放安全及client接入体感,DEF开放网关统一认证服务旨在通过流程上规范client申请链路,同时在client申请时指定开放服务和对应权限接口,由网关统一认证服务实现身份认证、权限管控,并通过Oauth2授权搭配JWT机制为接入服务提供高性能认证互信方案,消除开放服务独立认证与授权壁垒,保证所有开放服务权限管控自动化。

必要性

当前开放服务,如云构建、disp、def-flow和def-work等都各自维护一套基于开放的认证和权限体系,调用方在开放平台申请的token经网关审批后,仍需要由调用方联系对应开放服务负责人提供元信息、调用权限,由开放服务负责人手动录入各自系统进行二级鉴权。
由于开放平台并未与开放服务进行数据打通,因此很难做到自动化,对用户而言申请client的体感较差,对服务管理员而言需手动添加用户元信息,体感也很差。

在运行阶段,经历了多次认证与鉴权,特别是每个开放服务侧都需实现一套简单认证系统,每次请求都需查询DB造成性能开销,无法保证开放接口的性能一致性。

因此,急需解决开放服务认证体系与开放网关认证统一的问题,并且从流程上简化客户端接入时开放服务侧的工作,最终也要保证各服务现有认证的兼容,对调用方和客户端透明。

统一认证服务方案探究
● 兼容模式,微(开放)服务仍负责各自权限
● OAuth2授权模式,由网关认证中心统一管理开放服务权限
● OAuth2授权 + JWT验证,网关认证中心授权,开放服务本地认证

探索1.兼容模式

兼容模式不改变开放服务已有的认证体系,仍然由服务自身进行认证与鉴权。但:

  1. 服务接入体验差,每个开放服务需提供接入开放网关权限体系的开放接口和补偿接口(接口由于网络抖动或其他原因请求失败,需调用补偿接口保证数据与之前一致),并保证幂等
  2. 逻辑复杂,开放网关需维护分布式事务
  3. 运行时性能瓶颈,开放服务的认证每次都查表,RT大,统一治理成本高
    兼容模式仅仅是对现状的妥协并不符合我们的诉求,且引入了其他治理成本,因此放弃。

探索2.基于OAuth2的授权认证

OAuth2有几种授权模式 — 授权码、密码、隐藏式和凭证式,常用的是授权码模式,它的前提是有界面且针对用户是人的情况;凭证模式则是针对服务的一种授权,通过提供凭证(如AK/SK)来换取token,它交互流程简单,针对服务级别,适合内部应用使用,也契合API场景。

OAuth2的角色对应
● 客户端:使用Client的二方服务
● 资源所有者:开放服务,如def-work
● 资源服务器:开放服务
● 授权服务器:开放平台

客户端接入通过BPMS流程管控,依次由网关负责人、依赖的开放服务负责人审批,流程通过后则认为当前客户端为可信客户端,由业务网关认证后授权给开放服务使用。

该方案可以做到以下保证:
● 认证与权限收敛到网关,开放服务无需自建认证体系
● 客户端接入流程化,可信应用追溯
● “权限校验模块”对开放服务透明,集成在网关插件中
● token有有效期,且可被即时撤销,安全风险可控
但是,由于存在服务权限校验模块rpc调用网关Oauth2模块获取权限信息,因此存在些许不足:

  1. “权限校验模块”需RPC网关OAuth2验证token以及权限,性能、流量压力较大
  2. 网关侧OAuth2模块单点风险,若挂则所有开放接口均不可用

那么是否有一种自签名的token,该token签发后可由客户端自行验证并获取内部相关信息,并且保证token使用期可控呢?这样就替开放平台认证中心省下来流量与db等多重压力,而且提升认证链路的性能,最终找到了JWT。

最终方案,基于OAuth2授权和JWT本地鉴权

JWT最大的缺点是签发的token无法立即撤销,需等待其超时失效。但由于我们的场景是API网关以及开放接口调用,本身是无状态场景,网关侧对每个请求都需进行一次性认证,因此token的安全失效问题便可以迎刃而解。

而优点自不必说,
● 性能优势,开放服务本地校验速度有保证
● 一次性认证,token用完即失效,有安全保证

使用体感

用户在开放平台申请client时,选择对应开放服务及重点接口时会走特殊流程审批,

对应管理员审批通过后,AK/SK即可使用


使用该AK/SK请求接口时,认证中心会对用户请求进行认证与接口鉴权,认证失败直接返回,认证通过向开放服务下发token,开放服务侧接入的网关插件会本地解析token,拿到用户信息并存储在请求上下文
ctx.defauth = {
clientName: ‘xxxx’,
clientCredential: ‘xxxx’,
adminId: ‘xxxx’
}
针对已有client表的开放服务,可自行upsert用户数据至表中即可实现兼容,无需人工输入;若服务无需存储用户信息,可直接信任该认证信息处理请求即可。