微服务之服务调用与安全控制
引言:
近年来,大多数企业IT软件均在向微服务架构转型,由于微服务架构采用了更细粒度的分布式拆分,对于服务调用安全方面的问题更复杂,更需要重视,需要整体的系统化解决方案。本文将分享普元EOS8.0版本的服务调用安全控制方案,希望能够对需要搭建微服务平台的企业和人员能够带来一些启发和帮助。
一、微服务平台架构简介
EOS8 微服务平台功能架构图
普元8月份发布了EOS8微服务平台,上图为功能架构图。平台提供了微服务开发测试、运行容器、公共服务以及管理监控等较为完善的工具套件,用以支撑企业级的It系统微服务架构落地。
微服务平台运行视图
这是微服务平台的一个简化版的运行视图,比照这张图,我们先一起理解并统一下概念术语,这里的术语解释不一定是业界共识,仅作为今天分享内容上下文中的一个定义对大家理解分享内容来说非常重要。
域:从物理部署角度看,指微服务业务系统的基础运行环境,可以支持多个系统再域内运行监控和管理,即一个域部署一套微服务基础环境即可。从逻辑角度看,域常与业务或组织划分有所关联,可根据实际需求定义。
系统:即传统意义上的业务系统。微服务架构模式下,一个系统包含一个或者多个微服务。
应用:特指拆分后的 “微服务”。微服务架构模式下,系统按业务特点拆分成多个微服务,为避免“服务”这个词的使用混乱,我们这里将系统拆分后的微服务称作“应用”。
服务:特指API,即应用或网关发布和开放的对外接口。
网关:系统服务对外开放的服务门户。网关为客户端提供服务,作为服务出口屏蔽服务端实现。对于服务端来说,网关是所有外部请求的入口,提供安全认证、管理监控和服务编排等增强能力。
二、服务调用场景分析
服务调用场景的简单分解结果
运行视图
结合两图分析,我们推荐的服务调用模式:“跨系统调用走网关,系统内部直接调用”,优缺点分析如下:
跨系统调用走网关,网关作为请求的入口,可以为开发的服务提供很多增强的能力,如安全认证、流控、动态路由等等能力。网关作为系统服务的统一出口,可以屏蔽服务的实现。让客户端使用更简单。
如果跨系统不通过网关的话,类似服务安全控制、流控、降级这部分能力在网关、应用两端均需要重复建设。多种方式融合时,控制会非常混乱。
系统内部通常是一个项目团队,网关通常是不同的团队维护,系统开发期沟通交互多,应用间直接依赖SDK调用相比到网关发布再调用来说更方便。
系统内部调用的服务接口范围通常与给外部系统开放的服务接口范围不一致,如果都通过网关发布,工作量增多、安全控制方面的个性化需求多,管理复杂。
这张运行视图,注重标注出来了几个服务调用场景的关系。
①:表示用户通过系统前端UI访问后端应用的服务
②:表示系统内部,多个后端应用之间的服务调用
③:表示跨系统调用。由于空间有限,每个域只画了一个系统,所以图中所示的跨系统调用同时也跨了域。如果是域内跨系统服务调用,则需要调用本域网关发布的服务
④:表示与内部的网关,将外部请求路由到域内应用的服务调用
服务调用的过程中一定会存在服务提供者和消费者两个角色,划分如下:
“应用”既提供服务又消费服务,所以兼有两个角色很好理解。
“用户”通常是IT系统的使用者自然划入消费者角色。
“网关”比较特殊,在服务调用过程中,主要任务是中介。由于系统必须通过网关才能向外提供服务,且此时网关在中介的基础上又会提供一些重要的增值能力如流控、路由、监控等,因此我们也把网关划入服务提供者角色。而对于真正的服务提供者“应用”来说,他会收到网关路由过来的服务调用请求,那么从这个角度,网关也可以算是带点消费者的特点,算半个消费者。
服务的调用过程即服务发布与消费的过程,一次调用通常也被称作“交易”,而信任是交易完成的前提,因此服务提供者和消费者之间需要建立信任。在我们的服务调用场景中,建立信任实际就是服务提供者对消费者的身份进行认证,认证通过后即成功建立信任,进一步需要进行鉴权,让交易在一个可信可控的范围内进行。
那么在上述这四个服务调用的场景中,均需要做服务安全认证与鉴权。
认证:目标是检查消费者是否可信,一般可以由提供者自己检查或委托第三方认证中心检查。
1、用户认证,使用“用户令牌”检查用户是否登录
2、系统内服务调用认证,使用“应用令牌”检查是否本系统应用
3、跨系统服务调用认证,使用“API令牌”检查是否已经订阅过服务
4、可信网关认证,使用“网关令牌”检查请求是否来自本域的网关
鉴权:目标是对可信任的消费者的权限范围进行检查和控制
1、网关检查应用可访问的API范围,并进行流控、路由等控制
2、应用检查用户功能权限,数据权限
其中①属于用户认证,用户认证在之前的统一认证中心中已经做过详细介绍,本文就不再讲解。后续内容我们主要对②③④几个部分的服务调用与安全控制方案进行说明。
三、服务发布过程介绍
面向系统内部发布服务:
- 对系统内发布,指将服务开放给系统内其他应用访问
- 基于Spring MVC能力发布RESTful服务接口映射
- 基于Feign封装后的SDK提供给其他应用做服务调用依赖
- 基于Swagger设计API Doc
面向系统外部发布服务:
- 对系统外发布,指通过API Gateway 将已发布的RESTful API 向外部系统开放
- 发布时支持API分组
- 发布时支持API流控、路由等控制策略设置
介绍系统内部服务发布之前,先来看一下我们推荐的后端应用的设计方案
后端应用设计
对于系统内后端应用的设计,推荐以下几个设计原则:
1、服务设计与服务实现解耦
2、应用API设计先行,内部模块Lib化
3、系统内应用之间服务调用,采用SDK依赖RPC调用模式
基于上述原则,后端应用项目结构推荐如下:
系统内服务发布
基于后端应用的设计原则,服务规格均需要在API模块进行定义,平台支持向导或手工两种方式通过注解发布服务,使用到的组件和注解简要说明如下:
Spring MVC 注解:主要用来定义RESTFul 服务的URI映射关系。常用注解包含:@RequestMapping @GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping @PathVariable @RequestParam @RequestBody等等
Tarest注解:此注解来自EOS平台的服务发布调用模块Tarest ,作用是对服务发布和消费SDK进行定义。结合Feign框架提供远程模拟本地调用的SDK,以及结合Coframe框架做用户访问权限控制。服务发布时使用的注解有两个@TarestService @TarestOperation。 服务发布成功后,可以通过管理平台应用查看应用已发布服务列表
Swagger注解:用来定义服务的规格并生成接口文档。常用注解包含:@Api @ApiOperation,基于Swagger规范的服务规格文件除了用来生成文档之外,一个更重要的作用就是可以在服务编排中使用,对于一个RESTFul 接口来说,规格文件就相当于 WebService接口的WSDL文件一样重要
系统内服务发布:示例代码
@RequestMapping("/say-hello")
@TarestService(group="group1",version="v1",name="SayHelloService")
@Api(value="com.eos.consumer.app.api.ISampleAppHello")
public interface ISampleAppHello {
@GetMapping
@TarestOperation(name="sayHello")
@ApiOperation(value="sayhello",nickname="Hello World !")
String sayHello();
@GetMapping("/{user}")
@TarestOperation(name="sayHelloToUser")
@ApiOperation(value="sayHelloToUser", nickname="Hello {user} !")
String sayHello(@PathVariable("user") String user);
}
(左右滑动可查阅全部)
上述接口设计代码示例中,使用了由Spring、Swagger以及EOS提供的三类注解,来自不同框架解决不同问题的三部分注解目前看上去比较繁琐且容易出错,因此EOS平台会提供接口设计的向导工具,用以生成上述的接口代码和服务规格Swagger文件,尽量避免纯手工编写,提升应用开发效率。
系统外服务发布:动态路由、精准路由两种发布模式
动态路由发布
- 不指定具体的API,基于Base URL模糊匹配的方式发布服务
- 优点是统一控制权限,可动态增加路由,操作简单方便
- 缺点是无法获取明确的服务列表,控制策略影响范围大
精准路由发布
- 精确到一个具体API的Method,逐个发布
- 优点是可以精确控制权限,可提供明确的服务列表
- 缺点是新增发布时,权限、分组、服务策略等均需定义
动态路由发布服务是绝大部分网关均需要支持的能力,通常适合批量服务穿透式向外发布的场景。
另外一些特殊的场景下比如,需要协议、报文转换或者权限特殊控制的服务来说,就需要更细粒度的服务发布能力。
我们在动态路由的基础之上,实现了API的更细粒度的控制和路由策略绑定。
系统外服务发布:精确发布示例
通过网关向系统外部发布接口,可配置请求、响应方式以及报文转换规则
四、服务消费与认证安全
服务消费的方案
系统内应用间服务直接调用
- 采用SDK依赖,类RPC方式调用其他应用的服务, EOS平台采用了基于Feign的实现方式
- 系统内应用间调用互信,采用对称加解密请求令牌方式实现
跨系统服务调用必须经过网关中转
- 网关是系统向外提供服务的渠道,也是接收外部服务请求的入口。需要为系统提供认证、流控、路由、监控等相关服务治理能力
- 消费者需要先订阅目标网关上开放的服务才能调用,网关需要对消费者进行身份认证。EOS 8 中API 网关自行颁发API令牌并自带认证能力
- 服务提供者与当前域的网关之间也需要有身份识别,确保安全可靠。EOS 8 平台采用非对称加密证书请求令牌方式实现网关与系统的互信
系统内服务消费
1、依赖SDK
<dependency>
<groupId>com.eos.sample</groupId>
<artifactId>sample-app-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
2、声明服务消费
@TarestClient(name="sample-app",path="",fallback=SampleAppServiceClientFallback.class)
public interface ISampleAppServiceClient extends ISampleAppHello {}
(左右滑动可查阅全部)
3、类本地方式调用服务
@RestController
public class ServiceDemo implements ITestServiceDemo{
@Autowired
ISampleAppServiceClient client ;//自动注入TarestClient注解声明的消费服务
@Override
public String invokeDemo(String user) {
return client.sayHello(user);
}
}
(左右滑动可查阅全部)
服务消费声明的过程除了上述示例中的编写代码注解声明之外,还可以通过开发工具向导进行服务消费配置化声明。
系统内服务认证:如何识别本系统内部的其他应用?
系统内服务认证
应用端需配置本系统的内部认证秘钥,采用对称加解密的方式,发送和验证“应用令牌”
跨系统服务认证与消费:消费者订阅服务,网关识别消费者身份
- 消费者从网关订阅服务,消费服务时需要带”API 令牌”:访问网关服务
- 网关检查消费者请求的令牌是否合法以及API范围是否超限
消费网关发布的服务:HTTP头中带API令牌示例代码
public class GatewayServiceConsumeDemo {
public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
UserInfo user = new UserInfo();
user.setUsername("tiger");
user.setNickname("Tiger");
RequestEntity<UserInfo> requestEntity;
try {
//网关服务地址,示例为模拟Post方法创建一个用户对象
requestEntity = RequestEntity.post(new URI("http://gateway.com/api/users"))
.header("access_token", "12b839e2-399e-4dfc-a50e-e00771184c79") //设置api令牌头
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON).body(user);
ResponseEntity<UserInfo> responseEntity = restTemplate.exchange(requestEntity, UserInfo.class);
if (!responseEntity.getStatusCode().is2xxSuccessful()) {
HttpErrorStatus.valueOf(responseEntity.getStatusCodeValue()).exception();
}
UserInfo createdUserInfo = responseEntity.getBody();
System.out.println(createdUserInfo);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
(左右滑动可查阅全部)
网关与服务提供端认证:服务提供端如何识别网关身份?
1、网关安装启动前,通过工具生成公私钥
2、运行期网关进行服务路由转发时,利用私钥签名,生成网关令牌
3、应用从本域内的网关获取公钥,并将公钥配置到服务提供端配置文件中
4、运行期收到来自网关的服务请求时,使用公钥验证“网关令牌”是否合法
五、服务访问控制
网关对服务请求的控制
网关控制服务访问
- 流控,流量、IP、并发数控制
- 服务分组路由,升、降级等控制
网关负责对于来自外部的服务请求需要进行统一的控制与路由。如:流量控制、IP控制黑白名单、服务并发请求控制等等。还可以通过动态服务路由调整来达到服务请求的升降机,提升系统运行的可靠性。
应用对服务访问的控制
应用端控制用户服务权限范围
- 功能权限,接口调用范围
- 数据权限,数据访问范围
应用间服务调用时通常需要传递用户上下文,在某些场景中,即使应用认证通过,仍需控制应用的API访问权限和数据权限。
我们提供了应服务功能权限的控制,用以控制用户角色与API的访问关系。运行时,对这些服务方法进行拦截,检查用户权限。
对于数据权限控制,目前仍需要开发者自行扩展实现,比如利用AOP模式进行拦截某些方法控制数据访问。
回顾总结:
本文主要对服务的消费者和提供者之间的调用关系进行了梳理,以普元的EOS8平台已落地的方案实现为基础,从服务调用场景入手,着重讲后端服务调用,按系统内外分为两个部分,分别对服务发布、消费、认证方式以及控制方案进行了说明,希望能给大家进行微服务架构升级改造或者平台建设带来一些帮助。
精选提问:
问1:微服务平台都投入在哪些生产系统,想了解一下微服务网关实现,并发性能怎么样?
答:目前我知道的使用EOS8 平台的客户有邮储银行、邮政银行、太保、国电投、成飞等等企业,具体业务也是各式各样的。 网关底层也是基于Spring Cloud 结合Zuul、Ribbon等框架实现。并发性能不错,但我手头还没有具体数据。
问2:流量控制,服务路由具体是怎么实现的呢?有没有相关解决方案。
答:流量控制就是再网关层面做了流量计数,对服务API的访问次数进行控制,通常可以按照 天、小时、分钟等单位进行控制。服务路由,在网关上发布一个API服务的时候,需要绑定路由策略,比如不同分组或版本的API 路由到某几个服务提供者上。 内部路由实现集成了Zuul组件,并扩展了细粒度路由能力。
问3:请问服务发布有个注解targetservice是rpc模式吗?
答:TarestService是我们自定义的注解,主要用来统计服务提供和消费的。系统内部rpc模式指的是采用本地java代码的方式,调用远端的restful api调用。核心能力是SpringCloud Feign组件提供的,Tarest 是对Feign组件的封装,在Feign的基础上,增加了配置化声明服务消费、提供端拦截认证相关能力。
问4:请问网关如果要支持协议转换,有没有推荐的方案?
答:对于网关支持协议转换我觉得好的方案是需要在网关增加服务编排能力,通常服务编排需要提供服务的组装、协议转换、事务管理等。简单点做就是服务接入后可以配置消息处理拦截器,拦截器进行协议转换后,再路由到服务提供者。