vlambda博客
学习文章列表

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

Chapter 9. Putting It All Together

当我们采用微服务架构风格时,会面临一些挑战。第一个处理操作复杂性;服务发现和负载均衡器等服务帮助我们解决这些问题。我们在前面的章节中解决了这些挑战,并在此过程中了解了一些重要的工具。

在微服务采用中还有一些其他重要的关键点需要处理。监控我们的微服务环境中发生的事情的有效方法是监控微服务消耗其他微服务资源(例如 HTTP API)的次数,以及它们失败的次数。如果我们有近乎实时的统计数据,它可以为开发人员节省故障排除和错误调查的时间。

在本章中,我们将创建一些服务来帮助我们监控 Hystrix 命令并在分布式环境中聚合命令的统计信息。

安全性是微服务架构的一个重要特性,尤其是微服务架构所采用的分布式特性。我们的架构中有很多微服务;我们不能在服务之间共享状态,因此无状态安全非常适合我们的环境。

OAuth 2.0 协议规范有一个重要的特性:无状态实现。 Spring Cloud Security 提供对 OAuth 2.0 的支持。

最后,我们将 Dockerize 我们的微服务以使用 Docker 组合文件中的图像。

在本章中,我们将了解:

  • Implementing the Turbine server to aggregate Hystrix streams
  • Configuring the Hystrix Dashboard to use Turbine and input data
  • Creating a mail service that will integrate an email API
  • Understanding Spring Cloud Security
  • Dockerizing our microservices

The airline Bookings microservice


航空公司 Bookings 微服务是一个标准的 Spring Boot 应用程序。有 一些 与其他服务交互,例如 flights 微服务。

这些交互是使用 Hystrix 创建的,以便为航空公司 Bookings 微服务带来一些所需的行为,例如容错和弹性。

该服务有一些业务规则,它们对现在的学习上下文并不重要,因此我们将跳过项目创建和执行部分。

Note

