18 安全体系:如何建立可靠的安全体系?
你好,我是何小锋。上一讲我们学习了在RPC里面该如何提升单机资源的利用率,你要记住的关键点就一个,那就是“异步化”。调用方利用异步化机制实现并行调用多个服务,以缩短整个调用时间;而服务提供方则可以利用异步化把业务逻辑放到自定义线程池里面去执行,以提升单机的OPS。
回顾完上一讲的重点,我们就切入今天的主题,一起来看看RPC里面的安全问题。
为什么需要考虑安全问题?
说起安全问题,你可能会想到像SQL注入、XSS攻击等恶意攻击行为,还有就是相对更广义的安全,像网络安全、信息安全等,那在RPC里面我们说的安全一般指什么呢?
我们知道RPC是解决应用间互相通信的框架,而应用之间的远程调用过程一般不会暴露在公网,换句话讲就是说RPC一般用于解决内部应用之间的通信,而这个“内部”是指应用都部署在同一个大局域网内。相对于公网环境,局域网的隔离性更好,也就相对更安全,所以在RPC里面我们很少考虑像数据包篡改、请求伪造等恶意行为。
那在RPC里面我们应该关心什么样的安全问题呢?要搞清楚这个问题,我们可以先看一个完整的RPC应用流程。
我们一般是先由服务提供方定义好一个接口,并把这个接口的Jar包发布到私服上去,然后在项目中去实现这个接口,最后通过RPC提供的API把这个接口和其对应的实现类完成对外暴露,如果是Spring应用的话直接定义成一个Bean就好了。到这儿,服务提供方就完成了一个接口的对外发布了。
对于服务调用方来说就更简单了,只要拿到刚才上传到私服上的Jar的坐标,就可以把发布到私服的Jar引入到项目中来,然后借助RPC提供的动态代理功能,服务调用方直接就可以在项目完成RPC调用了。
这里面其实存在一个安全隐患问题,因为私服上所有的Jar坐标我们所有人都可以看到,只要拿到了Jar的坐标,我们就可以把发布到私服的Jar引入到项目中完成RPC调用了吗?
理论上确实是这样,当然我相信在公司内部这种不向服务提供方咨询就直接调用的行为很少发生,而且一般真实业务的接口出入参数都不会太简单,这样不经过咨询只靠调用方自己猜测完成调用的工作效率实在太低了。
虽然这种靠猜测调用的概率很小,但是当调用方在其它新业务场景里面要用之前项目中使用过的接口,就很有可能真的不跟服务提供方打招呼就直接调用了。这种行为对于服务提供方来说就很危险了,因为接入了新的调用方就意味着承担的调用量会变大,有时候很有可能新增加的调用量会成为压倒服务提供方的“最后一根稻草”,从而导致服务提供方无法正常提供服务,关键是服务提供方还不知道是被谁给压倒的。
当然你可能会说,这是一个流程问题,我们只要在公司内部规范好调用流程,就可以避免这种问题发生了。
确实是这样,我们可以通过流程宣贯让我们所有的研发人员达成一个“君子约定”,就是在应用里面每次要用一个接口的时候必须先向服务提供方进行报备,这样确实能在很大程度上避免这种情况的发生。但就RPC本身来说,我们是不是可以提供某种功能来解决这种问题呢?毕竟对于人数众多的团队来说,光靠口头约定的流程并不能彻底杜绝这类问题,依然存在隐患,且不可控。
调用方之间的安全保证
那在RPC里面,我们该怎么解决这种问题呢?
我们先总结下刚才的问题,根本原因就是服务提供方收到请求后,不知道这次请求是哪个调用方发起的,没法判断这次请求是属于之前打过招呼的调用方还是没有打过招呼的调用方,所以也就没法选择拒绝这次请求还是继续执行。
问题说明白了就好解决了,我们只需要给每个调用方设定一个唯一的身份,每个调用方在调用之前都先来服务提供方这登记下身份,只有登记过的调用方才能继续放行,没有登记过的调用方一律拒绝。
这就好比我们平时坐火车,我们拿着身份证去购买火车票,买票成功就类似服务调用方去服务提供方这儿进行登记。当你进站准备上火车的时候,你必须同时出示你的身份证和火车票,这两个就是代表你能上这趟火车的“唯一身份”,只有验证了身份,负责检票的工作人员才会让你上车,否则会直接拒绝你乘车。
现在方案有了,那在RPC里面我们该怎么实现呢?
首先我们要有一个可以供调用方进行调用接口登记的地方,我们姑且称这个地方为“授权平台”,调用方可以在授权平台上申请自己应用里面要调用的接口,而服务提供方则可以在授权平台上进行审批,只有服务提供方审批后调用方才能调用。但这只是解决了调用数据收集的问题,并没有完成真正的授权认证功能,缺少一个检票的环节。
既然有了刚搭建的授权平台,而且接口的授权数据也在这个平台上,我们自然就很容易想到是不是可以把这个检票的环节放到这个授权平台上呢?调用方每次发起业务请求的时候先去发一条认证请求到授权平台上,就说:“哥们儿,我能调用这个接口吗?”只有授权平台返回“没问题”后才继续把业务请求发送到服务提供方那去。整个流程如下图所示:
从使用功能的角度来说,目前这种设计是没有问题的,而且整个认证过程对RPC使用者来说也是透明的。但有一个问题就是这个授权平台承担了公司内所有RPC请求的次数总和,当公司内部RPC使用程度高了之后,这个授权平台就会成为一个瓶颈点,而且必须保证超高可用,一旦这个授权平台出现问题,影响的可就是全公司的RPC请求了。
可能你会说我们可以改进下,我们是不是不需要把这个认证的逻辑放到业务请求过程中,而是可以把这个认证过程挪到初始化过程中呢?这样确实可以在很大程度上减少授权平台的压力,但本质并没有发生变化,还是一个集中式的授权平台。
我们可以想一个更优雅一点的方案。
其实调用方能不能调用相关接口,是由服务提供方说了算,我服务提供方认为你是可以的,你就肯定能调,那我们是不是就可以把这个检票过程放到服务提供方里面呢?在调用方启动初始化接口的时候,带上授权平台上颁发的身份去服务提供方认证下,当认证通过后就认为这个接口可以调用。
现在新的问题又来了,服务提供方验票的时候对照的数据来自哪儿,我总不能又去请求授权平台吧?否则就又会遇到和前面方案一样的问题。
你还记得我们加密算法里面有一种叫做不可逆加密算法吗?HMAC就是其中一种具体实现。服务提供方应用里面放一个用于HMAC签名的私钥,在授权平台上用这个私钥为申请调用的调用方应用进行签名,这个签名生成的串就变成了调用方唯一的身份。服务提供方在收到调用方的授权请求之后,我们只要需要验证下这个签名跟调用方应用信息是否对应得上就行了,这样集中式授权的瓶颈也就不存在了。
服务发现也有安全问题?
好,现在我们已经解决了调用方之间的安全认证问题。那在RPC里面,我们还有其它的安全问题吗?
回到我们上面说的那个完整的RPC应用流程里面,服务提供方会把接口Jar发布到私服上,以方便调用方能引入到项目中快速完成RPC调用,那有没有可能有人拿到你这个Jar后,发布出来一个服务提供方呢?这样的后果就是导致调用方通过服务发现拿到的服务提供方IP地址集合里面会有那个伪造的提供方。
当然,这种情况相对上面说的调用方未经过咨询就直接调用的概率会小很多,但为了让我们的系统整体更安全,我们也需要在RPC里面考虑这种情况。要解决这个问题的根本就是需要把接口跟应用绑定上,一个接口只允许有一个应用发布提供者,避免其它应用也能发布这个接口。
那怎么实现呢?在[第 08 讲] 我们提到过,服务提供方启动的时候,需要把接口实例在注册中心进行注册登记。我们就可以利用这个流程,注册中心可以在收到服务提供方注册请求的时候,验证下请求过来的应用是否跟接口绑定的应用一样,只有相同才允许注册,否则就返回错误信息给启动的应用,从而避免假冒的服务提供者对外提供错误服务。
总结
安全问题在任何一个领域都很重要,但又经常被我们忽视,只有每次出安全事故后,我们才会意识到安全防护的重要性。所以在日常写代码的过程中,我们一定要保持一个严谨的态度,防止细小错误引入线上安全问题。
虽然RPC经常用于解决内网应用之间的调用,内网环境相对公网也没有那么恶劣,但我们也有必要去建立一套可控的安全体系,去防止一些错误行为。对于RPC来说,我们所关心的安全问题不会有公网应用那么复杂,我们只要保证让服务调用方能拿到真实的服务提供方IP地址集合,且服务提供方可以管控调用自己的应用就够了。
课后思考
前面讲的调用方之间的安全问题,我们更多只是解决认证问题,并没有解决权限问题。在现实开发过程中,一个RPC接口定义里面一般会包含多个方法,但我们目前只是解决了你能不能调用接口的问题,并没有解决你能调用我接口里面的哪些方法。像这种问题,你有什么好方案吗?
欢迎留言和我分享你的答案,也欢迎你把文章分享给你的朋友,邀请他加入学习。我们下节课再见!
- 陈国林 👍(11) 💬(1)
可以从鉴权和授权2个方面来看(参考k8s) 1. 鉴权可以用 token 的方式,token鉴权应该是目前用的最多的一种认证方式 2. 授权可以用 RBAC 方式,类似k8s里面的 serviceAccount 和 role 和 roleBinding 等等
2020-04-05 - JDY 👍(10) 💬(1)
我突然觉得,rpc这东西好像跟k8s有点像,k8s是类似把一个大服务拆分成很多模块,完了之后来把这些微服务管理起来,而rpc是提供了一系列接口,通过网络的方式,让各个调用方去调用。不知道能不能这样比较,求老师指点。
2020-04-15 - 余松 👍(6) 💬(1)
简单总结一下。服务端提供的安全校验方式。 1.md5摘要校验(安全级别较低,服务端直接提供或者网关代劳) 2.非对称加密算法就行签名(RSA和老师提到的HMAC等算法,服务端直接提供或者网关代劳) 3.Oauth2授权(有单独的授权服务,四种模式任君选择)
2020-04-28 - 陈国林 👍(5) 💬(1)
老师,你在上文提到的HMAC来做认证,服务提供方是使用 “公钥” 验证服务调用方的签名串吗?那如果是这样的话,是要事先生成公私钥对吧
2020-04-05 - Darren 👍(4) 💬(2)
我们之前是用的配置中心来存储对应的关系,B服务可以被那些服务调用或者不可以被那些服务调用,同时,也有B服务的接口权限,然后内部网关同步相关信息,保存到内存缓存中,当调用方请求经过网管时,获取调用方,服务方,服务方具体的接口,然后去校验相关的信息,判断是否可以放行,我们的鉴权没有放在服务方本身去实现,是在网关实现的,请老师指点
2020-04-01 - 百威 👍(3) 💬(1)
这种授权方式,对于调用方而言,加密后的签名怎么维护呢,感觉有代码侵入呀
2020-04-03 - 波波 👍(2) 💬(1)
老师,RPC可以用于公网通信吗?鉴权放在服务端对服务端的压力是不是也挺大的?
2020-04-20 - 雨霖铃声声慢 👍(2) 💬(1)
首先肯定是在服务端做,其次是应该在每一个服务提供端对其接口做权限审核,假如一个服务端提供10个接口出来,5个可以被A调用,另外5个不能被A服务调用,那么就应该有个配置文件配置这调用端和服务端各个接口的权限配置信息,这个权限配置应该放在配置中心,然后每个服务端会动态获取配置信息根据配置信息来决定是否拒绝服务。对于新增的调用服务,它需要先申请权限,然后服务端提供方修改配置文件对其提供服务。
2020-04-02 - Raymond 👍(1) 💬(1)
老师,请问服务提供方对服务调用方的授权认证后,已认证的状态应该存在服务提供端本地吧,那服务端集群中各个节点之间怎么共享同一个服务调用方应用的认证状态呢?还是说每次调用到不同的的服务提供方节点都需要从新进行鉴权?或者说是服务调用方在获取到服务提供方的IP列表后统一进行一次遍历的授权认证?请老师帮助解惑。
2020-05-08 - 2102 👍(1) 💬(1)
权限控制应放在服务提供方,权限控制规则可以放到单独管理节点,服务启动的时候从管理节点获取规则,权限规则变更后下发到服务节点。
2020-04-01 - 奕 👍(1) 💬(1)
在进行接口授权的同时也进行接口方法的授权
2020-04-01 - 钱 👍(11) 💬(2)
前面讲的调用方之间的安全问题,我们更多只是解决认证问题,并没有解决权限问题。在现实开发过程中,一个 RPC 接口定义里面一般会包含多个方法,但我们目前只是解决了你能不能调用接口的问题,并没有解决你能调用我接口里面的哪些方法。像这种问题,你有什么好方案吗? 这节讲的容易消化,老师提的问题,我之前在JD的时候也恰好实现过一个解决方案,我们组做过一个POP的订单中间件功能,提供的服务为了防止未知系统任意调用,在我们的那套系统里做了一个服务调用管理功能,就是到方法级。 1:我们提供服务调用的申请模板,描述那个系统,那个应用,调用我们那个服务的那个方法,调用量多少,调用应用负责人是谁,对接研发谁,还有别的配置信息 2:我们会把调用配置信息落库,然后放入缓存,使用调用者信息及我们提供服务的信息组成key,放入redis 3:在调用方调用我们的时候,去做拦截和鉴权 4:j-one统一部署平台中有系统和应用的唯一标识信息,这些信息在调用接口时作为入参的一部分,就能实现细粒度到方法基本的控制了 另外,就是使用token的方式,这种方式应用的就更多了,也是申请时分发,其实本质是一样的就看对应的配置信息存储在哪里啦!
2020-05-16 - 核桃 👍(1) 💬(0)
在hadoop生态圈里面,有一个叫kerberos的东西,提供了安全认证和授权的,但是这玩意太重了。可以实现双向的认证,包括客户端和服务端。因为像https服务,一般是不验证客户端的。而实现原理是非对称加密,有一个票据中心,这个中心把服务端给的公钥信息加密,还有中心的认证加密信息一起发给客户端,让客户端在发起请求的时候带上给服务端。服务端接受到以后到票据中心认证一下。 但是这种方式,容易出现票据过期的情况,需要定时更新票据,也就是续约。而且这套服务太重了。未来可能会有其他方案来代替调。
2022-02-13 - 惘 闻 👍(1) 💬(0)
一个接口只允许有一个应用发布提供者,避免其它应用也能发布这个接口。 这句话里的一个应用是什么意思啊,一个ip端口加应用id吗? 那一个接口只有一个服务能支撑大流量业务吗? 如果只针对应用id,那么伪装者也可以伪装这个应用id吧?
2021-02-02 - 独孤九剑 👍(0) 💬(0)
可以借鉴OAuth2的授权码策略
2021-07-19