本文将以一个实际案例——Spring Cloud + Spring Boot进行演示,阐述如何将单体应用架构迁移到微服务架构上时,如何实现安全防护

时间:2024-10-27 07:00:48

作者:禅与计算机程序设计艺术

1.背景介绍

近年来,微服务架构越来越流行,人们期待着微服务可以带来更好的灵活性、弹性和可扩展性,能够应对复杂的业务场景。但是,如何从传统的单体应用架构逐步过渡到微服务架构,并确保安全性是一个重要的课题。

随着微服务架构越来越流行,一些企业也开始考虑将现有的单体应用架构迁移到微服务架构上。然而,在将单体应用架构迁移到微服务架构之前,需要做好相关的安全措施,防止数据泄露、信息泄漏、攻击等安全风险发生。本文将以一个实际案例——Spring Cloud + Spring Boot进行演示,阐述如何将单体应用架构迁移到微服务架构上时,如何实现安全防护。

2.核心概念与联系

微服务架构模式的出现主要是为了解决单体应用的“笨重”、“易崩溃”的问题。它把应用拆分成多个独立的服务,每个服务都有一个自己的职责、边界和生命周期,彼此之间通过轻量级的通信协议进行通信,因此服务间的依赖关系变得松散,方便服务的水平伸缩。同时,微服务架构下,每个服务都由自己独立的开发团队来负责,相互之间也容易进行集成测试。

在微服务架构下,每个服务都有自己对应的数据库,以及不同的部署环境,这就使得服务之间的通信、数据共享变得十分复杂,需要考虑服务访问权限控制、安全性、可用性、数据完整性等方面因素。因此,对于想要迁移到微服务架构的应用,必须考虑以下几个关键点:

  1. 敏感信息:识别出所需的敏感信息,如个人身份信息(PII)、财务数据等,并进行安全保护。
  2. 服务间认证与授权:设置合理的服务访问权限控制策略,确保服务之间的数据隔离。
  3. 数据加密传输:采用加密传输、数据签名等方式保证数据的安全。
  4. 日志审计:采取有效的方式记录日志,包括错误日志、接口调用日志、监控日志等。
  5. 服务降级/熔断:配置服务降级策略,如熔断机制,避免发生严重故障影响业务连续性。

3.核心算法原理和具体操作步骤以及数学模型公式详细讲解

Spring Cloud提供了一系列的组件帮助我们实现微服务架构下的安全防护功能。比如,Zuul作为网关组件,提供请求转发、过滤等功能;Eureka作为服务注册中心,用于管理各个服务的状态;Ribbon作为客户端负载均衡器,能动态地分配请求给各个服务实例;Hystrix作为熔断器组件,能实时监控服务的运行状况,避免单个服务故障导致整个系统瘫痪。

在Spring Cloud下实现安全防护的过程可以划分为以下几个步骤:

第一步:设计API鉴权方案。在微服务架构中,各个服务都会暴露对应的RESTful API接口,如果没有足够的权限控制,可能会造成数据泄露或被恶意篡改。所以,首先要确定所有服务的访问权限控制规则,并根据需求设计相应的API访问鉴权逻辑。

第二步:加强服务间通讯的安全性。在微服务架构下,不同服务间通过轻量级的HTTP/HTTPS协议进行通信,需要考虑安全性问题。因此,建议服务消费者采用TLS加密传输、HTTPS协议来保障消息的安全性。

第三步:启用服务访问日志记录。在微服务架构下,日志记录是一个非常重要的环节,它能帮助我们快速定位问题并追踪服务调用流程。因此,建议对服务进行统一的日志记录,并且制定详细的规范,以便于日后查询和分析。

第四步:设置服务降级策略。当某些服务出现问题时,为了保证系统的正常运行,可能需要对其作出临时的降级处理。比如,当某个服务不可用时,可以返回缓存的结果或者默认值,避免造成业务连续性的影响。

第五步:采用消息队列或事件总线来实现异步通信。在微服务架构下,服务间的通信往往是异步的,消息队列或事件总线可以提高通信的效率。因此,建议把不需要同步通信的服务调用改造成异步调用,采用消息队列或事件总线来实现服务间通信。

4.具体代码实例和详细解释说明