完整的源代码可以在 GitHub (https://github.com/PacktPublishing/Spring-5.0-By-Example/tree/master/Chapter09/airline-booking);让我们检查一下,看看一些代码。

The airline Payments microservice


航空公司 Payments 是一种微服务,可为我们的机票系统提供付款确认。出于学习目的,我们将跳过这个项目,因为有 一些 业务规则,在 Spring Framework 上下文中没有什么重要的。

我们可以在 GitHub 上找到完整的源代码 (https://github.com/PacktPublishing/Spring-5.0-By-Example/tree/master/Chapter09/airline-payments)。

Learning about the Turbine server


我们的微服务组中有一些集成; Bookings 微服务调用 Fares 微服务和 Passengers 微服务,这些集成使用 Hystrix 完成,使其更具弹性和容错性。

但是,在微服务世界中,有几个服务实例。这将要求我们按实例聚合 Hystrix 命令指标。逐个面板管理实例面板不是一个好主意。 Turbine 服务器在这种情况下帮助开发人员。

默认情况下,Turbine 从 Hystrix 运行的服务器中提取指标,但不建议用于云环境,因为它会消耗大量的网络带宽,并且会增加流量成本。我们将使用 Spring Cloud Stream RabbitMQ 通过 Advanced 将指标推送到 Turbine消息队列协议 (AMQP)。因此,我们需要配置 RabbitMQ 连接并在我们的微服务中添加两个依赖项,依赖项是:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-netflix-hystrix-stream</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

这些依赖项将使指标能够通过 AMQP 协议发送到 Turbine 服务器。

Turbine 流默认使用端口 8989 。我们将其配置为在 8010 运行,我们可以使用 turbine.stream.port 属性"literal">application.yaml 来自定义它。

Turbine 流将是 Hystrix Dashboard 数据输入以显示命令指标。

有许多配置可以自定义 Turbine 服务器。它们使服务器非常适合不同的用例。

Note

我们可以在 Spring Cloud Turbine 部分(https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix。 html#_turbine)。有大量信息,特别是如果您需要自定义一些配置。

Creating the Turbine server microservice

让我们创建我们的 Turbine 服务器。我们将创建一个带有几个注释的标准 Spring Boot 应用程序,以启用 Turbine 流和发现客户端。

主要课程应该是:

package springfive.airline.turbine;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.turbine.stream.EnableTurbineStream;

@EnableEurekaClient
@EnableTurbineStream
@SpringBootApplication
public class AirlineTurbineApplication {

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

}

正如我们所见,@EnableTurbineStream 将使我们能够通过 RabbitMQ 消息代理推送 Hystrix 命令指标,这对我们来说已经足够了。

Turbine 服务器 application.yaml 文件可以在 GitHub (https://github.com/PacktPublishing/Spring-5.0-By-Example/blob/master/config-files/turbine.yaml )。有几种配置,例如发现客户端和涡轮服务器配置。

我们可以通过命令行或 IDE 运行应用程序。运行!

 

flights 微服务进行一些调用。 Create Flight API 将调用 planes 微服务,该微服务使用 Hystrix 命令,并会触发一些 Hystrix 命令调用。

Note

我们可以使用位于 GitHub 的 Postman Collection (https://github.com/PacktPublishing/Spring-5.0-By-Example/blob/master/postman/flights.postman_collection)。这个集合有一个 Create Flight 请求,它将调用 planes 微服务来获取飞机的详细信息。收集指标就足够了。

现在,我们可以测试我们的 Turbine 服务器是否正常运行。转到 Turbine 流端点,然后带有指标的 JSON 数据应显示如下:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

有一些 Hystrix 命令信息,但正如我们所见,需要组织这些信息以使其对我们有用。 Turbine 使用 Server-Sent Events (SSE) 技术,其中在第6章中介绍的,玩服务器发送事件

在下一节中,我们将介绍 Hystrix Dashboard。它将帮助我们组织这些信息并使这些信息对我们有用。

让我们跳到下一节。

Hystrix Dashboard


Hystrix Dashboard 将帮助我们组织 Turbine 流信息。正如我们在上一节中看到的,Turbine 服务器通过 SSE 发送信息。它是使用 JSON 对象完成的。

 

Hystrix 流为我们提供了一个仪表板。让我们创建我们的 Hystrix Dashboard 微服务。该应用程序是使用 @EnableHystrixDashboard 注释的标准 Spring Boot 应用程序。让我们添加依赖项来启用它:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

好的,现在我们可以为 我们的 应用程序创建主类。主类应如下所示:

package springfive.airline.hystrix.ui;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@EnableEurekaClient
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixApplication {

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

}

正如我们所看到的,这是一个非常标准的 Spring Boot 应用程序,带有 @EnableHystrixDashboard 注释。它将为我们提供 Hystrix Dashboard。

现在,我们可以通过 IDE 或 Java 命令行运行应用程序。运行!

Note

可以使用以下 URL 访问 Hystrix 仪表板:http://localhost:50010/hystrix

然后,转到 Hystrix Dashboard主页。应显示以下页面:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

 

太棒了——我们的 Hystrix Dashboard 已启动并运行。在这个页面上,我们可以指向 hystrix.streamturbine.stream 来消费和显示命令的指标。

保持这个应用程序运行,我们将在本章后面使用它。

很棒的工作,伙计们,让我们进入下一部分。

Creating the Mail microservice


现在,我们将创建我们的 Mail 微服务。名称不言自明,该组件 负责发送电子邮件。我们不会配置 SMTP简单邮件传输协议)服务器,我们使用SendGrid。

SendGrid 是一种 SaaS软件即服务)服务,用于电子邮件,我们将使用此服务 向我们的机票系统发送电子邮件。有一些触发器可以发送电子邮件,例如,当用户创建预订和接受付款时。

我们的 Mail 微服务将监听一个队列。然后将使用消息代理完成集成。我们选择这种策略是因为我们不需要使我们能够同步回答的功能。另一个基本特征是通信中断时的重试策略。使用消息策略可以轻松完成此行为。

我们使用 RabbitMQ 作为消息代理。对于这个项目,我们将使用 RabbitMQ Reactor,它是 RabbitMQ Java 客户端的反应式实现。

Creating the SendGrid account

在开始编码之前,我们需要创建一个 SendGrid 帐户。我们将使用足以进行测试的试用帐户。转到 SendGrid 门户(https://sendgrid.com/) 并点击免费试用按钮。

填写所需信息,然后单击创建帐户按钮。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

在主页面左侧,点击Settings,然后转到API Key 部分,按照此处显示的图像进行操作:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

然后,我们可以点击右上角的Create API Key按钮。该页面应如下所示:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

填写API Key信息,选择Full Access。之后 API Key 将出现在您的屏幕上。在安全的地方记下它,因为我们很快就会将它用作环境变量。

Goob 工作,我们的 SendGrid 帐户已经可以使用了,现在我们可以编写我们的 Mail 微服务了。

让我们在下一节中进行。

 

Creating the Mail microservice project

正如我们在第 8 章中所做的那样,断路器和安全,我们将了解项目的基本部分。我们将使用 Spring Initializr,因为我们在前面的章节中有几次 time

Adding RabbitMQ dependencies

让我们添加 RabbitMQ 所需的依赖项。应添加以下依赖项:

<dependency>
  <groupId>io.projectreactor.rabbitmq</groupId>
  <artifactId>reactor-rabbitmq</artifactId>
  <version>1.0.0.M1</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

第一个是关于 RabbitMQ 的响应式实现,第二个是 starter AMQP,它将自动设置一些配置。

Configuring some RabbitMQ stuff

我们要配置一些 RabbitMQ 交换、队列和绑定。可以使用 RabbitMQ 客户端库来完成。我们将为 Mail 微服务配置所需的基础设施。

我们的配置类应该是这样的:

package springfive.airline.mailservice.infra.rabbitmq;

// imports are omitted

@Configuration
public class RabbitMQConfiguration {

private final String pass;

  private final String user;

  private final String host;

  private final Integer port;

  private final String mailQueue;

  public RabbitMQConfiguration(@Value("${spring.rabbitmq.password}") String pass,
@Value("${spring.rabbitmq.username}") String user,
@Value("${spring.rabbitmq.host}") String host,
@Value("${spring.rabbitmq.port}") Integer port,
@Value("${mail.queue}") String mailQueue) {
this.pass = pass;
    this.user = user;
    this.host = host;
    this.port = port;
    this.mailQueue = mailQueue;
}

@Bean("springConnectionFactory")
public ConnectionFactory connectionFactory() {
    CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setUsername(this.user);
factory.setPassword(this.pass);
factory.setHost(this.host);
factory.setPort(this.port);
    return factory;
}

@Bean
public AmqpAdmin amqpAdmin(@Qualifier("springConnectionFactory") ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}

@Bean
public TopicExchange emailExchange() {
return new TopicExchange("email", true, false);
}

@Bean
public Queue mailQueue() {
return new Queue(this.mailQueue, true, false, false);
}

@Bean
public Binding mailExchangeBinding(Queue mailQueue) {
return BindingBuilder.bind(mailQueue).to(emailExchange()).with("*");
}

@Bean
public Receiver receiver() {
val options = new ReceiverOptions();
com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
connectionFactory.setUsername(this.user);
connectionFactory.setPassword(this.pass);
connectionFactory.setPort(this.port);
connectionFactory.setHost(this.host);
options.connectionFactory(connectionFactory);
    return ReactorRabbitMq.createReceiver(options);
}

}

这里有一些有趣的东西,但都是关于 RabbitMQ 中的基础设施。这很重要,因为当我们的应用程序处于引导时间时,这意味着我们的应用程序正在准备运行。此代码将被执行并创建必要的队列、交换和绑定。 application.yaml文件提供了一些配置,看构造函数。

Modeling a Mail message

我们的 Mail 服务是 abstract 并且可以用于不同的目的,所以我们将创建一个简单的类来表示我们系统中的邮件消息。我们的 Mail 类应该是这样的:

package springfive.airline.mailservice.domain;

import lombok.Data;

@Data
public class Mail {

  String from;

String to;

String subject;

String message;

}

很简单,这个类代表了我们系统上的一个抽象消息。

The MailSender class

正如我们所料,我们将通过 REST API 与 SendGrid 服务集成。在我们的例子中,我们将使用 Spring WebFlux 提供的响应式 WebClient

现在,我们将使用在上一节中创建的 SendGrid API 密钥。我们的 MailSender 类应该是这样的:

package springfive.airline.mailservice.domain.service;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import springfive.airline.mailservice.domain.Mail;
import springfive.airline.mailservice.domain.service.data.SendgridMail;

@Service
public class MailSender {

private final String apiKey;

  private final String url;

  private final WebClient webClient;

  public MailSender(@Value("${sendgrid.apikey}") String apiKey,
@Value("${sendgrid.url}") String url,
WebClient webClient) {
this.apiKey = apiKey;
    this.webClient = webClient;
    this.url = url;
}

public Flux<Void> send(Mail mail){
final BodyInserter<SendgridMail, ReactiveHttpOutputMessage> body = BodyInserters
      .fromObject(SendgridMail.builder().content(mail.getMessage()).from(mail.getFrom()).to(mail.getTo()).subject(mail.getSubject()).build());
    return this.webClient.mutate().baseUrl(this.url).build().post()
        .uri("/v3/mail/send")
        .body(body)
        .header("Authorization","Bearer " + this.apiKey)
        .header("Content-Type","application/json")
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError, clientResponse ->
            Mono.error(new RuntimeException("Error on send email"))
        ).bodyToFlux(Void.class);
}

}

我们收到了构造函数中的配置,即sendgrid.apikeysendgrid.url。它们将很快配置。在 send() 方法中,有一些有趣的结构。查看 BodyInserters.fromObject():它允许我们在 HTTP 正文中发送 JSON 对象。在我们的例子中,我们将创建一个 SendGrid 邮件对象。

onStatus() 函数中,我们可以传递一个谓词来处理 HTTP 错误族。在我们的例子中,我们对 4xx 错误系列感兴趣。

这个类将处理发送邮件消息,但有必要监听 RabbbitMQ 队列,我们​​将在下一节中进行。

Creating the RabbitMQ queue listener

让我们创建我们的 MailQueueConsumer 类,它将 listen 到 RabbitMQ 队列。该类应如下所示:

package springfive.airline.mailservice.domain.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import reactor.rabbitmq.Receiver;
import springfive.airline.mailservice.domain.Mail;

@Service
@Slf4j
public class MailQueueConsumer {

private final MailSender mailSender;

  private final String mailQueue;

  private final Receiver receiver;

  private final ObjectMapper mapper;

  public MailQueueConsumer(MailSender mailSender, @Value("${mail.queue}") String mailQueue,
Receiver receiver, ObjectMapper mapper) {
this.mailSender = mailSender;
    this.mailQueue = mailQueue;
    this.receiver = receiver;
    this.mapper = mapper;
}

@PostConstruct
public void startConsume() {
this.receiver.consumeAutoAck(this.mailQueue).subscribe(message -> {
try {
val mail = this.mapper.readValue(new String(message.getBody()), Mail.class);
        this.mailSender.send(mail).subscribe(data ->{
log.info("Mail sent successfully");
});
} catch (IOException e) {
throw new RuntimeException("error on deserialize object");
}
    });
}

}

@PostConstruct 注解的方法会在 MailQueueConsumer 准备好后被调用,这意味着注入被处理。然后 Receiver 将开始处理消息。

Running the Mail microservice

现在,我们将运行我们的 Mail 微服务。找到我们项目的 MailServiceApplication 类,main 类。主类应如下所示:

package springfive.airline.mailservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableHystrix
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class MailServiceApplication {

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

}

它是一个标准的 Spring Boot 应用程序。

我们可以在 IDE 中或通过 Java 命令行运行应用程序。

运行!

Note

我们需要传递 ${SENDGRID_APIKEY}${SENDGRID_URL} 作为环境变量。如果您使用 Java 命令行运行应用程序,-D 选项允许我们传递环境变量。如果您使用的是 IDE,您可以在 Run/Debug Configurations 中进行配置。

Creating the Authentication microservice


我们希望保护我们的微服务。安全性对于微服务应用程序来说是必不可少的,尤其是因为分布式特性。

在微服务架构风格上,通常会有一个服务充当身份验证服务。这意味着该服务将对我们微服务组中的请求进行身份验证。

Spring Cloud Security 提供了一个声明式模型来帮助开发人员启用应用程序的安全性。支持公共模式,例如 OAuth 2.0。此外,Spring Boot Security 启用 单点登录< /span>(SSO)。

Spring Boot Security 还支持与 Zuul 代理集成的中继 SSO 令牌。这意味着令牌将被传递给下游微服务。

对于我们的架构,我们将使用 OAuth 2.0 和 JWT 模式,它们都与 Zuul 代理集成。

在我们这样做之前,让我们了解 OAuth 2.0 流程中的主要实体:

  • Protected resource: This service will apply security rules; the microservices applications, in our case
  • OAuth authorization server: The authentication server is a service between the application, which can be a frontend or a mobile, and a service that applications want to call
  • Application: The application that will call the service, the client.
  • Resource Owner: The user or machine that will authorize the client application to access their account

让我们画出基本的 OAuth 流程:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

 

我们可以在这张图中观察到以下

  1. The Client requests the authorization
  2. The Resource Owner sends the authorization grant
  3. The application client requests the access token from the Authorization Server
  4. If the authorization grant is valid, the Authorization Server will provide the access token
  5. The application calls the protected resource and sends the access token
  6. If the Resource Server recognizes the token, the resource will serve for the application

这些是 OAuth 2.0 授权流程的基础知识。我们将使用 Spring Cloud Security 实现这个流程。我们开始做吧。

Creating the Auth microservice

正如我们在本章中所做的那样,我们将看看重要的部分。让我们从我们的依赖项开始。我们需要放入 以下 依赖项:

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

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

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

这些依赖项将使我们能够使用 Spring Cloud Security 功能。让我们开始编写我们的身份验证微服务。

 

Configuring the security

让我们开始编写我们的 Auth 微服务。我们将从 authorization 和身份验证开始,因为我们要保护微服务中的所有资源,然后我们将配置 WebSecurityConfigureAdapter。该类应如下所示:

package springfive.airline.authservice.infra.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import springfive.airline.authservice.service.CredentialsDetailsService;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

private final PasswordEncoder passwordEncoder;

  private final CredentialsDetailsService credentialUserDetails;

  public SecurityConfig(PasswordEncoder passwordEncoder,
CredentialsDetailsService credentialUserDetails) {
this.passwordEncoder = passwordEncoder;
    this.credentialUserDetails = credentialUserDetails;
}

@Override
  @Autowired
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(this.credentialUserDetails).passwordEncoder(this.passwordEncoder);
  }

@Override
  protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeRequests()
        .antMatchers("/login", "/**/register/**").permitAll()
        .anyRequest().authenticated()
        .and()
        .formLogin().permitAll();
  }

}

