Spring微服务实战第7章 保护微服务

第7章 保护微服务

本章主要内容

  • 了解安全在微服务环境中的重要性
  • 认识OAuth2标准
  • 建立和配置基于Spring的OAuth2服务
  • 使用OAuth2执行用户验证和授权
  • 使用OAuth2保护Spring微服务
  • 在服务之间传播OAuth2访问令牌

提到“安全”这个词往往会引起开发人员不由自主地痛苦沉吟。你会听到他们咕哝着低声诅咒:“它迟钝,难以理解,甚至是很难调试。”然而,没有任何开发人员(除了那些没有经验的开发人员)会说他们不担心安全问题。

一个安全的应用程序涉及多层保护,包括:

  • 确保有正确的用户控制,以便可以确认用户是他们所说的人,并且他们有权执行正在尝试执行的操作;
  • 保持运行服务的基础设施是打过补丁且最新的,以让漏洞的风险最低;
  • 实现网络访问控制,让少量已授权的服务器能够访问服务,并使服务只能通过定义良好的端口进行访问。

本章只讨论上述列表中的第一个要点:如何验证调用微服务的用户是他们所说的人,并确定他们是否被授权执行他们从微服务中请求的操作。另外两个主题是非常宽泛的安全主题,超出了本书的范围。

要实现验证和授权控制,我们将使用Spring Cloud Security和OAuth2(Open Authentication)标准来保护基于Spring的服务。OAuth2是一个基于令牌的安全框架,允许用户使用第三方验证服务进行验证。如果用户成功进行了验证,则会出示一个令牌,该令牌必须与每个请求一起发送。然后,验证服务可以对令牌进行确认。OAuth2背后的主要目标是,在调用多个服务来完成用户请求时,用户不需要在处理请求的时候为每个服务都提供自己的凭据信息就能完成验证。Spring Boot和Spring Cloud都提供了开箱即用的OAuth2服务实现,使OAuth2安全能够非常容易地集成到服务中。

注意

本章将介绍如何使用OAuth2保护微服务。不过,一个成熟的OAuth2实现还需要一个前端Web应用程序来输入用户凭据。本章不会讨论如何建立前端应用程序,因为这已经超出了本书关于微服务的范围。作为代替,本章将使用REST客户端(如POSTMAN)来模拟凭据的提交。有关如何配置前端应用程序,我建议读者查看以下Spring教程:https://spring.io/blog/2015/02/03/sso- with-oauth2-angular-js-and-spring-security-part-v。

OAuth2背后真正的强大之处在于,它允许应用程序开发人员轻松地与第三方云服务提供商集成,并使用这些服务进行用户验证和授权,而无须不断地将用户的凭据传递给第三方服务。像Facebook、GitHub和Salesforce这样的云服务提供商都支持将OAuth2作为标准。

在讨论使用OAuth2保护服务的技术细节之前,让我们先看看OAuth2架构。

7.1 OAuth2简介

OAuth2是一个基于令牌的安全验证和授权框架,它将安全性分解为以下4个组成部分。

(1)受保护资源——这是开发人员想要保护的资源(在我们的例子中是一个微服务),需要确保只有已通过验证并且具有适当授权的用户才能访问它。

(2)资源所有者——资源所有者定义哪些应用程序可以调用其服务,哪些用户可以访问该服务,以及他们可以使用该服务完成哪些事情。资源所有者注册的每个应用程序都将获得一个应用程序名称,该应用程序名称与应用程序密钥一起标识应用程序。应用程序名称和密钥的组合是在验证OAuth2令牌时传递的凭据的一部分。

(3)应用程序——这是代表用户调用服务的应用程序。毕竟,用户很少直接调用服务。相反,他们依赖应用程序为他们工作。

(4)OAuth2验证服务器——OAuth2验证服务器是应用程序和正在使用的服务之间的中间人。OAuth2验证服务器允许用户对自己进行验证,而不必将用户凭据传递给由应用程序代表用户调用的每个服务。

这4个组成部分互相作用对用户进行验证。用户只需提交他们的凭据。如果他们成功通过验证,则会出示一个验证令牌,该令牌可在服务之间传递,如图7-1所示。OAuth2是一个基于令牌的安全框架。针对OAuth2服务器,用户通过提供凭据以及用于访问资源的应用程序来进行验证。如果用户凭据是有效的,那么OAuth2服务器就会提供一个令牌,每当用户的应用程序使用的服务试图访问受保护的资源(微服务)时,就可以提交这个令牌。

image-20210908162317274

图7-1 OAuth2允许用户进行验证,而不必持续提供凭据