通过上面的具体介绍,我们已经知道了在Spring Cloud下实现微服务架构下安全防护的过程。下面,让我们结合Spring Cloud实现案例来进一步阐述该过程。

实战案例:Spring Cloud + Spring Boot 迁移到微服务架构上的安全防护方案

一、环境准备

假设有一个 Spring Cloud + Spring Boot 的单体应用,其中主要包括订单模块、库存模块、支付模块、用户模块等。

目录结构如下:

├──  (项目依赖)
└── order (订单服务)
    ├──  (订单服务依赖)
    └── src
        ├── main
        │   ├── java
        │   │   └── com
        │   │       └── example
        │   │           └── order
        │   │               └──  (启动类)
        │   └── resources
        │       ├──  (配置文件)
        │       ├──  (日志配置)
        │       └── META-INF
        │           └──  (spring扫描配置)
        └── test
            └── java
                └── com
                    └── example
                        └── order
                            └──  (单元测试)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

配置文件如下:

server:
  port: ${port:9000} # 服务端口号
spring:
  application:
    name: demo-order # 服务名称
  datasource:
    url: jdbc:mysql://localhost:3306/demo_db?useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true
    username: root
    password: <PASSWORD>
  jpa:
    hibernate:
      ddl-auto: update # Hibernate自动更新DDL
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">

  <!-- Console appender -->
  <appender name="console" class="">
    <encoder>
      <pattern>%d{HH:mm:} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- File appender -->
  <appender name="file" class="">
    <file>logs/${}.log</file>
    <append>true</append>
    <encoder>
      <pattern>%date %level [%thread] %class:%line - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- Root logger -->
  <root level="${}">
    <appender-ref ref="console"/>
    <appender-ref ref="file"/>
  </root>

</configuration>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

META-INF/ 配置文件如下:

=\
,\
,\
,\
,\
..EnableOAuth2Sso
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

启动类 文件如下:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) // 禁用数据源自动配置
@ComponentScan({""}) // 定义组件包
public class OrderApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
    
    @Override
    public void run(String... args) throws Exception {
        
    }
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

以上就是 Spring Cloud + Spring Boot 单体应用的初始环境搭建。

二、实现安全防护

1. 敏感信息保护
1.1 定义敏感信息

在实际的业务场景中,可能存在敏感信息,如身份证号码、银行卡号、手机号码等。敏感信息属于非常重要的数据资源,需要采取必要的措施保护。

1.2 在 Spring Cloud 中配置数据脱敏工具

由于 Spring Cloud 提供了丰富的工具来帮助我们进行微服务架构中的开发,其中包括 Spring Security、Spring Data JPA 和 Spring Cloud Config。通过配置,我们可以在不修改代码的情况下完成敏感信息的脱敏。

1. 创建加密密钥存储库
创建名为 secret 的密钥存储库,并将加密密钥放在仓库中。密钥的生成可以使用 Spring Cloud Config Server 来实现。

2. 创建配置文件配置项
创建一个名为 的配置文件配置项,并添加敏感信息的配置项。例如:

data:
  sensitive-info:
    bankcardNumber: '1234************1234'
    idCardNumber: '330******2***7'
    mobilePhone: '151****1234'
  • 1
  • 2
  • 3
  • 4
  • 5

3. 使用 Spring Cloud Config Client 获取敏感信息
在 Spring Cloud Config Client 中,只需要注入 Encryptor 对象就可以获取加密后的敏感信息。例如:

@RestController
@RequestMapping("/api")
public class DemoController {

    private final Encryptor encryptor;

    public DemoController(@Value("${-info}") Map<String, String> sensitiveInfoMap, Encryptor encryptor) {
        this.sensitiveInfoMap = sensitiveInfoMap;
        this.encryptor = encryptor;
    }

    @GetMapping("getBankCardNumber")
    public ResponseEntity getBankCardNumber() {
        String encryptedBankCardNumber = this.encryptor.encrypt(this.sensitiveInfoMap.get("bankcardNumber"));
        return new ResponseEntity<>(encryptedBankCardNumber, HttpStatus.OK);
    }
    
   ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

4. 配置加密工具
在 Spring Security 中,我们可以配置加密工具。例如:

spring:
  security:
    user:
      name: myuser
      password: '{cipher}'+BCryptPasswordEncoder$fINAAABAAJqZlcqUvjAYTqnCAg=='
  jpa:
    properties:
      hibernate:
        dialect: .MySQL5InnoDBDialect
        hbm2ddl:
          auto: none
        format_sql: true
      javax:
        persistence:
          schema-generation:
            create-scripts:
              action: create
              include-schema: false
  datasource:
    driverClassName: 
    url: jdbc:mysql://localhost:3306/demo_db?useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true
    username: root
    password: secret
  data:
    rest:
      base-path: /api
      