这里有很多东西。让我们从 @EnableWebSecurity 开始,这个注解启用 Spring Security 与 Spring MVC 的集成。 @EnableGlobalMethodSecurity 提供 AOP 拦截器以使用注解启用方法安全性。例如,我们可以通过注释控制器上的方法来使用此功能。基本思想是将方法调用包装在 AOP 拦截器中,并对方法应用安全性。

WebSecurityConfigurerAdapter 使我们能够配置安全端点和一些关于如何验证用户的东西,这可以使用 configure(AuthenticationManagerBuilder auth) 方法。我们已经配置了我们的 CredentialsDetailsS​​ervice 和我们的 PasswordEncoder 以避免应用层之间的平面密码。在这种情况下,CredentialsDetailsS​​ervice 是我们用户数据的来源。

在我们的方法configure(HttpSecurity http)中,我们配置了一些HTTP安全规则。我们可以看到,所有用户都可以访问/login/**/register/**。这是关于登录注册功能。所有其他请求都需要由授权服务器进行身份验证。

CredentialsDetailsS​​ervice 应该如下所示:

package springfive.airline.authservice.service;

import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import springfive.airline.authservice.domain.Credential;
import springfive.airline.authservice.domain.data.CredentialData;
import springfive.airline.authservice.repository.CredentialRepository;

@Component
public class CredentialsDetailsService implements UserDetailsService {

private final CredentialRepository credentialRepository;

  public CredentialsDetailsService(CredentialRepository credentialRepository) {
this.credentialRepository = credentialRepository;
}

@Override
public CredentialData loadUserByUsername(String email) throws UsernameNotFoundException {
final Credential credential = this.credentialRepository.findByEmail(email);
    return CredentialData.builder().email(credential.getEmail()).password(credential.getPassword()).scopes(credential.getScopes()).build();
}

}

这里没有什么特别的。我们需要override loadUserByUsername(String email) 方法来提供用户数据到 Spring Security。

让我们配置我们的令牌签名者和我们的令牌存储。我们将使用 @Configuration 类来提供这些 bean,就像我们在前几章中所做的那样:

package springfive.airline.authservice.infra.oauth;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class OAuthTokenProducer {

@Value("${config.oauth2.privateKey}")
private String privateKey;

@Value("${config.oauth2.publicKey}")
private String publicKey;

@Bean
public JwtTokenStore tokenStore(JwtAccessTokenConverter tokenEnhancer) {
return new JwtTokenStore(tokenEnhancer);
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public JwtAccessTokenConverter tokenEnhancer() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
        return converter;
}

}

我们已经在 application.yaml 文件中配置了我们的私钥和公钥。或者,我们也可以从类路径中读取 jks 文件。然后,我们使用 JwtAccessTokenConverter 类提供我们的令牌签名者或令牌增强器,其中我们使用了私钥和公钥。

在我们的令牌存储中,Spring Security Framework 将使用该对象从令牌中读取数据,然后在 JwtTokenStore 上设置 JwtAccessTokenConverter实例。

最后,我们使用 BCryptPasswordEncoder 类提供了密码编码器类。

 

我们的最后一个类是授权服务器配置。可以使用以下类完成配置:

查看位于 GitHub 上的 OAuth2AuthServer 类 (https://github.com/PacktPublishing/Spring-5.0-By -Example/blob/master/Chapter09/auth-service/src/main/java/springfive/airline/authservice/infra/oauth/OAuth2AuthServer.java)。

我们已经使用 @EnableAuthorizationServer 在我们的< code class="literal">Auth 微服务。此类与 AuthorizationServerConfigurerAdapter 一起提供一些自定义。

configure(AuthorizationServerSecurityConfigurer oauthServer), 我们已经为令牌端点配置了安全性。

configure(AuthorizationServerEndpointsConfigurer endpoints), 我们已经配置了令牌服务的端点,例如 /oauth/token/oauth/authorize

最后,在配置(ClientDetailsS​​erviceConfigurer 客户端)上,我们已经配置了客户端的 ID 和机密。我们使用内存中的数据,但我们也可以使用 JDBC 实现。

Auth 微服务主类应该是:

package springfive.airline.authservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class AuthServiceApplication {

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

}

在这里,我们创建了一个启用了服务发现和 Zuul 代理的标准 Spring Boot 应用程序。

Testing the Auth microservice

正如我们所见,Auth 微服务已准备好进行测试。我们的 microservice 正在监听端口 7777,我们使用 < GitHub 上的 code class="literal">application.yaml 文件。

Client credentials flow

让我们从 client 凭证流程开始。

我们的应用程序需要在端口 7777 上启动,然后我们可以使用以下命令行来获取令牌:

curl -s 442cf4015509eda9c03e5ca3aceef752:4f7ec648a48b9d3fa239b497f7b6b4d8019697bd@localhost:7777/oauth/token   -d grant_type=client_credentials  -d scope=trust | jq .

我们可以看到,这个 client IDclient secret 是来自 planes 微服务。我们在 OAuth2AuthServer 类中进行了此配置。让我们记住确切的点:

....
@Override
public void configure(ClientDetailsServiceConfigurer clients)throws Exception {
  clients
      .inMemory()
      .withClient("ecommerce") // ecommerce microservice
.secret("9ecc8459ea5f39f9da55cb4d71a70b5d1e0f0b80")
      .authorizedGrantTypes("authorization_code", "refresh_token", "implicit",
"client_credentials")
      .authorities("maintainer", "owner", "user")
      .scopes("read", "write")
      .accessTokenValiditySeconds(THREE_HOURS)
      .and()
.withClient("442cf4015509eda9c03e5ca3aceef752") // planes microservice
      .secret("4f7ec648a48b9d3fa239b497f7b6b4d8019697bd")
      .authorizedGrantTypes("authorization_code", "refresh_token", "implicit",
          "client_credentials")
      .authorities("operator")
      .scopes("trust")
      .accessTokenValiditySeconds(ONE_DAY)

....

调用上述命令后,结果应该是:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

如我们所见,令牌是成功获得的。做得好,我们的客户端凭据流配置成功。让我们转到隐式流,这将在下一节中介绍。

Implicit grant flow

在本节中,我们将看看如何在我们的 Auth代码> 使用隐式流的微服务。

在我们测试我们的流程之前,让我们创建一个用户以在 Auth 微服务中启用身份验证。以下命令将在 Auth 服务中创建一个用户:

curl -H "Content-Type: application/json" -X POST -d '{"name":"John Doe","email":"[email protected]", "password" : "john"}' http://localhost:7777/register

如我们所见,电子邮件是[email protected],密码是john

我们将使用浏览器来完成这项任务。让我们转到以下网址:

http://localhost:7777/oauth/authorize?client_id=ecommerce&response_type=token&scope=write&state=8777&redirect_uri=https://httpbin.org/anything

让我们了解一下参数:

第一部分是服务地址。要使用隐式授权流程,我们需要路径 /oauth/authorize。此外,我们将使用 ecommerce 作为客户端 ID,因为我们之前已经对其进行了配置。 response_type=token 通知隐式流程,scope 是我们想要写的范围,state 是一个随机变量,redirect_urioauth 登录过程之后的 URI .

将 URL 放入 Web 浏览器,应显示以下页面:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

输入UserPassword后,会出现以下页面显示以授权我们受保护的资源:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

点击授权按钮。然后我们将在浏览器 URL 中看到令牌,如下所示:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

如果我们复制浏览器 URL,则可以查看完整的令牌。

很棒的工作,伙计们,我们的 Auth 微服务已经全面运行。

在接下来的部分中,我们将配置 Auth 微服务来保护 Zuul 代理下游微服务,例如 planes 微服务。让我们跳到下一节。

Protecting the microservices with OAuth 2.0

现在我们将配置 OAuth 2.0 以 protect 我们的微服务;在我们的例子中,我们的微服务是资源服务器。让我们从 planes 微服务开始。我们将添加新的依赖项并配置私钥和公钥。此外,我们将配置我们的 JwtTokenStore

我们开始做吧。

Adding the security dependency

要添加新的 required 依赖项,我们将更改 pom.xml planes 微服务。我们将添加以下依赖项:

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

小菜一碟——我们所需的依赖项已正确配置。

在下一节中,我们将配置 application.yaml 文件。

Configuring the application.yaml file

要配置我们的私钥和公钥,我们将使用 application.yaml 文件。我们在 Auth 微服务中做了这个配置。配置非常简单。我们需要添加以下代码段:

config:
  oauth2:
    privateKey: |
      -----BEGIN RSA PRIVATE KEY-----
      MIICXQIBAAKBgQDNQZKqTlO/+2b4ZdhqGJzGBDltb5PZmBz1ALN2YLvt341pH6i5
      mO1V9cX5Ty1LM70fKfnIoYUP4KCE33dPnC7LkUwE/myh1zM6m8cbL5cYFPyP099t
      hbVxzJkjHWqywvQih/qOOjliomKbM9pxG8Z1dB26hL9dSAZuA8xExjlPmQIDAQAB
      AoGAImnYGU3ApPOVtBf/TOqLfne+2SZX96eVU06myDY3zA4rO3DfbR7CzCLE6qPn
      yDAIiW0UQBs0oBDdWOnOqz5YaePZu/yrLyj6KM6Q2e9ywRDtDh3ywrSfGpjdSvvo
      aeL1WesBWsgWv1vFKKvES7ILFLUxKwyCRC2Lgh7aI9GGZfECQQD84m98Yrehhin3
      fZuRaBNIu348Ci7ZFZmrvyxAIxrV4jBjpACW0RM2BvF5oYM2gOJqIfBOVjmPwUro
      bYEFcHRvAkEAz8jsfmxsZVwh3Y/Y47BzhKIC5FLaads541jNjVWfrPirljyCy1n4
      sg3WQH2IEyap3WTP84+csCtsfNfyK7fQdwJBAJNRyobY74cupJYkW5OK4OkXKQQL
      Hp2iosJV/Y5jpQeC3JO/gARcSmfIBbbI66q9zKjtmpPYUXI4tc3PtUEY8QsCQQCc
      xySyC0sKe6bNzyC+Q8AVvkxiTKWiI5idEr8duhJd589H72Zc2wkMB+a2CEGo+Y5H
      jy5cvuph/pG/7Qw7sljnAkAy/feClt1mUEiAcWrHRwcQ71AoA0+21yC9VkqPNrn3
      w7OEg8gBqPjRlXBNb00QieNeGGSkXOoU6gFschR22Dzy
      -----END RSA PRIVATE KEY-----
    publicKey: |
      -----BEGIN PUBLIC KEY-----
      MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNQZKqTlO/+2b4ZdhqGJzGBDlt
      b5PZmBz1ALN2YLvt341pH6i5mO1V9cX5Ty1LM70fKfnIoYUP4KCE33dPnC7LkUwE
      /myh1zM6m8cbL5cYFPyP099thbVxzJkjHWqywvQih/qOOjliomKbM9pxG8Z1dB26
      hL9dSAZuA8xExjlPmQIDAQAB
      -----END PUBLIC KEY-----

此外,用户信息 URI 将使用 YAML 中的以下配置完成:

  oauth2:
    resource:
      userInfoUri: http://localhost:7777/credential

太棒了——我们的应用程序已经完全配置好了。现在,我们将做最后一部分:配置以获取信息令牌。

让我们这样做。

Creating the JwtTokenStore Bean

我们将创建 JwtTokenStore,它将被使用 来获取令牌信息.该类应如下所示:

package springfive.airline.airlineplanes.infra.oauth;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class OAuthTokenConfiguration {

@Value("${config.oauth2.privateKey}")
private String privateKey;

@Value("${config.oauth2.publicKey}")
private String publicKey;

@Bean
public JwtTokenStore tokenStore() throws Exception {
    JwtAccessTokenConverter enhancer = new JwtAccessTokenConverter();
enhancer.setSigningKey(privateKey);
enhancer.setVerifierKey(publicKey);
enhancer.afterPropertiesSet();
    return new JwtTokenStore(enhancer);
}

}

太棒了——我们的令牌签名者已配置。

最后,我们将以下注解添加到主类,它应该是这样的:

package springfive.airline.airlineplanes;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@EnableZuulProxy
@EnableEurekaClient
@EnableResourceServer
@SpringBootApplication
public class AirlinePlanesApplication {

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

}

它将保护我们的应用程序,并且需要访问令牌来访问应用程序端点。

请记住,我们需要对我们想要保护的所有微服务执行相同的任务。

Monitoring the microservices


在微服务架构风格中,monitoring 是至关重要的一环。当我们采用这种架构时,有很多好处,例如上市时间、源代码维护和业务性能的提高。这是因为我们可以为不同的团队划分业务目标,每个团队会负责一些微服务。另一个重要特征是计算资源的优化,例如云计算成本。

众所周知,天下没有免费的午餐,这种风格也带来了一些弊端,比如操作复杂。有很多小服务需要监控。可能有数百个不同的服务实例。

我们已经在我们的基础设施中实施了其中一些服务,但直到现在,我们还没有数据来分析我们的系统健康状况。在本节中,我们将探索我们配置的服务。

赶紧来分析一下吧!

Collecting metrics with Zipkin

我们已经在上一章配置了我们的 Zipkin 服务器。现在我们将使用这个 server 来分析我们的微服务数据。我们开始做吧。

拨打一些电话以创建航班。 Create Flight API 将调用 Auth ServiceFlight Service。看下面的图:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

我们将看看 flights 微服务和 planes 微服务通信。我们来分析一下:

 

转到 Zipkin 主页,http://localhost:9999/,选择flights,然后点击Find a trace .该页面应如下所示:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

正如我们所见,我们的 Zipkin 服务器上有一些数据。点击 Span,其中包含 flightsplanes< /code> 标记,然后我们将查看这个特定的跟踪,我们将被 重定向 到具有特定的另一个页面跨度数据,如下所示:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

在这个页面上,我们可以看到重要的信息,比如总请求时间。然后点击planes这一行,我们就可以看到详细信息了,如下图:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

查看请求信息。其中有一些有趣的东西,例如 mvc.controller.classmvc.controller.method。这些帮助开发人员解决错误。同样在第一个面板中,我们有服务交互的时间。查找微服务网络延迟非常有帮助;例如,它使环境管理更容易,因为我们有可视化工具来更好地理解数据。

此外,Zipkin 服务器还为其他interesting 查找微服务统计信息的功能,例如查找延迟超过特定时间的请求。这对操作人员非常有帮助。

Collection commands statistics with Hystrix

现在,我们要监控我们的 Hystrix 命令。我们的微服务中有几个命令,最常用的可能是 OAuth 令牌请求者,因为我们总是需要一个令牌来调用系统中的任何微服务。我们的 Turbine 服务器和 Hystrix UI 是在本章开头配置的,我们现在将使用这些服务。

请记住,我们使用 spring-cloud-netflix-hystrix-stream 作为实现将 Hystrix 数据发送到 Turbine 服务器,因为它的性能比 HTTP 更好,并且还带来了一些异步特性.

Note

异步调用可以使微服务更具弹性。在这种情况下,我们不会使用 HTTP 调用(同步调用)来注册 Hystrix Commands 统计信息。我们将使用 RabbitMQ 队列来注册它。在这种情况下,我们会将消息放入队列中。此外,异步调用使我们的应用程序更优化以使用计算资源。

运行 Turbine 服务器应用程序和 Hystrix UI 应用程序。 Turbine 将聚合来自服务器的指标。或者,您可以运行同一服务的多个实例,例如 flights。 Turbine 将正确汇总统计信息。

让我们调用创建航班 API;我们可以使用 Postman 来做到这一点。

然后我们可以看到实时命令统计。在此之前,我们将在 Hystrix Dashboard 中配置 turbine.stream

转到 Hystrix 仪表板页面:http://localhost:50010/hystrix/< /代码>。将显示以下页面:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

然后我们有一些工作要做。让我们配置我们的涡轮服务器流。我们的 Turbine 流在 http://localhost:8010/turbine.stream 运行。把这些信息放在Hystrix Dashboard信息下面,然后我们就可以点击Monitor Stream 按钮。

我们将重定向到 Hystrix 命令仪表板;我们之前调用了 Create Flights API 几次。将显示命令指标,如下图所示:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》把这一切放在一起

如我们所见,我们调用了八次 Create Flights API。该 API 使用了一些命令,例如 flights.plane-by-id, 它调用了 planes 微服务,以及 flights.request-token< /code> 调用 Auth 服务。

看看监控命令是多么容易。像 Zipkin 服务器这样的操作人员可以使用这个页面。

很棒的工作,伙计们,我们的服务集成得到了充分的监控,这使得我们的微服务采用更加舒适,因为我们有有用的应用程序来监控我们的服务实例。

Dockerizing the microservices


在前面的章节中,我们使用了 Fabric8 Maven Docker 插件,使我们能够使用 Maven 目标创建 Docker 镜像。

现在,我们需要配置我们的微服务来使用这个插件来轻松地为我们创建图像。与一些持续集成和交付工具(例如 Jenkins)集成会很有帮助,因为我们可以轻松调用 docker: build 目标。

每个项目都有自定义配置,例如端口和图像名称。我们可以在 GitHub 存储库中找到配置。请记住,配置是使用 pom.xml 完成的。

以下列表包含所有项目的 GitHub 存储库地址; pom.xml 具有 Maven Docker 插件配置:

Running the system


现在我们可以使用 运行我们的Docker 容器,这些镜像是在上一节中创建的。

我们将服务拆分为两个 Docker 组合文件。第一个是关于基础设施服务。第二个是关于我们的微服务。

堆栈必须在同一个 Docker 网络上运行,因为服务应该通过容器主机名连接。

基础设施的 Docker 组合文件可以在 GitHub 上找到:https://github.com/PacktPublishing/Spring-5.0-By-Example/blob/master/stacks/docker-compose-infra.yaml

微服务的 Docker 组合文件可以在 GitHub 上找到:https://github.com/PacktPublishing/Spring-5.0-By-Example/blob/master/stacks/docker-compose-micro.yaml

现在,我们可以使用 docker-compose 命令运行这些文件。键入以下命令:

docker-compose -f docker-compose-infra.yaml up -d
docker-compose -f docker-compose-micro.yaml up -d

然后完整的应用程序将启动并运行。

做的好各位。

 

 

Summary


在本章中,我们学习了微服务架构的一些要点。

我们被介绍了一些用于监控微服务环境的重要工具。我们已经了解了 Turbine 服务器如何帮助我们在分布式环境中监控 Hystrix 命令。

我们还介绍了 Hystrix Dashboard 功能,它可以帮助开发人员和运维人员提供丰富的仪表板,其中包含近乎实时的命令统计信息。

我们了解了 Spring Cloud Security 如何为我们的微服务启用安全功能,并且我们实现了 OAuth 2 服务器,使用 JWT 为我们的安全层启用弹性。