内容总结自《微服务架构设计模式》
开发面向生产环境的微服务应用
- 一、开发安全的服务
- 1、安全性概述
- 2、单体应用安全性
- 3、微服务应用安全性
- 二、如何使用外部化配置模式
- 三、如何使用可观测性模式
- 四、使用微服务基底模式开发服务
- 1、使用微服务基地
- 2、从微服务到服务网格
- 五、总结
一、开发安全的服务
1、安全性概述
应用开发人员主要负责实现安全性的四个方面:
- 身份验证:验证尝试访问应用程序的应用程序或人员(安全的术语叫主体)的身份。例如,应用程序通常会验证访问主体的凭据,例如用户的ID和密码,或应用程序的API密钥。
- 访问授权:验证是否允许访问主体对指定数据完成请求的操作。应用程序通常使用基于角色的安全性和访问控制列表(ACL)的组合。基于角色的安全性为每个用户分配一个或多个角色,授予他们调用特定操作的权限。ACL授予用户或角色对特定业务对象或聚合执行操作的权限。
- 审计:跟踪用户在应用中执行的所有操作,以便检测安全问题,帮助客户实现并强制执行合规性。
- 安全的进程间通信:理想情况下,所有进出服务的通信都应该采用传输层安全性(TLS)加密。服务间通信甚至可能需要使用身份验证。
2、单体应用安全性
安全架构的一个关键部分是会话,它存储主体的ID和角色。传统的Java EE应用程序,会话是Httpsession内存中会话。会话令牌代表着每一个具体的会话,客户端在每个请求中包含会话令牌。它通常是一串无法读懂的数字标记,例如经过加密的强随机数。应用程序的会话令牌是一个名为JSESSIONID的HTTP cookie。
实现安全性的另一个关键是安全上下文,它存储有关发出当前请求的用户的信息。SpringSecurity框架使用标准的Java EE方法将安全上下文存储在静态的线程局部变量中,任何被调用以处理请求的代码都可以访问该变量。请求处理程序可以调用securityContextHolder.getContext ( ) . getAuthentication()获取有关当前用户的信息,例如他们的身份和角色。相反,Passport框架将安全上下文存储为request对象的user属性。
还有就是使用基于角色的授权。应用定义了与不同类型用户相对应的几个角色,如CONSUMER、RESTAURANT、COURIER和ADMIN。可使用Spring Security 的声明性安全机制来限制对特定角色的URL 和服务方法的访问。角色也与业务逻辑交织在一起。例如,消费者只能访问自己的订单,而管理员可以访问所有订单。
3、微服务应用安全性
在微服务应用程序中实现安全性的一个挑战是我们不能仅仅从单体应用程序借鉴设计思路。这是因为单体应用程序的安全架构的一些方面对微服务架构来说是不可用的,例如:
- 内存中的安全上下文:使用内存中的安全上下文(如ThreadLocal)来传递用户身份。服务无法共享内存,因此它们无法使用内存中的安全上下文(如ThreadLocal)来传递用户身份。在微服务架构中,我们需要一种不同的机制来将用户身份从一个服务传递到另一个服务;
- 集中会话:因为内存中的安全上下文没有意义,内存会话也没有意义。从理论上讲,多种服务可以访问基于数据库的会话,但它会违反松耦合的原则。我们需要在微服务架构中使用不同的会话机制。
1、使用API Gateway
处理身份验证有两种不同的方法。一种选择是让各个服务分别对用户进行身份验证。这种方法的问题在于它允许未经身份验证的请求进入内部网络。它依赖于每个开发团队在所有服务中正确实现安全性。因此,出现安全漏洞的风险和概率都很大。
在服务中实现身份验证的另一个问题是不同的客户端以不同的方式进行身份验证。纯API客户端使用基本身份验证为每个请求提供凭据。其他客户端可能首先登录,然后为每个请求提供会话令牌。但我们要避免在服务中处理多种不同的身份验证机制。
更好的方法是让API Gateway在将请求转发给服务之前对其进行身份验证。在APIGateway中进行集中API身份验证的优势在于只需要确保这里的验证是正确的。因此,出现安全漏洞的可能性要小得多。另一个好处是只有API Gateway需要处理各种不同的身份验证的机制
验证客户端的凭据很重要,但这还不够。应用程序还必须实现访问授权机制,以验证是否允许客户端执行所请求的操作。例如,一个接口getorderDetails()查询只能由下此order的消费者(基于实例的安全性的一个示例)和为所有消费者提供服务的客户服务代表调用。
实现访问授权的一个位置是APl Gateway。例如,它可以将对GET/orders/ {orderId)的访问限制为消费者和客户服务代表。如果不允许用户访问特定路径,则API Gateway可以在将请求转发到服务之前拒绝该请求。与身份验证一样,在API Gateway中集中实现访问授权可降低安全漏洞的风险。你可以使用安全框架(如Spring Security)在APl Gateway中实现访问授权。
在API Gateway中实现访问授权的一个弊端是,它有可能产生API Gateway与服务之间的耦合,要求它们以同步的方式进行代码更新。而且,APl Gateway通常只能实现对URL路径的基于角色的访问。由API Gateway实现对单个领域对象的访问授权通常是不实际的,因为这需要详细了解服务的领域逻辑。
另一个实现访问授权的位置是服务。服务可以对URL和服务方法实现基于角色的访问授权。它还可以实现ACL来管理对聚合的访问。例如,在order service中可以实现基于角色和基于ACL 的授权机制,以控制对order的访问。FTGO应用程序中的其他服务也可以实现类似的访问授权逻辑。
2、使用JWT传递用户身份和角色
在微服务架构中实现安全性时,你需要确定API Gateway应使用哪种类型的令牌来将用户信息传递给服务。有两种类型的令牌可供选择。一种选择是使用不透明(无可读性)的令牌,它们通常是一串UUID。不透明令牌的缺点是它们会降低性能和可用性,并增加延迟。因为这种令牌的接收方必须对安全服务发起同步RPC调用,以验证令牌并检索用户信息。
另一种消除对安全服务调用的方法是使用包含有关用户信息的透明令牌。透明令牌的一个流行的标准是JSON Web令牌(JWT)。JWT是在访问双方之间安全地传递信息(例如用户身份和角色)的标准方式。JWT 的内容包含一个JSON对象,其中有用户的信息,例如其身份和角色,以及其他元数据,如到期日期等。它使用仅为JWT的创建者所知的数字签名,例如API Gateway和JWT 的接收者(服务)。该签名确保恶意第三方不能伪造或篡改JWT。
3、使用OAuth 2.0
假设你要为FTGO应用程序实现一个User Service,该应用程序管理包含用户信息(如凭据和角色)的数据库。API Gateway调用User Service来验证客户端请求并获取JWT。你可以设计User Service的API并使用你喜欢的Web框架实现它。但这不是FTGO应用程序特有的通用功能,自己开发此类服务往往是得不偿失的。
幸运的是,你不需要开发这种安全基础设施。你可以使用名为OAuth 2.0的标准的现成服务或框架。OAuth 2.0是一种访问授权协议,最初旨在使公共云服务(如GitHub或Google)的用户能够授予第三方应用程序访问其信息的权限,而不必向第三方应用透露他们的密码。例如,OAuth 2.0使你能够安全地授予第三方基于云的持续集成(CI)服务,访问你的GitHub存储库。
OAuth 2.0中关键概念如下:
- 授权服务器:提供用于验证用户身份以及获取访问令牌和刷新令牌的API。SpringOAuth是一个很好的用来构建OAuth 2.0授权服务器的框架。
- 访问令牌:授予对资源服务器的访问权限的令牌。访问令牌的格式取决于具体的实现技术。Spring OAuth的实现中采用了JWT格式的访问令牌。
- 刷新令牌:客户端用于获取新的AccessToken的长效但同时也可被可撤销的令牌。
- 资源服务器:使用访问令牌授权访问的服务。在微服务架构中,服务是资源服务器。
- 客户端:想要访问资源服务器的客户端。在微服务架构中,API Gateway是 OAuth 2.0客户端。
使用OAuth2.0的一一个重要好处是它是经过验证的安全标准。使用现成的OAuth2.0身份验证服务器意味着你不必浪费时间重新发明轮子或者是没有开发不安全的设计的风险。但OAuth 2.0不是在微服务架构中实现安全性的唯一方法。 无论你使用哪种方法,三个关键思
想如下:
- API Gateway负责验证客户端的身份。
- APIGateway和服务使用透明令牌(如JWT)来传递有关主体的信息。
- 服务使用令牌获取主体的身份和角色。
二、如何使用外部化配置模式
假设你负责开发order History Service。该服务使用来自ApacheKafka的事件并读取和写入AWS DynamoDB表项。为了运行此服务,它需要各种配置属性,包括Apache Kafka的网络位置以及AWS DynamoDB的访问凭据和网络位置。
这些配置属性的值取决于运行服务的环境。例如,开发环境和生产环境使用的ApacheKafka代理和AWS 访问凭据肯定不同。将特定环境的配置属性值硬写人可部署服务的代码中是没有意义的,因为这些环境都是动态创建的。相反,服务应该由部署流水线构建一次,并自动部署到多个环境中。
将几套可能的配置属性集硬写入源代码中,然后使用例如Spring Framework 的配置文件机制在运行时选择,这样做也没有意义。因为这样做会引入安全漏洞,并限制可以部署的位置。此外,应该使用秘密存储机制安全地存储凭据等敏感数据。因此,你应该使用外部化配置模式为服务在运行时提供适当的配置属性。
外部化配置机制在运行时向服务实例提供配置属性值。主要有两种方法:
- 推送模型:部署基础设施通过类似操作系统环境变量或配置文件,将配置属性传递给服务实例。
- 取模型:服务实例从配置服务器读取它所需要的配置属性。
1、推送模式
Spring Boot从各种来源读取属性。我发现以下来源在微服务架构中很有用:
-
命令行参数。
-
SPRING_APPLICATION_JSON,包含JSON的操作系统环境变量或JVM系统属性。
-
JVM 系统属性。
-
操作系统环境变量。
-
当前目录中的配置文件。
来自此列表中靠前的来源的特定属性值将覆盖此列表中稍后的来源中的相同属性。例如,操作系统环境变量会覆盖从配置文件中读取的属性。
2、拉取模式
有多种方法可以实现配置服务器,包括:
- 版本控制系统,如 Git。
- SQL和 NoSQL数据库。
- 专用配置服务器,例如Spring Cloud Config Server,Hashicorp Vault和AWPS Parameter Store
Spring Cloud Config项目是一个优秀的基于配置服务器的框架。它由服务器和客户端组成。服务器支持各种后端,用于存储配置属性,包括版本控制系统、数据库和 HashicorpVault。客户端从服务器检索配置属性并将它们注入Spring ApplicationContext。
使用配置服务器有几个好处:
- 集中配置:所有配置属性都存储在一个位置,这使它们更易于管理。此外,为了消除重复的配置属性,有些实现允许你定义全局默认值,针对单个服务的值可以覆盖这些默认值。
- 敏感数据的透明解密:加密敏感数据(如数据库访问凭据)是一种安全性最佳实践。但是,使用加密的一个挑战是通常服务实例需要解密它们,这意味着它需要解密密钥。某些配置服务器实现会在将属性返回给服务之前自动对其进行解密。
- 动态重新配置:服务可能会通过轮询等方式检测更新的属性值,并重新配置自身。使用配置服务器的主要缺点是,除非由基础设施提供,否则它需要额外的人力进行设置和运维。幸运的是,有各种开源框架,例如Spring Cloud Config,它使运行配置服务器变得更加容易。
三、如何使用可观测性模式
1、健康检查API
公开返回服务运行状况的接口。应用需要有一个探活接口,用于证明当前应用是否正常。
2、日志聚合分
记录服务活动并将日志写入集中式日志记录服务器,该服务器提供搜索和告警。排除故障,必不可少,如阿里云SLS、ELK搭建日志平台
3、布式跟踪
为每一个在服务之间跳转的外部请求分配唯一ID,并跟踪请求。如鹰眼、Zipkin
4、应用程序指标
服务运维指标,例如计数器和指标,并将它们公开给指标服务器。比如CPU、内存占用率,服务RT、QPS。可以配置相关的应用告警,提前防范风险
5、异常跟踪
向异常跟踪服务报告异常,该异常跟踪服务可以对异常进行重复数据删除,向开发人员发出警报并跟踪每个异常的解决方案
服务很少记录异常,当它发生异常时,确定根本原因很重要。异常可能是失败或编程错误的结果。查看异常的传统方法是查看日志。你甚至可以配置日志记录服务器,以便在日志文件中出现异常时向你发出警报。但是,这种方法存在一些问题:
- 日志文件以单行日志条目为导向,而异常由多行组成。
- 没有机制来追踪日志文件中发生的异常的解决方案。你必须手动将异常复制粘贴到问题追踪器中。
- 可能存在重复的异常,但没有自动机制将它们视为异常。
6、审核日志记录
记录数据库中的用户操作,以帮助客户支持、确保合规性,并检测可疑行为。
不同的实现审计日志:
- 向业务逻辑添加审计日志代码
- 使用面向切面编程
- 使用事件溯源
四、使用微服务基底模式开发服务
1、使用微服务基地
2、从微服务到服务网格
微服务基底是解决各种共性问题的好方法,例如断路器。但使用微服务基底的一个障碍是,你需要确保你使用的编程语言有对应的微服务基底框架或类库。例如,如果你是Java/Spring的开发人员,Spring Boot和 Spring Cloud很有用,但如果你想编写基于Node.js的服务,它们就没有任何帮助。
避免此问题的新兴替代方案是在所谓的服务网格中实现服务之外的某些功能。服务网格是网络基础设施,它调和(mediate)服务与其他服务和外部应用程序之间的通信。进出服务的所有网络流量都通过服务网格。它实现了各种共性的需求:包括断路器、分布式追踪、服务发现、负载均衡和基于规则的流量路由。服务网格还可以通过在服务之间使用基于TLS的机制来保护进程间通信。因此,你不再需要在服务中解决这些特定问题。
使用服务网格后,微服务基底需要担负的责任就少了很多。它只需要实现与应用程序代码紧密集成的问题,例如外部化配置和健康检查。
当前的服务网格实现方案:
- Istio
- Linkerd
- Conduit
五、总结
- 服务首先要实现它的功能性需求,但它也必须是安全、可配置和可观测的。
- 微服务架构中的许多安全问题与单体架构类似。但是应用程序安全性的某些方面必然是不同的,包括如何在API Gateway和服务之间传递用户身份,以及谁负责身份验证和访问授权。常用的方法是API Gateway对客户端进行身份验证。API Gateway在每个服务请求中包括透明令牌,例如JWT。令牌包含主体的身份及其角色。服务使用令牌中的信息来授权访问资源。OAuth 2.0是微服务架构中安全性的良好基础。
- 服务通常使用一个或多个外部服务,例如消息代理和数据库。每个外部服务的网络位置和凭据通常取决于运行服务的环境。你必须应用外部化配置模式并实现一种在运行时提供具有配置属性的服务的机制。一种常用的方法是部署基础设施在创建服务实例时通过操作系统环境变量或属性文件提供这些属性。另一个选择是服务实例从配置属性服务器检索其配置。
- 开发人员和运维人员共同负责实现可观测性模式。运维人员负责可观测性基础设施,例如处理日志聚合、应用指标、异常追踪和分布式追踪的服务器。开发人员有责任确保他们的服务是可观测的。服务必须具有健康检查API端点、生成日志条目、收集和公布指标、向异常追踪服务报告异常,以及实现分布式追踪。
- 为了简化和加速开发,你应该在微服务基底之上开发服务。微服务基底是一个框架或一组框架,用于处理本章描述的各种共性问题。但是,随着时间的推移,微服务基底的许多与网络相关的功能很可能会迁移到服务网格中,服务网格是一层基础设施软件,服务的所有网络流量都将通过服务网格。