vlambda博客
学习文章列表

读书笔记《spring-security-third-edition》使用 OAuth 2 和 JSON Web 令牌实现微服务安全

使用 OAuth 2 和 JSON Web 令牌实现微服务安全

在本章中,我们将了解基于微服务的架构,并了解带有 JSON Web 令牌 (JWT) 的 OAuth 2 如何在 Spring 中保护微服务方面发挥作用- 基于应用程序。

以下是本章将涉及的主题列表:

  • The general difference between monolithic applications and microservices
  • Comparing service-oriented architectures (SOA) with microservices
  • The conceptual architecture of OAuth 2 and how it provides your services with trustworthy client access
  • Types of OAuth 2 access tokens
  • Types of OAuth 2 grant types
  • Examining JWT and their general structure
  • Implementing a resource server and authentication server used to grant access rights to clients in order to access OAuth 2 resources
  • Implementing a RESTful client to gain access to resources through an OAuth 2 grant flow

本章有很多内容要介绍,但在深入了解如何开始利用 Spring Security 实现 OAuth 2 和 JWT 之前,我们首先要创建一个没有 Thymeleaf 或任何其他基于浏览器的用户界面。

删除所有 Thymeleaf 配置和资源后,各种控制器已转换为 JAX-RS REST 控制器。

您应该从 chapter16.00-calendar 中的代码开始。

什么是微服务?

微服务是一种架构方法,允许开发物理上分离的模块化应用程序,这些应用程序是自治的,支持敏捷性、快速开发、持续部署和扩展。

应用程序构建为一组服务,类似于 SOA,这样服务通过标准 API(例如 JSON 或 XML)进行通信,这允许聚合与语言无关的服务。基本上,可以用最适合创建服务的任务的语言编写服务。

每个服务都在自己的进程中运行并且位置中立,因此它可以位于接入网络上的任何位置。

巨石

微服务方法与传统的单体软件方法相反,后者由紧密集成的模块组成,这些模块不经常发布,并且必须作为一个单元进行扩展。本书中的传统 Java EE 应用程序和 JBCP 日历应用程序是单体应用程序的示例。请看下图,它描述了单体架构:

读书笔记《spring-security-third-edition》使用 OAuth 2 和 JSON Web 令牌实现微服务安全

尽管单体方法非常适合某些组织和某些应用程序,但微服务正受到需要在其生态系统中获得更多敏捷性和可扩展性选项的公司的欢迎。

微服务

微服务架构是小型离散服务的集合,其中每个服务实现特定的业务能力。这些服务运行它们自己的进程并通过通常使用 RESTful 服务方法的 HTTP API 进行通信。这些服务被创建为仅服务于一个特定的业务功能,例如用户管理、管理角色、电子商务购物车、搜索引擎、社交媒体集成等等。请看下图,它描述了微服务架构:

读书笔记《spring-security-third-edition》使用 OAuth 2 和 JSON Web 令牌实现微服务安全

每个服务都可以独立于应用程序中的其他服务和企业中的其他系统进行部署、升级、扩展、重新启动和删除。

因为每个服务都是独立创建的,所以它们都可以用不同的编程语言编写并使用不同的数据存储。集中式服务管理几乎不存在,这些服务使用轻量级 HTTP、REST 或 Thrift API 在它们之间进行通信。

Apache Thrift 软件框架可从以下网址下载 https://thrift.apache.org。它是一个用于开发可扩展跨语言服务的框架,它将软件堆栈与代码生成引擎相结合,以构建在 C++、Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、 JavaScript、Node.js、Smalltalk 和其他语言。

面向服务的架构

您可能会问自己,“这与 SOA 不一样吗?”不完全是,你可以说微服务首先实现了 SOA 的承诺。

SOA 是一种软件设计风格,其中服务通过计算机网络上与语言无关的通信协议向其他组件公开。

SOA 的基本原则是独立于供应商、产品和技术。

服务的定义是一个独立的功能单元,可以远程访问并独立执行和更新,例如在线检索信用卡对帐单。

尽管相似,SOA 和微服务仍然是不同类型的架构。

典型的 SOA 通常在部署单体内部实现,并且更受平台驱动,而微服务可以独立部署,因此在各个方面都提供更大的灵活性。

当然,关键的区别在于尺寸。微词说明了一切。微服务往往比常规 SOA 服务小得多。正如马丁·福勒所说:

"We should think about SOA as a superset of microservices."
Martin Fowler

微服务安全

微服务可以提供很大的灵活性,但也带来了必须解决的挑战。

