1、服务鉴权中心背景
当前公司内部RPC调用都是没有权限控制的,只要注册中心的地址都可以拿到所有的服务。服务提供者暴露服务之后,任何人都可以来调用,这样从安全层面上来讲对于服务提供者存在一些潜在的安全风险。为此我们推出了鉴权中心,已满足不同系统之间服务调用过程中可以做到以下几点:
- 定位到服务是谁访问
- 判断调用者有没有权限访问
- 用户访问的频次是否符合要求
- 鉴权中心对业务优先级排序,当服务能力不足优先保证重点业务(服务降级)
2、功能模块设计
整个鉴权系统分为鉴权中心、服务调用者编码、服务提供者校验三大块:
2.1 鉴权中心模块设计
2.1.1 用户管理
鉴权中心用户角色有两种,一种是管理员,一种是普通用户。
管理员是通过配置文件加载,管理员与普通用户的区别在与管理员可以查看所有通过认证中心发布订阅的服务。普通用户即为认证中心的实际使用则,可以做服务的订阅、发布权限申请。用户登录流程如下:
普通登录:用户登录流程,登录成功返回用户提供服务列表。用户通过鉴权中心UI登录,登录模块与AUTH打通,如果AUTH返回登录成功,根据返回的用户ID从鉴权中心user表中查询,用户是否存在,如果存在则获取用户的provider_ids,根据provider_ids展现用户提供的服务列表。如果不存在在插入一条心记录;如果AUTH返回失败则提示用户名、密码异常。
管理员定义为系统过期用户的维护人员,可以清理过期用户,查看所有通过鉴权中心订阅发布的服务,但是不能修改服务。管理员为系统初始化进去,管理员可以再添加管理员。
2.1.2 初始服务导入
由于初始情况下注册中心上没有管理任何服务,在鉴权中心首次发布时与注册中心打通。初始化服务导入有管理员执行。从注册中心拿到目前发布的服务,更新服务的信息到t_provider表。初始化服务导入流程如下:
与初始化数据导入相关的表有1个,通过注册中心可以知道目前发布了哪些服务即填充t_provider表;
2.1.3 订阅管理
订阅管理是定位用户为服务调用的责任人,用户申请服务订阅、查看服务订阅(查看已经订阅的服务和正在审批流程中的服务,对于在审批流程中的服务可以发起催办服务)、取消订阅。订阅管理默安装更新时间展现用户订阅的服务列表。服务订阅的用例图如下:
- 申请订阅服务,服务调用者选择已经发布的服务,选取要使用服务的应用名称,点击申请订阅,在t_consumer表中生成一条订阅记录,同时在认证表也生成一条记录,申请时订阅的状态是审批中,后台生成一个审批任务将其发生到服务提供者的责任人。责任人审批通过则申请成功则返回一对秘钥对。
申请凭证的时序图如下,当鉴权中心收到用户申请后会发起审批单给服务提供者的责任人,责任人通过则将秘钥对发送给申请人;不通过会返回拒绝的理由。
在申请订阅服务是考虑与工单系统打通,审批任务包括发生工单和发生审批邮件,服务提供的责任人审批是回调生成一个秘钥对同时修改订阅的状态为已通过。
申请订阅服务流程图如下:
- 用户查看自己为责任人的音乐订阅了哪些服务,根据用户用户ID从t_user从t_consumer匹配处理owner包含的记录,然后根据凭证为订阅关系查询到服务提供者。对于状态为审批中的记录用户可以发起催办,后台再次生成审批任务防止到队列头部,审批任务会通知到服务的责任人。
- 取消服务,用户主动取消对某个服务的订阅。向认证中心发起取消请求,更新订阅状态及认证可用性的标准为,同时在注册中心修改认证服务配置以通知到服务提供者将更新凭证缓存。
- 订阅服务清理,对于状态为取消状态的订阅关系,后台通过异步的任务定期清理。
2.1.4 服务管理
服务管理是定位用户为发布服务的责任人,用户可以管理自己发布的服务。服务管理的用例图如下:
服务管理总结一下有以下功能:
- 可以查看当前发布了哪些服务
- 查看某个服务有哪些订阅者
- 修改订阅者的权重或开关
- 审批订阅服务,查看带审批列表。服务审批提供一个审批的API 的URL,可以通过工单系统或邮件调用。
注意点:修改服务需要与配置中心联动,触发服务提供者的拦截器从新从鉴权中心拉取鉴权新兵更新到本地缓存。
2.1.5 鉴权服务注册
鉴权服务默认提供两种调用方式:
第一种的dubbo协议的调用,鉴权中心会把自己作为一个鉴权服务注册到注册中心,服务调用则相当于订阅了鉴权服务。按照dubbo协议注册认证服务和普通的dubbo服务调用一致。
第二种方式提供restful api 给非dubbo协议的用户调用,restful api需要封装鉴权逻辑到sdk给业务依赖。
2.1.6 UI设计
鉴权中心的UI是基于以上模块分区,分别包括登录、用户管理、数据初始化(管理员视图)、订阅管理和服务管理。后台提供对于的API给前端调用。(UI设计原型)
2.2协议改造模块
协议改造分为两块,在服务的订阅者和服务的提供者者两块。服务订阅者需要将认证信息编码到请求头、上下文或者协议的内容里。
2.2.1 服务订阅者协议改造
当前认证版本支持hessian\http\webservice\dubbo这几种协议。
对于hessian\http\webservice的处理方式比较简单,在客户端请求的时候可以将认证信息放到请求的header里面。
dubbo协议添加凭证信息的方式:
1.修改dubbo协议
请求编码com.alibaba.dubbo.remoting.exchange.codec, dubbo协议采用固定长度的消息头(16字节)和不定长度的消息体来进行数据传输,消息头定义了一些通讯框架netty在IO线程处理时需要的信息。原生Dubbo协议如下:
- magic:类似java字节码文件里的魔数,用来判断是不是dubbo协议的数据包。魔数是常量0xdabb,用于判断报文的开始。
- flag:标志位, 一共8个地址位。低四位用来表示消息体数据用的序列化工具的类型(默认hessian),高四位中,第一位为1表示是request请求,第二位为1表示双向传输(即有返回response),第三位为1表示是心跳ping事件。
- status:状态位, 设置请求响应状态,dubbo定义了一些响应的类型。具体类型见alibaba.dubbo.remoting.exchange.Response
- invoke id:消息id, long 类型。每一个请求的唯一识别id(由于采用异步通讯的方式,用来把请求request和返回的response对应上)
- body length:消息体 body 长度, int 类型,即记录Body Content有多少个字节。
变更dubbo协议,在body content后面新增signLength,signContent,accessKeyLength,accessKey,因为协议变更在原因协议后新增几个标志位,所以理论上新协议可以兼容原有的dubbo协议(待验证),变更后的新的dubbo协议格式如下:
2.通过RpcContext传递认证相关参数(代验证)
对于dubbo协议不能简单使用RpcContext来传递认证信息,因为RPC服务的提供者有可能也正在调用其他的dubbo服务,这时RpcContext的内容会不会被重置需要验证。
2.2.2 服务发布者协议改造
服务端根据协议适配解析header内容,传递给下游鉴权Filter进行鉴权。对于hessian\http\webservice的处理方式从header取出认证相关的内容。对于dubbo协议需要根据新的解码协议解析出来认证向的信息。解码方法与编码方法对应
2.3 安全凭证签名与校验
安全凭证包括 AccessKey 和 SecretKey,其中AccessKey是用于标识 服务调用者身份的,而SecretKey是用于加密签名字符串和服务器端验证签名字符串的密钥。在第一次使用服务之前,用户需要在鉴权中心-访问管理控制台上申请安全凭证。安全凭证包括 AccessKey和 SecretKey,其中AccessKey是用于标识 服务调用者身份的,而SecretKey是用于加密签名字符串和服务器端验证签名字符串的密钥。用户应严格保管其SecretKey,避免泄露。申请安全凭证会向服务提供的责任人列表发送审批单,审批通过就好下发给用户一对AccessKey和 SecretKey。
2.3.1 签名
签名分为以下几个步骤:第一生成签名原文字符串,签名原文字符串是将请求的appname,interface, accessKey, timestamp, nonce(随机字符串)进行拼接。 timestamp, nonce为可选项,根据签名的协议规则决定。签名参数示例如下:
参数名称 |
中文 |
参数值 |
AppName |
应用名称 |
account-api |
ServiceName |
服务名称 |
AccountServiceFacade |
AccessKey |
密钥Id |
AKIDz8krbsJ5yKBZQpn74WFkmLPx3gnPhESA |
Timestamp |
当前时间戳 |
1467285768 |
Nonce |
随机字符串 |
11886 |
构建好签名原文字符串之后,首先使用 HMAC-SHA1 算法对上一步中获得的签名原文字符串进行签,即可获得最终的签名串。最终的签名字符串及为signStr。生成示例如下:
$accessKey = 'Gu5t9xGARNpq86cd98joQYCN3Cozk1qA';
$srcStr = 'appName=account-api&ServiceName=AccountServiceFacade&Nonce=11886&Timestamp=1467285768&SecretId=AKIDz8krbsJ5yKBZQpn74WFkmLPx3gnPhESA';
$signStr =hash_hmac('sha1', $srcStr, $secretKey, true);
echo $signStr;
常用的签名签名算法对比,经过对比性能与安全性之后选择适合SHA-1
签名加密:SHA-1签名只能防止篡改,该签名已经证实是可以破解的。如果需要有更加安全的考虑,需要对签名的数据进行加密。签名加密流程如下:
考虑到增加认证不能对原有服务调用有过多的性能损耗,同时考虑到网络环境不可靠,建议选择折中的签名认证方案。签名加密只适合部分对数据安全性要求极高的业务。(本期暂时不考虑)
2.3.2 签名校验
1.根据服务参数AccessKey查询加密字符串SecretKey,同时根据AccessKey也能定位到服务是被谁调用。
2.请求参数生成签名原文字符串
3.使用SecretKey对请求原文字符串再生成一次singStr, singStr生成方式与客户端相同
4.比对生成的singStr与参数singStr是否一致,不一致则拒绝服务。(判断是否有权限调用)
5.如果singStr一致,判断用户的调用频次是否满足要求,不满足则返回对应的错误码。(控制访问频次)
3、接口设计
3.1 接口安全性
认证中心暴露给第三方系统的接口都需要携带用户的token信息,认证中心与AUTH打通,通过token信息验证用户的身份。
工单系统接口示例:
http://10.101.18.127:8080/swagger-ui.html#!/process45instance45controller/startProcessInstancesUsingPOST
启动参数
definitionKey 流程定义key 我们定义好流程后生成
formProperties 提交表单的参数信息map对象 键值都为String
initiator提交表单用户员工账号
name 流程名字
返回值为
{"code":0,"msg":null,"data":{"processInstanceId":"995915"}}
processInstanceId为启动流程id
3.2 外部接口
目前暴露给外部系统的有一个接口即服务申请的审批接口,服务提供者责任人可以在工单系统中回调审批接口对服务调用者的申请进行审批。认证中心传递appname和provider_id给工单系统,工单系统根据appname找到责任人发送工单,回调认证中心接口是需要将provider_id带上。
3.2.1 接口说明
工单系统回调审批接口,对用户申请进行审批,接口示例如下:
http://127.0.0.1:8080/aucenter/v1/approve?certificateId=123&isApproved=true&msg=ok&token=DSI3UqqD66bUJb4tbGxZpRW74
接口返回信息为JSON格式,返回结果包括:
{
resultCode:0,
msg:””
}
resultCode为0代表审批正常,非0代表审批失败,服务端会将异常信息放置与msg里。当工单审批通过或在t_certificate更新审批人,同时申请秘钥对,将available置为true
4、数据库设计
认证中心元数据信息存储在MYSQL,中,目前与数据相关的有两个表。t_provider记录从注册中心拉取的服务信息,t_certificate记录服务订阅的信息。
CREATE TABLE IF NOT EXISTS `authcenter`.`t_certificate` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`provider_id` bigint(20) NULL DEFAULT NULL,
`app_name` varchar(255) NULL DEFAULT NULL COMMENT '调用者的应用名称',
`access_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`secure_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`proposer` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`approver` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`available` tinyint(4) NULL DEFAULT NULL,
`weight` int(11) NULL DEFAULT 5,
`create_time` datetime NULL DEFAULT NULL,
`modify_time` datetime NULL DEFAULT NULL,
PRIMARY KEY (`id`) ,
UNIQUE INDEX `access_key` (`access_key` ASC) USING BTREE
)
ENGINE = InnoDB
AVG_ROW_LENGTH = 0
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci
KEY_BLOCK_SIZE = 0
MAX_ROWS = 0
MIN_ROWS = 0
ROW_FORMAT = Compact;
CREATE TABLE `authcenter`.`t_provider` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`app_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`interface` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`owners` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`version` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`cluster_id` bigint(20) NULL DEFAULT NULL,
`available` tinyint(4) NULL DEFAULT NULL,
`create_time` timestamp(0) NULL DEFAULT NULL,
`modify_time` timestamp(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `appname_interface`(`app_name`, `interface`) USING BTREE,
INDEX `app_name`(`app_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Compact;
CREATE TABLE `authcenter`.`t_cluster` (
`id` bigint(20) NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '集群名称',
`zk_quorum` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`create_time` timestamp(0) NULL DEFAULT NULL,
`modify_time` timestamp(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `name`(`name`) USING BTREE,
UNIQUE INDEX `zk_quorum`(`zk_quorum`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
5、系统性能设计
性能目标:鉴权安全凭证的加解密算法对业务的资源消耗影响小于5%。签名生成逻辑在服务调用者实现,目前SHA-1算法,鉴权校验逻辑在服务调用方通过拦截器实现。
服务提供者相当于鉴权中心的客户端,默认情况下服务提供者会有本地缓存,只有在凭证更新才会客户端更新缓存。
6、系统稳定性设计
- dubbo鉴权,鉴权中心通过注册中心注册鉴权服务,当一个鉴权中心节点宕机会通知客户端更新鉴权服务列表,对鉴权可用性没有影响。
- restful鉴权,当鉴权sdk监听到鉴权服务节点变化通知更新本地可以鉴权列表。对于鉴权失败的服务节点添加到dead鉴权节点列表,并将请求重试到alive节点。所有当某个鉴权节点宕机对服务可用性没有影响。
鉴权中心部署的示意图如下:
7、系统异常处理
- 服务调用方鉴权异常:返回用户鉴权异常信息和错误码,比如没有申请服务访问权限。将错误信息通过日志输出。
- 鉴权中心内部接口异常,内部接口返回给UI的接口都是JSON格式,示例如下:
{“resultCode”:0,data:””,msg:””},当resultCode非0是UI需要提示错误信息。
- 鉴权中心需要与AUTH系统、CMDB系统、工单系统打通,在调用第三方系统过程中如果失败需要在日志里面输入返回的异常信息,默认情况下鉴权系统会重试调用,重试失败触发告警逻辑。