360SDN.COM

首页/Java/列表

spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计

来源:ObjectDeveloper  2017-09-11 12:18:25    评论:0点击:


前言

本篇接着<spring boot / cloud (八) 使用RestTemplate来构建远程调用服务>这篇博客来继续讨论微服务间接口调用的认证和鉴权的思考和设计

在上一篇文章中主要是偏实现方面,具体的实现思想没有过多讨论,本篇文章则是主要讨论一下设计的思路.

我们都知道,在微服务的架构设计中,一个大的系统会被按照不同的领域拆分成一个个小的微服务,而这些微服务之间不可避免的会有业务数据的交互,

那么我们会用一些远程服务调用的方式来连接各个微服务( 比如:RPC,RestFul,等 ).

不过,就像前面说的一样,期初,应用不大,随便弄弄无所谓,但是等应用规模起来以

后,你会发现,有成百上千的服务在运行,这些服务相互依赖,仿佛一团乱麻.

更可怕的事情是如果没有有效的权限控制,我们很有可能都不清楚是谁调用了你的服务......

所以说,我认为,在微服务架构设计中,内部服务调用的权限控制是非常必要的(至少我参与的项目都有这种需求),它应该满足如下几个主要的功能:

  • 防止越权行为

  • 管理服务的依赖关系

  • 规范服务调用行为

  • 能够在运行时修改权限配置

下面我们来看看具体的分析:


场景分析


防止越权行为

在系统中添加权限相关的控制,主要是为了增加系统的安全性,总结下来主要是为了防止如下的两种越权行为:

  • 横向越权 (指的是攻击者尝试访问与他拥有相同权限的用户的资源)

  • 纵向越权 (指的是一个低级别攻击者尝试访问高级别用户的资源)

所以说通常,在系统中的权限校验也按照以上划分会分为两个步骤:

  • 校验访问者身份

  • 校验是否拥有被访问资源的权限


校验访问者身份

先说校验访问者身份,这个主要目的就是确定A的确是A,不是其他的阿猫阿狗.

要做到这一点并不难,并且有很多安全框架都能支持,比如apache shiro,spring security,jwt,等等.

这些框架主要思路还是使用token签名的方式,也就是说,

要么调用方和服务方约定一个私钥,然后调用方自行通过算法生成token,

或者服务方提供一个获取token的接口(OAuth2),调用方主动调用接口获取token,

最后调用方在调用服务的时候,都把这个token给带上,便于服务方认证身份.

那么那种方式更好呢? 个人经验我会按场景做如下架构原则定义:

  • 如果服务是给系统用的,则采用私钥的方式

  • 如果服务是给用户(人)使用的,则采用获取token的方式(通过用户名和密码来获取token)

那么,还有一种情况,如果这个接口既是给人使用的,也是给第三方系统使用的,怎么办呢?

这个其实也不复杂,不过不会在今天这篇文章中讨论,这里只提一点,通过入口区分,也就是网关,大家可先自行脑洞.

那么,回过头来看,我们今天讨论的场景显然是属于是给系统使用的服务,所以在我设计的RMS组件中,是采用的私钥的方式.

采用这种身份认证方式需要注意如下几点:

  • 私钥的安全性

  • token的过期策略

  • token的计算算法

在RMS组件中,我并没有引入第三方的依赖,因为我希望,这个身份认证是轻量级的,灵活的,这些第三方认证框架大而全,很优秀,但我们只会用到其中的一小块,会造成一些没必要的依赖.

从实现方面,首先,所有的私钥,都会配置到远端的配置中心里面,本地不做任何存储,由专门的人员管理和维护,系统只有在运行的时候,才能获取到私钥.

同时依赖于spring cloud config server的特性,可以在运行时更换私钥,更加灵活,也保证了的私钥的安全,

如下的sign(token)的算法,通过应用名称和私钥,要有当前时间(精确到小时),拼接起来然后进行md5,得到最终的sign,因为加入了时间的这个因子,所以计算出来的sign是每小时过期的

算法方面大家可以随意设计,但是切记,不要过度设计,满足需求即可


校验是否拥有被访问资源的权限

然后我们再聊校验是否拥有被访问资源的权限,这个点说简单也简单,说复杂也非常复杂.

在前面一步的校验中,已经确定了身份,现在是要确定,A是否有访问B的/user服务的权限.