接下来,受保护资源可以联系OAuth2服务器以确定令牌的有效性,并检索用户授予它们的角色。角色用于将相关用户分组在一起,并定义用户组可以访问哪些资源。对于本章来说,我们将使用OAuth2和角色来定义用户可以调用哪些服务端点,以及用户可以在端点上调用的HTTP动词。

Web服务安全是一个极其复杂的主题。开发人员必须了解谁将调用自己的服务(公司网络的内部用户还是外部用户),他们将如何调用这些服务(是在内部基于Web客户端、移动设备还是在企业网络之外的Web应用程序),以及他们用代码来完成什么操作。OAuth2允许开发人员使用称为授权(grant)的不同验证方案,在不同的场景中保护基于REST的服务。OAuth2规范具有以下4种类型的授权:

  • 密码(password);
  • 客户端凭据(client credential);
  • 授权码(authorization code);
  • 隐式(implicit)。

本书不会逐一介绍每种授权类型,或者为每种授权类型提供代码示例。究其原因,仅仅是因为需要包含在一章里的内容太多了。取而代之,本章将会完成以下事情:

  • 讨论微服务如何通过一个较简单的OAuth2授权类型(密码授权类型)来使用OAuth2;
  • 使用JSON Web Token来提供一个更健壮的OAuth2解决方案,并在OAuth2令牌中建立一套信息编码的标准;
  • 介绍在构建微服务时需要考虑的其他安全注意事项。

本书在附录B中会提供其他OAuth2授权类型的概述资料。如果读者有兴趣详细了解OAuth2规范以及如何实现所有授权类型,强烈推荐Justin Richer和Antonio Sanso的著作《OAuth2 in Action》,这是对OAuth2的全面解读。

7.2 从小事做起:使用Spring和OAuth2来保护单个端点

为了了解如何建立OAuth2的验证和授权功能,我们将实现OAuth2密码授权类型。要实现这一授权,我们将执行以下操作。

  • 建立一个基于Spring Cloud的OAuth2验证服务。
  • 注册一个伪EagleEye UI应用程序作为一个已授权的应用程序,它可以通过OAuth2服务验证和授权用户身份。
  • 使用OAuth2密码授权来保护EagleEye服务。我们不会为EagleEye构建UI,而是使用POSTMAN模拟登录的用户对EagleEye OAuth2服务进行验证。
  • 保护许可证服务和组织服务,使它们只能被已通过验证的用户调用。

7.2.1 建立EagleEye OAuth2验证服务

就像本书中所有的例子一样,OAuth2验证服务将是另一个Spring Boot服务。验证服务将验证用户凭据并颁发令牌。每当用户尝试访问由验证服务保护的服务时,验证服务将确认OAuth2令牌是否已由其颁发并且尚未过期。这里的验证服务等同于图7-1中的验证服务。

开始时,需要完成以下两件事。

(1)添加引导类所需的适当Maven构建依赖项。

(2)添加一个将作为服务的入口点的引导类。

读者可以在authentication-service目录中找到验证服务的所有代码示例。要建立OAuth2验证服务器,需要在authentication-service/pom.xml文件中添加以下Spring Cloud依赖项:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-security</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.security.oauth</groupId>
  <artifactId>spring-security-oauth2</artifactId>
</dependency>

第一个依赖项spring-cloud-security引入了通用Spring和Spring Cloud安全库。第二个依赖项spring-security-oauth2拉取了Spring OAuth2库。

既然已经定义完Maven依赖项,那么就可以在引导类上进行工作。这个引导类可以在authentication-service/src/main/java/com/thoughtmechanix/authentication/Application.java中找到。代码清单7-1展示Application类的代码。

代码清单7-1 authentication-service的引导类

// 为了简洁,省略了import语句
@SpringBootApplication
@RestController
@EnableResourceServer
@EnableAuthorizationServer  ⇽--- 用于告诉Spring Cloud,该服务将作为OAuth2服务
public class Application {
   

    @RequestMapping(value = {
    "/user" }, produces = "application/json")  ⇽--- 在本章稍后用于检索有关用户的信息
    public Map<String, Object> user(OAuth2Authentication user) {
   
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("user",
        ➥  user.getUserAuthentication().getPrincipal());
        userInfo.put("authorities",AuthorityUtils.authorityListToSet(
            ➥  user.getUserAuthentication().getAuthorities()));
        return userInfo;
    }

    public static void main(String[] args) {
   
        SpringApplication.run(Application.class, args);
    }
}

在代码清单7-1中,要注意的第一样东西是@EnableAuthorizationServer注解。这个注解告诉Spring Cloud,该服务将用作OAuth2服务,并添加几个基于REST的端点,这些端点将在OAuth2验证和授权过程中使用。