  cloud:
    config:
      server:
        git:
          uri: https:///example/config-
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

在这里,我们配置了 Spring Security 对用户名密码加密,并设置了 MySQL 数据库相关的配置项。然后,我们注入 Encryptor 对象到 Controller 中,使用 () 方法获取加密后的敏感信息。这样,我们就可以达到敏感信息的安全保护。

2. 服务访问权限控制
2.1 为每一个服务创建角色和权限

在 Spring Cloud 中,我们可以通过角色和权限来进行服务访问权限控制。角色和权限的信息存储在配置中心,可以通过 Spring Cloud Gateway 来实现权限认证。

1. 创建角色和权限配置文件
创建一个名为 的配置文件配置项,并添加角色和权限配置。例如:

roles:
  admin:
    permissions: "*"
  customer:
    permissions: "ORDER:*"
  • 1
  • 2
  • 3
  • 4
  • 5

2. 设置配置文件推送地址
通过 Spring Cloud Config Client 来实现配置文件的推送,并指定推送的 URL 和驱动类型。例如:

spring:
  cloud:
    config:
      server:
        git:
          uri: https:///example/config-
          repos: auth-config
          default-label: master
          search-paths: "{application}/{profile},auth-config/{branch}"
          username: myusername
          password: mypassword
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3. 通过 Spring Cloud Gateway 进行权限认证
在 Spring Cloud Gateway 中,我们可以根据角色信息来进行权限认证。例如:

spring:
  cloud:
    gateway:
      routes:
        - id: permission-route
          uri: http://localhost:${}/
          predicates:
            - Path=/api/**
          filters:
            - Name: PreAuthorize
              Args:
                access("#('read') or #('write') or #('admin')"): ACCESS_GRANTED
                access("@('ROLE_ADMIN') and hasPermission('ORDER', '*')"): HAS_ORDER_PERMISSION
                denyAll: DEFAULT_DENY
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

通过 access("#('read') or #('write') or #('admin')"): ACCESS_GRANTED,我们可以限制只有拥有 readwriteadmin 三个权限范围之一的用户才能访问。然后,我们再次通过 @('ROLE_ADMIN')hasPermission('ORDER', '*') 两个自定义表达式,来验证是否具有订单服务的读权限。

3. 数据加密传输
3.1 使用 Spring Security 对数据加密传输

为了保证数据的安全性,我们可以采用加密传输、数据签名等方式。Spring Security 是 Java Web 框架中的一个安全模块,支持对用户请求参数进行加密传输。

1. 添加加密配置
在配置文件中增加加密配置:

spring:
  profiles: dev
  
encrypt:
  key: secretKeyToEncodeAndDecodeDataForSensitiveInformation
  • 1
  • 2
  • 3
  • 4
  • 5

2. 修改配置文件的加密属性
修改需要加密传输的配置文件的加密属性:

spring:
  profiles: prod
  
spring:
  datasource:
    url: '${}&password={cipher}'+${}
    username: myusername
    password: mypassword
  
  rabbitmq:
    password: '{cipher}'+${}
  
  redis:
    password: '{cipher}'+${}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3. 配置加密工具
在 Spring Security 中,我们可以配置加密工具。例如:

spring:
  security:
    encryption:
      key: ${}
    user:
      name: myuser
      password: '{cipher}'+BCryptPasswordEncoder$fINAAABAAJqZlcqUvjAYTqnCAg=='
  jpa:
    properties:
      hibernate:
        dialect: .MySQL5InnoDBDialect
        hbm2ddl:
          auto: none
        format_sql: true
      javax:
        persistence:
          schema-generation:
            create-scripts:
              action: create
              include-schema: false
  datasource:
    driverClassName: 
    url: jdbc:mysql://localhost:3306/demo_db?useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true
    username: root
    password: secret
  data:
    rest:
      base-path: /api
      
  cloud:
    config:
      server:
        git:
          uri: https:///example/config-
          
  eureka:
    client:
      serviceUrl:
        defaultZone: http://localhost:8761/eureka/
    instance:
      leaseRenewalIntervalInSeconds: 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

在这里,我们配置了 Spring Security 对用户名密码加密,并设置了 MySQL 数据库相关的配置项。然后,我们可以使用 {cipher} 属性来标记需要加密的字段,Spring Security 会自动加密这些字段。这样,我们就可以达到数据加密传输的目的。

4. 日志审计
4.1 使用 Logstash 将日志发送到 Elasticsearch

为了对系统日志进行持久化,我们可以采用 Elasticsearch 来存储日志。Logstash 可以用来收集和解析系统日志。

1. 安装并启动 Elasticsearch
安装 Elasticsearch,并启动 Elasticsearch 服务。

2. 安装并启动 Logstash
下载并安装 Logstash,并启动 Logstash 服务。

3. 配置 Logstash
创建 配置文件,并添加配置:

input {
  tcp {
    type => "syslog"
    port => 5000
  }
}
output {
  elasticsearch {
    hosts => ["http://localhost:9200"]
    index => "demo-system-%{type}-%{+}"
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

4. 配置 Spring Boot Admin
若系统里有多个 Spring Boot 应用,我们还可以利用 Spring Boot Admin 来监控这些应用的健康情况。我们可以创建一个配置文件 ,并配置 Spring Boot Admin。

spring:
  boot:
    admin:
      client:
        url: http://localhost:8080
      monitor:
        health-indicator:
          enabled: true
        period: 10s
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

5. 添加 Logstash 依赖
在 Spring Boot 应用的 文件中添加 Logstash 依赖:

<dependency>
    <groupId></groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>${}</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

6. 配置 Logstash 配置文件推送
在 Spring Boot 应用的配置文件中添加 Logstash 配置文件推送配置:

management:
  endpoint:
    logfile:
      enabled: true
    loggers-name-filter:
      enabled: true
    loggers:
      enabled: true
    environment-post-processor:
      enabled: true

logstash:
  host: localhost
  port: 5000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

7. 测试日志发送
在 Spring Boot 应用的日志中写入一条消息,并观察 Elasticsearch 中的索引。

5. 服务降级/熔断
5.1 使用 Hystrix 设置服务降级策略

为了减少单个服务故障引起的影响,我们可以采用服务降级策略。Hystrix 是 Spring Cloud 提供的一个基于断路器模式的容错框架。

1. 配置 Hystrix Dashboard
安装并启动 Hystrix Dashboard 服务,并配置 Hystrix Dashboard。

2. 配置 Hystrix fallback 策略
在配置文件中添加 Hystrix fallback 策略:

feign:
  hystrix:
    enabled: true
  • 1
  • 2
  • 3

在 Controller 层方法中添加异常捕获块,并指定 fallback 函数:

@FeignClient(name = "${}", contextId = "order", fallbackFactory = OrderFeignFallbackFactory.class)
interface OrderFeignClient {
    
    @PostMapping("/orders")
    Response postOrder(@RequestBody OrderDto dto);
    
	...
	
}

static class OrderFeignFallbackFactory implements feign.hystrix.FallbackFactory<OrderFeignClient> {

    @Override
    public OrderFeignClient create(Throwable cause) {
        System.out.println("fallback by hystrix");
        
        OrderFeignFallbackFactory fallback = new OrderFeignFallbackFactory();
        return fallback.getOrderFeignClient();
    }

    private OrderFeignClient getOrderFeignClient() {

        OrderFeignClient orderFeignClientMock = mock(OrderFeignClient.class);
        
        doReturn(Response.builder().code("500").message("Internal Error").build()).when(orderFeignClientMock).postOrder(any());
    
        return orderFeignClientMock;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

3. 配置服务熔断
在配置文件中添加熔断配置:

feign:
  circuitbreaker:
    enabled: true
    requestVolumeThreshold: 10
    errorThresholdPercentage: 50
  • 1
  • 2
  • 3
  • 4
  • 5

当发生 50% 的错误率时,触发服务熔断,保护服务调用方。

通过上面的步骤,我们就实现了 Spring Cloud 下微服务架构的安全防护功能。