其他的不说,我这边只提两点:

  • uri匹配

  • 性能

在没有RestFul风格的url的时候,一切其实都还蛮美好的,因为,url就是唯一值,是整个系统的最小颗粒度的权限点.

大家以前可能是这样做的,有张表,记录这系统的url,以及其他的角色,岗位等的关联,然后,如何校验呢,非常简单,

直接的sql语句select count(1) form xxx where url='/aaa/getUserByName'就行了,能查到值就代表有权限.

在稍微进阶一点的,会考虑性能问题,会将某个用户的一些权限缓存起来,然后在内存中进行判断.

但是,当RestFul风格的url到来的时候,这一切变得不那么美好了,先看如下几个url的例子:

我们可以看到,按照原有的方式已经不那么使用了,url的定义从原来的平面化的,变成了立体化的,

按照原来的方式,那么就变成了,如果拥有查询用户详情接口权限的系统,同时也就拥有了更新用户和删除用户的权限,这是非常严重的越权行为.这显然不是我们

期望看到的.

那么如何优化呢? 首先,我们的权限判断中应该加入httpmethod的判断,这样,就能很简单的避免以上的情况.

但是更严重的问题来了,url不再是固定不变的了,而是动态的,怎么办呢?先拍脑子想想,处理方案可能有如下几种:

  • 正则匹配

  • 将所有url解析成树形结构,将动态部分用星号表示,然后进行最短匹配

以上两种方案我都试过,不过方案都过于复杂,甚至存在性能问题,因为以上两种方式都不可不免会进行循环匹配.

我们当然不想因为一个url校验,而引入一个性能问题的风险,那么如何解决这个问题呢?

其实我们回过头来想想,spring mvc为什么就能准确的定位到每个url对应的handler呢?

其实还是那句话,最复杂的部分,spring已经帮我们完成了,在spring 上下文初始化的时候,容器就会记录所有的mapping,如下:

以上日志大家随便启动一个spring boot应用都能看到,其实这类输出就是我们controller定义的requestMapping

而我们平时获取url的方式是这样的:

这样获取到的url 大概是,也就是我们实际请求的url:

那么如何改变呢?以上这个url我们还是不知道如何匹配? 换个思路想想,能进入拦击器,则表示spring将这个地址已经匹配到了对应的handler,我们只需找到这个url对应的那个handler就行了.

在request的作用域中(Attribute),存放这很多spring的信息,debug一下,打个断点,看看都有啥?,最终我定位到了bestMatchingPattern这个属性,大致含义就是最佳匹配模式,也里面的值是requesMapping里的value

这不正是我们想要的结果吗?spring已经帮我们做了最佳的替换,如下代码:

那么后续我们就可以调整我们的设计了,如果你的权限配置是配置在数据库中的那么,最简单的鉴权sql语句就是:

这样做的好处就是可以做到最精确的匹配,颗粒度达到最细,并且毫无性能损耗,你无需做任何的循环匹配.详细代码可以在项目的RmsAuthHandlerInterceptor类中找到

其他

在上面还提到了 管理服务的依赖关系 , 规范服务调用行为 , 能够在运行时修改权限配置 这几点 ,

在上一篇讲解RMS代码的时候也都提及到了,会通过远程配置文件的方式,来进行管理,如下:

会分为application和service两类,application主要描述身份认证和权限,service主要描述服务详情 . 通过这种结构来管理服务间的依赖关系

然后在项目中,大家可以看Rms这个类,里面抽象出了一个公共的方法,用于规范调用行为,最终调用的方式如下:

在系统中,开发人员都无需关心任何接口的定义,只需通过接口编号就可以进行调用.

最后,所有的RMS相关的配置都会放在配置中心,同一管理.

结束

今天代码层面的东西讲的比较少,主要是跟大家介绍一下设计的思路,还是一个原则,使代码更健壮,更灵活,更合理,同时,也切记不要重复造轮子,也不要过度玩技术.

在下一篇文章中,我会介绍一下分布式任务调度的思考和设计,敬请期待.

代码仓库 (博客配套代码,请点击"阅读原文")


想获得最快更新,请关注公众号

阅读原文

为您推荐

友情链接 |九搜汽车网 |手机ok生活信息网|ok生活信息网|ok微生活
 Powered by www.360SDN.COM   京ICP备11022651号-4 © 2012-2016 版权