在代码清单7-1中,看到的第二件事是添加了一个名为/user(映射到/auth/user)的端点。当试图访问由OAuth2保护的服务时,将会用到这个端点,本章后文会进行介绍。此端点由受保护服务调用,以确认OAuth2访问令牌,并检索访问受保护服务的用户所分配的角色。本章稍后会详细讨论这个端点。

7.2.2 使用OAuth2服务注册客户端应用程序

此时,我们已经有了一个验证服务,但尚未在验证服务器中定义任何应用程序、用户或角色。我们可以从已通过验证服务注册EagleEye应用程序开始。为此,我们将在验证服务中创建一个名为OAuth2Config的类(在authentication-service/src/main/java/com/thoughtmechanix/authentication/ security/OAuth2Config.java中)。

这个类将定义通过OAuth2验证服务注册哪些应用程序。需要注意的是,不能只因为应用程序通过OAuth2服务中注册过,就认为该服务能够访问任何受保护资源。

验证与授权

我经常发现开发人员混淆术语验证(authentication)和授权(authorization)的含义。验证是用户通过提供凭据来证明他们是谁的行为。授权决定是否允许用户做他们想做的事情。例如,Jim可以通过提供用户ID和密码来证明他的身份,但是他可能没有被授权查看敏感数据,如工资单数据。出于我们讨论的目的,必须在授权发生之前对用户进行验证。

OAuth2Config类定义了OAuth2服务知道的应用程序和用户凭据。在代码清单7-2中可以看到OAuth2Config类的代码。

代码清单7-2 OAuth2Config服务定义哪些应用程序可以使用服务

// 为了简洁,省略了import语句
@Configuration  ⇽--- 继承AuthorizationServer- ConfigurerAdapter类,并使用@Configuration注解标注这个类
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
   

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override  ⇽--- 覆盖configure()方法。这定义了哪些客户端将注册到服务
    public void configure(ClientDetailsServiceConfigurer clients) throwsException {
   
        clients.inMemory()
             .withClient("eagleeye")
             .secret("thisissecret")
             .authorizedGrantTypes("refresh_token","password","client_credentials")
             .scopes("webclient","mobileclient");
    }

    @Override  ⇽--- 该方法定义了AuthenticationServerConfigurer中使用的不同组件。这段代码告诉Spring使用Spring提供的默认验证管理器和用户详细信息服务
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)throws Exception {
   
        endpoints
            .authenticationManager(authenticationManager)
            .userDetailsService(userDetailsService);
      }
}

在代码清单7-2所示的代码中,要注意的第一件事是,这个类扩展了Spring的AuthenticationServerConfigurer类,然后使用@Configuration注解对这个类进行了标记。AuthenticationServerConfigurer类是Spring Security的核心部分,它提供了执行关键验证和授权功能的基本机制。对于OAuth2Config类,我们将要覆盖两个方法。第一个方法是configure(),它用于定义通过验证服务注册了哪些客户端应用程序。configure()方法接受一个名为clientsClientDetailsServiceConfigurer类型的参数。让我们来更详细地了解一下configure()方法中的代码。在这个方法中做的第一件事是注册哪些客户端应用程序允许访问由OAuth2服务保护的服务。这里使用了最广泛的术语“访问”(access),因为我们通过检查调用服务的用户是否有权采取他们正在尝试的操作,控制了客户端应用程序的用户以后可以做什么。

clients.inMemory()
    .withClient("eagleeye")
    .secret("thisissecret")
    .authorizedGrantTypes("password","client_credentials")
    .scopes("webclient","mobileclient");

对于应用程序的信息,ClientDetailsServiceConfigurer类支持两种不同类型的存储:内存存储和JDBC存储。对本例来说,我们将使用clients.inMemory()存储。

withClient()secret()这两个方法提供了注册的应用程序的名称(eagleeye)以及密钥(一个密码,thisissecret),该密钥在EagleEye应用程序调用OAuth2服务器以接收OAuth2访问令牌时提供。

下一个方法是authorizedGrantTypes(),它被传入一个以逗号分隔的授权类型列表,这些授权类型将由OAuth2服务支持。在这个服务中,我们将支持密码授权类型和客户端凭据授权类型。

scopes()方法用于定义调用应用程序在请求OAuth2服务器获取访问令牌时可以操作的范围。例如,ThoughtMechanix可能提供同一应用程序的两个不同版本:基于Web的应用程序和基于手机的应用程序。在这些应用程序中都可以使用相同的客户端名称和密钥来请求对OAuth2服务器保护的资源的访问。然而,当应用程序请求一个密钥时,它们需要定义它们所操作的特定作用域。通过定义作用域,可以编写特定于客户端应用程序所工作的作用域的授权规则。

例如,可能有一个用户使用基于Web的客户端和手机应用程序来访问EagleEye应用程序。EagleEye应用程序的每个版本都:

(1)提供相同的功能;

(2&#x

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值