服务沟通

单体应用程序使用进程之间的内存通信,而微服务通过网络进行通信。转向网络通信不仅引发了速度问题,而且还引发了安全问题。

紧耦合

微服务使用许多数据存储而不是少数。这为微服务和紧密耦合的服务之间的隐式服务契约创造了机会。

技术复杂性

微服务会产生额外的复杂性,从而产生安全漏洞。如果团队没有正确的经验,那么管理这些复杂性很快就会变得难以管理。

OAuth 2 规范

有时会有一种误解,认为 OAuth 2 是 OAuth 1 的演变,但它是一种完全不同的方法。 OAuth1 规范需要签名,因此您必须使用加密算法来创建和验证 OAuth 2 不再需要的签名。OAuth 2 加密现在由 TLS 处理,这是必需的。

OAuth 2 RFC-6749, OAuth 2.0 授权框架 ( https://tools.ietf.org/html/rfc6749):
OAuth 2.0 授权框架使第三方应用程序能够通过协调资源所有者和 HTTP 服务之间的批准交互,或者通过允许第三方应用程序代表资源所有者来获得对 HTTP 服务的有限访问权限。方申请以自己的名义获取访问权限

本规范取代并废弃了 OAuth 1.0 协议中描述的 RFC 5849,OAuth 1.0 协议 ( https://tools.ietf.org/html/rfc5849)。

为了正确理解如何使用 OAuth 2,我们需要识别某些角色以及这些角色之间的协作。让我们定义参与 OAuth 2 授权过程的每个角色:

  • Resource owner: The resource owner is the entity capable of granting access to a protected resource that is located on a resource server
  • Authorization server: The authorization server is a centralized security gateway for issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorization
  • Resource server: The resource server is the server hosting the protected resources and is capable of dissecting and responding to protected resource requests using the OAuth 2 access token
  • Microservice client: The client is the application making resource requests that are protected on behalf of the resource owner, but with their authorization

访问令牌

OAuth 2 访问令牌,在代码示例中通常称为 access_token,表示客户端可以用来访问 API 的凭据。

访问令牌

访问令牌通常具有有限的生命周期,当在每个请求的 HTTP 请求标头中包含此令牌时,用于使客户端能够访问受保护的资源。

刷新令牌

刷新令牌的生命周期更长,用于在访问令牌过期后获取新的访问令牌,但无需再次向服务器发送凭据。

资助类型

授权类型是客户端可以用来获取代表所授予权限的访问令牌的方法。根据应用程序的需要,有不同的授权类型允许不同类型的访问。每种授权类型都可以支持不同的 OAuth 2 流程,而无需担心实施的技术方面。

授权码

授权代码授予类型,在 RFC 6749第 4.1 节 (https://tools.ietf.org/html/rfc6749),是一个基于重定向的流程,浏览器从授权服务器接收授权代码并将其发送给客户端。然后,客户端将与授权服务器交互并交换 access_token 的授权代码,以及可选的 id_tokenrefresh_token。客户端现在可以使用这个 access_token 来代表用户调用受保护的资源。

隐式

隐式授权类型,在 RFC 6749第 4.1 节 (https://tools.ietf.org/html/rfc6749),类似于授权码授权类型,但是客户端应用直接接收access_token,不需要< kbd>授权码。发生这种情况是因为客户端应用程序(通常是在浏览器中运行的 JavaScript 应用程序,其信任度低于在服务器上运行的客户端应用程序)无法通过 client_secret 来信任(授权代码中需要这样做)授予类型)。由于信任有限,隐式授权类型不会向应用程序发送刷新令牌。

密码凭据

资源所有者密码授予类型,在 RFC 6749第 4.3 节 (https://tools.ietf.org/html/rfc6749),可直接用作授权授予来获取 access_token 和可选的 refresh_token< /kbd>。当用户和客户端之间存在高度信任以及其他授权授权流不可用时,使用此授权。这种授权类型消除了客户端通过与长期 access_tokenrefresh_token 交换凭据来存储用户凭据的需要。

客户端凭据

客户端凭据授予,定义在 RFC 6749第 4.4 节 (https://tools.ietf.org/html/rfc6749#section-4.4),用于非交互式客户端 (CLI)、守护进程或其他正在运行的服务。客户端可以使用客户端提供的凭据(客户端 ID 和客户端密码)直接向授权服务器请求 access_token 进行身份验证。

JSON 网络令牌

JWT 是一个开放标准,RFC 7519 (https://tools.ietf.org /html/rfc7519),它定义了一种紧凑且自包含的格式,用于以 JSON 对象的形式在各​​方之间安全地传输信息。此信息可以验证和信任,因为它是数字签名的。 JWT 可以使用密钥(使用基于哈希的消息身份验证代码 (HMAC) 算法)或公钥/私钥对使用Rivest–Shamir–Adleman (RSA) 加密算法。

智威汤逊 RFC-7519 ( https://tools.ietf.org/html/rfc7519):
JSON Web Token (JWT) 是一种紧凑的、URL 安全的方式,用于表示要在两方之间传输的声明。 JWT 中的声明被编码为 JSON 对象,该对象用作 JSON Web 签名 (JWS ) 结构或作为 JSON Web 加密 (JWE) 结构的明文,使声明能够通过消息身份验证代码 (MAC) 进行数字签名或完整性保护和/或加密。

JWT 用于携带与持有令牌的客户端的身份和特征(声明)相关的信息。 JWT 是一个容器,由服务器签名以避免客户端篡改。此令牌在身份验证过程中创建,并在任何处理之前由授权服务器验证。资源服务器使用它来允许客户端向资源服务器提供代表其“身份证”的令牌,并允许资源服务器以无状态、安全的方式验证令牌的有效性和完整性。

代币结构

JWT 的结构遵循以下三部分结构,包括标头、有效负载和签名:

    [Base64Encoded(HEADER)] . [Base64Encoded (PAYLOAD)] . [encoded(SIGNATURE)]

编码的 JWT

以下代码片段是根据客户端请求返回的完整编码 access_token

     eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDk2MTA2ODks
InVzZXJfbmFtZSI6InVzZXIxQGV4YW1wbGUuY29tIiwiYXV0aG9yaXRpZXMiOlsi
Uk9MRV9VU0VSIl0sImp0aSI6Ijc1NTRhZGM4LTBhMjItNDBhYS05YjQ5LTU4MTU2N
DBhNDUzNyIsImNsaWVudF9pZCI6Im9hdXRoQ2xpZW50MSIsInNjb3BlIjpb
Im9wZW5pZCJdfQ.iM5BqXj70ET1e5uc5UKgws1QGDv6NNZ4iVEHimsp1Pnx6WXuFwtpHQoerH_F-
pTkbldmYWOwLC8NBDHElLeDi1VPFCt7xuf5Wb1VHe-uwslupz3maHsgdQNGcjQwIy7_U-
SQr0wmjcc5Mc_1BWOq3-pJ65bFV1v2mjIo3R1TAKgIZ091WG0e8DiZ5AQase
Yy43ofUWrJEXok7kUWDpnSezV96PDiG56kpyjF3x1VRKPOrm8CZuylC57wclk-
BjSdEenN_905sC0UpMNtuk9ENkVMOpa9_Redw356qLrRTYgKA-qpRFUpC-3g5
CXhCDwDQM3jyPvYXg4ZW3cibG-yRw

标题

我们的 access_token JWT 的编码标头是 base64 编码的,如以下代码所示:

    eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9

通过解码编码的标头,我们有以下有效负载:

    {
"alg": "RS256",
"typ": "JWT"
}

有效载荷

我们的 access_token JWT 的编码负载是 base64 编码的,如下所示:

    eyJleHAiOjE1MDk2MTA2ODksInVzZXJfbmFtZSI6InVzZXIxQGV4YW1wbGUuY29
tIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6Ijc1NTR
hZGM4LTBhMjItNDBhYS05YjQ5LTU4MTU2NDBhNDUzNyIsImNsaWVudF9pZCI6I
m9hdXRoQ2xpZW50MSIsInNjb3BlIjpbIm9wZW5pZCJdfQ

通过解码编码的有效载荷,我们有以下有效载荷声明:

    {
"exp": 1509610689,
"jti": "7554adc8-0a22-40aa-9b49-5815640a4537",
"client_id": "oauthClient1",
"authorities": [
"ROLE_USER"
],
"scope": [
"openid"
],
"user_name": "[email protected]"
}

签名

我们的 access_token 的编码有效负载已由授权服务器使用私钥进行编码,如以下代码所示:

    iM5BqXj70ET1e5uc5UKgws1QGDv6NNZ4iVEHimsp1Pnx6WXuFwtpHQoerH_F-          
pTkbldmYWOwLC8NBDHElLeDi1VPFCt7xuf5Wb1VHe-uwslupz3maHsgdQNGcjQwIy7_U-
SQr0wmjcc5Mc_1BWOq3-pJ65bFV1v2mjIo3R1TAKgIZ091WG0e8DiZ5AQaseYy43ofUWrJEXok7kUWDpn
SezV96PDiG56kpyjF3x1VRKPOrm8CZuylC57wclk-
BjSdEenN_905sC0UpMNtuk9ENkVMOpa9_Redw356qLrRTYgKA-qpRFUp
C-3g5CXhCDwDQM3jyPvYXg4ZW3cibG-yRw

以下是创建 JWT 签名的伪代码:

    var encodedString = base64UrlEncode(header) + ".";
encodedString += base64UrlEncode(payload);
var privateKey = “[-----PRIVATE KEY-----]”;
var signature = SHA256withRSA(encodedString, privateKey);
var JWT = encodedString + "." + base64UrlEncode(signature);

Spring Security 中的 OAuth 2 支持

Spring Security OAuth 项目支持使用标准 Spring Framework 和 Spring Security 编程模型和配置习惯用法和 OAuth 2 授权来使用 Spring Security。

资源所有者

资源所有者可以是一个或多个来源,并且在 JBCP 日历的上下文中,它将日历应用程序作为资源所有者。除了配置资源服务器之外,JBCP 日历不会有任何特定的配置来表示其所有权。

资源服务器

@EnableResourceServer 注释表示包含应用程序启用 Spring Security 过滤器的意图,该过滤器通过传入的 OAuth2 令牌对请求进行身份验证:

    //src/main/java/com/packtpub/springsecurity/configuration/
OAuth2ResourceServerConfig.java

@EnableResourceServer
public class OAuth2ResourceServerConfig
extends ResourceServerConfigurerAdapter {...}

@EnableResourceServer 注释表示包含应用程序启用 OAuth2AuthenticationProcessingFilter 过滤器的意图,该过滤器通过传入的 OAuth 2 令牌对请求进行身份验证。 OAuth2AuthenticationProcessingFilter 过滤器要求使用应用程序中某处的 @EnableWebSecurity 注释启用 Web 安全性。 @EnableResourceServer 注释注册了一个自定义 WebSecurityConfigurerAdapter 类,其中包含 3 的硬编码 @Order。由于 Spring Framework 的技术限制,目前无法更改此 WebSecurityConfigurerAdapter 类的顺序。为了解决这个限制,建议不要使用顺序为 3 的其他安全适配器,如果您按照相同的顺序设置了一个,Spring Security 会报错:

//o.s.s.OAuth 2.config.annotation.web.configuration.ResourceServerConfiguration.class

@Configuration
public class ResourceServerConfiguration
extends WebSecurityConfigurerAdapter implements Ordered {
private int order = 3;
...
}

授权服务器

为了启用授权服务器功能,我们在配置中包含 @EnableAuthorizationServer 注释。添加此注释会将 o.s.s.OAuth 2.provider.endpoint.AuthorizationEndpoint 接口和 o.s.s.OAuth 2.provider.endpoint.TokenEndpoint 接口放入上下文中。开发人员将负责使用 @EnableWebSecurity 配置保护 AuthorizationEndpoint (/oauth/authorize)。 TokenEndpoint (/oauth/token) 将使用基于 OAuth 2 客户端凭据的 HTTP 基本身份验证自动保护:

    //src/main/java/com/packtpub/springsecurity/configuration/
OAuth2AuthorizationServerConfig.java


@Configuration
@EnableAuthorizationServer
public class OAuth 2AuthorizationServerConfig {...}

RSA JWT 访问令牌转换器密钥对

为了创建一个安全的 JWT 编码签名,我们将创建一个自定义 RSA keystore,我们将使用它来创建一个自定义 o.s.s.OAuth 2.provider.token.storeJwtAccessTokenConverter 接口:

$ keytool -genkey -alias jbcpOAuth 2client -keyalg RSA \
-storetype PKCS12 -keystore jwtConverterStore.p12 \
-storepass changeit \
-dname "[email protected],OU=JBCP Calendar,O=JBCP,L=Park City,S=Utah,C=US"

这将创建一个名为 jwtConverterStore.p12PKCS12 证书,需要将其复制到 ./src/main/resources/key 目录中。

OAuth 2 资源配置属性

我们希望通过提供 keyPair 属性来外部化配置 JWT 资源所需的属性,包括 keystorealiasstorePassword 用于我们生成的证书,您可以在我们的 application.yml 文件 src/main/resources/application.yml 中看到:

    # OAuth 2 Configuration:
security:
OAuth 2:
# Resource Config:
resource:
jwt:
keyPair:
keystore: keys/jwtConverterStore.p12
alias: jbcpOAuth 2client
storePassword: changeit

OAuth 2 客户端配置属性

我们需要为客户端身份验证、授权和 OAuth 2 范围配置客户端详细信息,您可以在 application.yml 文件 src/main/resources/application.yml< 中看到/kbd>:

# OAuth 2 Configuration:
security:
OAuth 2:
# Client Config:
client:
# Basic Authentication credentials for OAuth 2
clientId: oauthClient1
clientSecret: oauthClient1Password
authorizedGrantTypes: password,refresh_token
scope: openid

JWT 访问令牌转换器

创建 JWT 令牌的最后一步是创建一个自定义 JwtAccessTokenConverter,它将使用生成的 RSA 证书作为我们的 JWT 签名。为此,我们需要提取我们的 keyPair 配置并配置自定义 JwtAccessTokenConverter,如 OAuth2AuthorizationServerConfig.java 文件中所示:

    //src/main/java/com/packtpub/springsecurity/configuration/
OAuth2AuthorizationServerConfig.java


public class OAuth2AuthorizationServerConfig {
@Value("${security.OAuth 2.resource.jwt.keyPair.keystore}")
private String keystore;
@Value("${security.OAuth 2.resource.jwt.keyPair.alias}")
private String keyPairAlias;
@Value("${security.OAuth 2.resource.jwt.keyPair.storePassword}")
private String keyStorePass;
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new
JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory
(new ClassPathResource(keystore),
keyStorePass.toCharArray() ).getKeyPair(keyPairAlias);
converter.setKeyPair(keyPair);
return converter;
}
}

UserDetailsS​​ervice 对象

我们将使用 CalendarUser 凭据将授权的 GrantedAuthority 分配给客户端。为此,我们必须配置我们的 CalendarUserDetailsS​​ervice 类或使用名称 userDetailsS​​ervice 对其进行限定,如以下 CalendarUserDetailsS​​ervice.java 文件:

    //src/main/java/com/packtpub/springsecurity/core/userdetails/
CalendarUserDetailsService.java

@Component("userDetailsService")
public class CalendarUserDetailsService
implements UserDetailsService {...}

为我们的 @Component 注释定义自定义名称的另一种替代方法是定义一个 @Bean 声明,我们可以使用 SecurityConfig.java 中的以下条目来完成 文件:

    //src/main/java/com/packtpub/springsecurity/configuration/SecurityConfig.java

@Bean
public CalendarUserDetailsService userDetailsService
(CalendarUserDao calendarUserDao) {
return new CalendarUserDetailsService(calendarUserDao);
}

运行 OAuth 2 服务器应用程序

此时,我们可以启动应用程序,我们将准备好发送 OAuth 2 请求。

此时,您的代码应如下所示:chapter16.01-calendar.

服务器请求

我们可以使用命令行工具(例如 cURLHTTPie)来测试应用程序,或者您也可以使用 REST 客户端插件(例如 Postman)向服务器发送请求.

HTTPie:一个 CLI,类似 cURL 的人类工具, HTTPie(读作 aitch-tee-tee-pie)是一个命令行 HTTP 客户端。它的目标是使 CLI 与 Web 服务的交互尽可能人性化。它提供了一个简单的 HTTP 命令,允许使用简单自然的语法发送任意 HTTP 请求,并显示彩色输出。 HTTPie 可用于测试、调试和一般与 HTTP 服务器交互( https://httpie.org)。

令牌请求

当我们发出初始令牌请求时,我们应该得到类似于以下内容的成功响应:

    $ http -a oauthClient1:oauthClient1Password -f POST
localhost:8080/oauth/token
grant_type=password [email protected] password=user1
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Cache-Control: no-store
Content-Type: application/json;charset=UTF-8
Date: Thu, 09 Nov 2017 20:29:26 GMT
Expires: 0
Pragma: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
X-Application-Context: application:default
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MT
AzMDI1NjYsInVzZXJfbmFtZSI6InVzZXIxQGV4YW1wbGUuY29tIiwiYXV0aG9yaXRpZ
XMiOlsiUk9MRV9VU0VSIl0sImp0aSI6ImYzNzYzMWI4LWI0OGEtNG
Y1MC1iNGQyLTVlNDk1NTRmYzZjZSIsImNsaWVudF9pZCI6Im9hdXRoQ
2xpZW50MSIsInNjb3BlIjpbIm9wZW5pZCJdfQ.d5I2ZFX9ia_43eeD5X3JO6i_uF1Zw-
SaZ1CWbphQlYI3oCq6Xr9Yna5fvvosOZoWjb8pyo03EPVCig3mobhO6AF
18802XOlBRx3qb0FGmHZzDoPw3naTDHlhE97ctlIFIcuJVqi34T60cvii
uXmcE1tJ-H6-7AB04-wZl_WaucoO8-K39GvPyVabWBfSpfv0nbhh_XMNiB
PnN8u5mqSKI9xGjYhjxXspRyy--
zXx50Nqj1aYzxexy8Scawrtt2F87o1IesOodoPEQGTgVVieIilplwkMLhMvJfxhyMOt
ohR63XOGBSI4dDz58z3zOlk9P3k2Uq5FmkqwNNkduKceSw","expires_in": 43199,
"jti": "f37631b8-b48a-4f50-b4d2-5e49554fc6ce","refresh_token":
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyM
UBleGFtcGxlLmNvbSIsInNjb3BlIjpbIm9wZW5pZCJdLCJhdGkiOiJmMzc2MzF
iOC1iNDhhLTRmNTAtYjRkMi01ZTQ5NTU0ZmM2Y2UiLCJleHAiOjE1MTI4NTEzNjYs
ImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiJjODM2OGI4NS0xNTk5L
TQ0NTgtODQ2Mi1iNGFhNDg1OGIzY2IiLCJjbGllbnRfaWQiOiJvYXV0aENsaWVudDEifQ.
RZJ2GbEvcmFbZ3SVHmtFnSF_O2kv-
TmN56tddW2GkG0gIRr612nN5DVlfWDKorrftmmm64x8bxuV2CcFx8Rm4SSWuoYv
j4oxMXZzANqXWLwj6Bei4z5uvuu00g6PtJvy5Twjt7GWCvEF82PBoQL-
bTM3RNSKmPnYPBwOGaRFTiSTdKsHCcbrg-
H84quRKCjXTl7Q6l8ZUxAf1eqWlOYEhRiGHtoULzdOvL1_W0OoWrQds1EN5g
AuoTTSI3SFLnEE2MYu6cNznJFgTqmVs1hYmX1hiXUhmCq9nwYpWei-
bu0MaXCa9LRjDRl9E6v86vWJiBVzd9qQilwTM2KIvgiG7w", "scope": "openid",
"token_type": "bearer"
}

具体来说,我们已获得可在后续请求中使用的访问令牌。以下是将用作我们的承载的 access_token

    eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTAzMDI1
NjYsInVzZXJfbmFtZSI6InVzZXIxQGV4YW1wbGUuY29tIiwiYXV0aG9yaXRpZXM
iOlsiUk9MRV9VU0VSIl0sImp0aSI6ImYzNzYzMWI4LWI0OGEtNGY1MC1iNGQyL
TVlNDk1NTRmYzZjZSIsImNsaWVudF9pZCI6Im9hdXRoQ2xpZW50MSIsInNjb
3BlIjpbIm9wZW5pZCJdfQ.d5I2ZFX9ia_43eeD5X3JO6i_uF1Zw-
SaZ1CWbphQlYI3oCq6Xr9Yna5fvvosOZoWjb8pyo03EPVCig3mobhO6AF18802XO
lBRx3qb0FGmHZzDoPw3naTDHlhE97ctlIFIcuJVqi34T60cviiuXmcE1tJ-H6-7AB04-wZl_WaucoO8-
K39GvPyVabWBfSpfv0nbhh_XMNiBPnN8u5mqSKI9xGjYhjxXspRyy--
zXx50Nqj1aYzxexy8Scawrtt2F87o1IesOodoPEQGTgVVieIilplwkMLhMvJfxhyMOto
hR63XOGBSI4dDz58z3zOlk9P3k2Uq5FmkqwNNkduKceSw

现在我们将获取 access_token 并使用该令牌以以下格式向服务器发起附加请求:

$ http localhost:8080/ "Authorization: Bearer [access_token]"

当添加我们在第一个请求中收到的 access_token 时,我们应该得到以下请求:

    $ http localhost:8080/ 'Authorization: Bearer    
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTAzMD
I1NjYsInVzZXJfbmFtZSI6InVzZXIxQGV4YW1wbGUuY29tIiwiYXV0aG9yaXRp
ZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6ImYzNzYzMWI4LWI0OGEtNGY1MC1iNGQyLT
VlNDk1NTRmYzZjZSIsImNsaWVudF9pZCI6Im9hdXRoQ2xpZW50MSIsInNjb3BlIjpb
Im9wZW5pZCJdfQ.d5I2ZFX9ia_43eeD5X3JO6i_uF1Zw-
SaZ1CWbphQlYI3oCq6Xr9Yna5fvvosOZoWjb8pyo03EPVCig3mobhO6AF18802XOl
BRx3qb0FGmHZzDoPw3naTDHlhE97ctlIFIcuJVqi34T60cviiuXmcE1tJ-H6-7AB04-wZl_WaucoO8-
K39GvPyVabWBfSpfv0nbhh_XMNiBPnN8u5mqSKI9xGjYhjxXspRyy--
zXx50Nqj1aYzxexy8Scawrtt2F87o1IesOodoPEQGTgVVieIilplwkMLhMvJf
xhyMOtohR63XOGBSI4dDz58z3zOlk9P3k2Uq5FmkqwNNkduKceSw'
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 55
Content-Type: text/plain;charset=UTF-8
Date: Thu, 09 Nov 2017 20:44:00 GMT
Expires: 0
Pragma: no-cache
X-Application-Context: application:default
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

{'message': 'welcome to the JBCP Calendar Application'}

我们可以继续使用相同的 access_token 进行后续请求,例如检索当前用户的事件:

    $ http localhost:8080/events/my 'Authorization: Bearer    
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTAzMDI1NjYsI
nVzZXJfbmFtZSI6InVzZXIxQGV4YW1wbGUuY29tIiwiYXV0aG9yaXRpZXMiOlsiU
k9MRV9VU0VSIl0sImp0aSI6ImYzNzYzMWI4LWI0OGEtNGY1MC1iNGQyLTVlNDk1NT
RmYzZjZSIsImNsaWVudF9pZCI6Im9hdXRoQ2xpZW50MSIsInNjb3BlIjpbIm9wZW5pZ
CJdfQ.d5I2ZFX9ia_43eeD5X3JO6i_uF1Zw-
SaZ1CWbphQlYI3oCq6Xr9Yna5fvvosOZoWjb8pyo03EPVCig3mobhO6AF18802XO
lBRx3qb0FGmHZzDoPw3naTDHlhE97ctlIFIcuJVqi34T60cviiuXmcE1tJ-H6-7AB04-wZl_WaucoO8-
K39GvPyVabWBfSpfv0nbhh_XMNiBPnN8u5mqSKI9xGjYhjxXspRyy--
zXx50Nqj1aYzxexy8Scawrtt2F87o1IesOodoPEQGTgVVieIilplwkMLhMvJfxhyMOtohR63
XOGBSI4dDz58z3zOlk9P3k2Uq5FmkqwNNkduKceSw'
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
Date: Thu, 09 Nov 2017 20:57:17 GMT
Expires: 0
Pragma: no-cache
Transfer-Encoding: chunked
X-Application-Context: application:default
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
{
"currentUser": [
{
"description": "This is going to be a great birthday",
"id": 100,
"summary": "Birthday Party",
"when": 1499135400000
}
]
}

现在我们的 OAuth 2 服务器已经准备好为客户端发出 access_tokens,我们现在可以创建一个微服务客户端来与我们的系统进行交互。

微服务客户端

我们通过添加 @EnableOAuth2Client 注释将此应用程序启用为 OAuth 2 客户端来启动我们的新客户端应用程序。添加 @EnableOAuth2Client 注释将允许此应用程序从一个或多个 OAuth2 授权服务器检索和使用授权代码授权。使用客户端凭证授权的客户端应用程序不需要 AccessTokenRequest 或范围内的 RestOperations (应用程序的状态是全局的),但它们仍应使用过滤器来触发 OAuth2RestOperations 在必要时获取令牌。使用密码授权的应用程序需要在使用 RestOperations 方法之前在 OAuth2ProtectedResourceDetails 中设置身份验证属性,我们将在稍后配置该方法。让我们看一下以下步骤,看看它是如何完成的:

  1. We need to set up a few properties that will be used to configure the client, as shown in the following JavaConfig.java file:
    //src/main/java/com/packtpub/springsecurity/configuration/JavaConfig.java

@Configuration
@EnableOAuth 2Client
public class JavaConfig {
@Value("${oauth.token.uri}")
private String tokenUri;
@Value("${oauth.resource.id}")
private String resourceId;
@Value("${oauth.resource.client.id}")
private String resourceClientId;
@Value("${oauth.resource.client.secret}")
private String resourceClientSecret;
@Value("${oauth.resource.user.id}")
private String resourceUserId;
@Value("${oauth.resource.user.password}")
private String resourceUserPassword;
@Autowired
private DataSource dataSource;
...
}
  1. In addition to several standard properties we need to execute the OAuth 2 RESTful operations, we also need to create a dataSource to hold oauth_client_token that will be retrieved upon the initial request, then used in subsequent operations for a given resource. Now let's create ClientTokenServices for managing oauth_client_token, as shown in the following JavaConfig.java file:
    //src/main/java/com/packtpub/springsecurity/configuration/JavaConfig.java

@Bean
public ClientTokenServices clientTokenServices() {
return new JdbcClientTokenServices(dataSource);
}
  1. Now we create the OAuth2RestTemplate that will manage the OAuth2 communication. We will start by creating a ResourceOwnerPasswordResourceDetails to hold the resource connection details, then construct an OAuth2RestTemplate to be used as an OAuth2RestOperations for the client request:
//src/main/java/com/packtpub/springsecurity/configuration/JavaConfig.java

@Bean
public OAuth2RestOperationsOAuth2RestOperations() {
ResourceOwnerPasswordResourceDetails resource =
new ResourceOwnerPasswordResourceDetails();
resource.setAccessTokenUri(tokenUri);
resource.setId(resourceId);
resource.setClientId(resourceClientId);
resource.setClientSecret(resourceClientSecret);
resource.setGrantType("password");
resource.setScope(Arrays.asList("openid"));
resource.setUsername(resourceUserId);
resource.setPassword(resourceUserPassword);
return new OAuth 2RestTemplate(resource);
}

配置 OAuth 2 客户端

现在我们已经启用了我们的 @EnableOAuth2Client 注释并设置了一个 ResourceOwnerPasswordResourceDetails 对象,我们需要配置用于连接到资源服务器和身份验证服务器的属性:

    //src/main/resources/application.yml

oauth:
url: ${OAUTH_URL:http://localhost:8080}
token:
uri: ${OAUTH_URL:http://localhost:8080}/oauth/token
resource:
id: microservice-test
# Client BASIC Authentication for Authentication Server
client:
id: ${OAUTH_CLIENT_ID:oauthClient1}
secret: ${OAUTH_CLIENT_SECRET:oauthClient1Password}
# Resource Password Credentials
user:
id: ${OAUTH_USER_ID:[email protected]}
password: ${OAUTH_USER_PASSWORD:user1}

现在我们已经准备好了这些部分,可以开始使用 OAuth2RestOperations 对象发出请求。我们将首先创建 RestController 以提取远程详细信息并将其作为 RESTful 请求的结果显示,如我们的 OAuth2EnabledEventsController.java 文件所示:

    //src/main/java/com/packtpub/springsecurity/web/controllers/
OAuth2EnabledEventsController.java


@RestController
public class OAuth2EnabledEventsController {
@Autowired
private OAuth2RestOperations template;
@Value("${base.url:http://localhost:8888}")
private String baseUrl;
@Value("${oauth.url:http://localhost:8080}")
private String baseOauthUrl;
@GetMapping("/events/my")
public String eventsMy() {
@SuppressWarnings("unchecked")
String result = template.getForObject(baseOauthUrl+"/events/my",
String.class);
return result;
}
}

我们现在应该为客户端应用程序拥有相同的代码库。

您的代码应该类似于 chapter16.01-calendar-client

我们需要确保 chapter16.01-calendar 应用程序正在运行并准备好接受来自客户端的 OAuth 2 请求。然后我们可以启动 chapter16.01-calendar-client 应用程序,它将公开几个 RESTful 端点,包括一个用于访问位于 /events/my 上的已配置用户事件的端点远程资源,并将通过运行 http://localhost:8888/events/my 返回以下结果:

    {
"currentUser": [
{
"id": 100,
"summary": "Birthday Party",
"description": "This is going to be a great birthday",
"when": 1499135400000
}
]
}

概括

在本章中,您了解了单体应用程序和微服务之间的一般区别,并将 SOA 与微服务进行了比较。您还了解了 OAuth 2 的概念架构以及它如何为您的服务提供值得信赖的客户端访问权限,并了解了 OAuth 2 访问令牌的类型和 OAuth 2 授权类型的类型。

我们检查了 JWT 及其一般结构,实现了一个资源服务器和身份验证服务器,用于授予客户端访问权限以访问 OAuth 2 资源,并实现了一个 RESTful 客户端以通过 OAuth 2 授权流程访问资源。