15、微服务扩展:Swagger、安全认证、国际化与缓存

微服务扩展:Swagger、安全认证、国际化与缓存

1. Todo Bean 定义

以下代码片段展示了 Todo bean 的定义:

"Resource«Todo»": {
  "type": "object",
  "properties": {
    "desc": {
      "type": "string"
    },
    "done": {
      "type": "boolean"
    },
    "id": {
      "type": "integer",
      "format": "int32"
    },
    "links": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/Link"
      }
    },
    "targetDate": {
      "type": "string",
      "format": "date-time"
    },
    "user": {
      "type": "string"
    }
  }
}

该代码定义了 Todo bean 中的所有元素及其格式。

2. Swagger UI

Swagger UI( http://localhost:8080/swagger-ui.html )可用于查看文档。通过在 pom.xml 中添加依赖( io.springfox:springfox-swagger-ui )来启用 Swagger UI。此外,在线的 Swagger UI( http://petstore.swagger.io )也可用于可视化任何 Swagger 文档(Swagger JSON)。

Swagger UI 展示控制器暴露的服务列表,点击控制器会展开显示其支持的请求方法和 URI。例如,POST 服务创建用户待办事项的详细信息在 Swagger UI 中会展示重要参数(包括请求体)、请求体预期结构以及服务返回的不同 HTTP 状态码。

3. 使用注解自定义 Swagger 文档

Swagger UI 提供注解来进一步自定义文档。例如,初始生成的获取待办事项服务的文档较为原始:

"/users/{name}/todos": {
  "get": {
    "tags": [
      "todo-controller"
    ],
    "summary": "retrieveTodos",
    "operationId": "retrieveTodosUsingGET",
    "consumes": [
      "application/json"
    ],
    "produces": [
      "*/*"
    ]
  }
}

可通过添加注解来改进文档,如:

@ApiOperation(
  value = "Retrieve all todos for a user by passing in his name",
  notes = "A list of matching todos is returned. Current pagination is not supported.",
  response = Todo.class,
  responseContainer = "List",
  produces = "application/json")
@GetMapping("/users/{name}/todos")
public List<Todo> retrieveTodos(@PathVariable String name) {
  return todoService.retrieveTodos(name);
}

注解的重要作用如下:
- @ApiOperation value 属性作为服务摘要显示在文档中。
- notes 属性作为服务描述显示在文档中。
- produces 属性自定义服务文档的 produces 部分。

更新后的文档如下:

"get": {
  "tags": [
    "todo-controller"
  ],
  "summary": "Retrieve all todos for a user by passing in his name",
  "description": "A list of matching todos is returned. Current pagination is not supported.",
  "operationId": "retrieveTodosUsingGET",
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json",
    "*/*"
  ]
}

Swagger 提供的其他重要注解如下表所示:
| 注解 | 作用 |
| ---- | ---- |
| @Api | 将类标记为 Swagger 资源 |
| @ApiModel | 为 Swagger 模型提供额外信息 |
| @ApiModelProperty | 添加和操作模型属性的数据 |
| @ApiOperation | 描述特定路径的操作或 HTTP 方法 |
| @ApiParam | 为操作参数添加额外元数据 |
| @ApiResponse | 描述操作的示例响应 |
| @ApiResponses | 包装多个 ApiResponse 对象的列表 |
| @Authorization | 声明在资源或操作上使用的授权方案 |
| @AuthorizationScope | 描述 OAuth 2 授权范围 |
| @ResponseHeader | 表示可作为响应一部分提供的头部 |

还有一些 Swagger 定义注解可用于自定义一组服务的高级信息,如:
| 注解 | 作用 |
| ---- | ---- |
| @SwaggerDefinition | 为生成的 Swagger 定义添加定义级属性 |
| @Info | Swagger 定义的一般元数据 |
| @Contact | 描述 Swagger 定义联系人的属性 |
| @License | 描述 Swagger 定义许可证的属性 |

4. 使用 Spring Security 保护 REST 服务

此前创建的服务均未进行安全保护,实际应用中的服务通常需要进行安全认证。这里将讨论两种 REST 服务的认证方式:基本认证和 OAuth 2.0 认证,并使用 Spring Security 实现。

4.1 添加 Spring Security 启动器

pom.xml 文件中添加以下依赖:

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

该依赖引入了三个重要的 Spring Security 依赖: spring-security-config spring-security-core spring-security-web

4.2 基本认证

spring-boot-starter-security 依赖默认会自动配置所有服务的基本认证。尝试访问服务时会收到“Access Denied”响应,例如请求 http://localhost:8080/users/Jack/todos 会返回:

{
  "timestamp": 1484120815039,
  "status": 401,
  "error": "Unauthorized",
  "message": "Full authentication is required to access this resource",
  "path": "/users/Jack/todos"
}

由于未配置用户 ID 和密码,Spring Boot 会自动配置默认的用户 ID( user )和密码(通常打印在日志中)。可以使用 Postman 发送带有基本认证详细信息的请求。也可在 application.properties 中配置自定义的用户 ID 和密码:

security.user.name=user-name
security.user.password=user-password

Spring Security 还支持使用 LDAP、JDBC 或其他数据源进行认证。

4.3 集成测试

由于基本认证,之前编写的集成测试会因无效凭证而失败,需要更新集成测试以提供基本认证凭证:

private TestRestTemplate template = new TestRestTemplate();
HttpHeaders headers = createHeaders("user-name", "user-password");

HttpHeaders createHeaders(String username, String password) {
  return new HttpHeaders() {
    {
      String auth = username + ":" + password;
      byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(Charset.forName("US-ASCII")));
      String authHeader = "Basic " + new String(encodedAuth);
      set("Authorization", authHeader);
    }
  };
}

@Test
public void retrieveTodos() throws Exception {
  String expected = "["
    + "{id:1,user:Jack,desc:\"Learn Spring MVC\",done:false}" + ","
    + "{id:2,user:Jack,desc:\"Learn Struts\",done:false}" + "]";
  ResponseEntity<String> response = template.exchange(
    createUrl("/users/Jack/todos"), HttpMethod.GET,
    new HttpEntity<String>(null, headers),
    String.class);
  JSONAssert.assertEquals(expected, response.getBody(), false);
}

关键步骤如下:
1. createHeaders 方法创建基本认证头部。
2. 使用 HttpEntity 将创建的头部提供给 REST 模板。

4.4 单元测试

为了在单元测试中不使用安全认证,可在 WebMvcTest 注解中设置 secure = false

@RunWith(SpringRunner.class)
@WebMvcTest(value = TodoController.class, secure = false)
public class TodoControllerTest {
5. OAuth 2 认证

OAuth 是一种协议,用于在一系列基于 Web 的应用程序和服务之间交换授权和认证信息,允许第三方应用从服务(如 Facebook、Twitter 或 GitHub)获取受限的用户信息。

5.1 重要术语
  • 资源所有者 :第三方应用的用户,决定 API 中哪些信息可提供给第三方应用。
  • 资源服务器 :托管要保护的 Todo API。
  • 客户端 :想要消费 API 的第三方应用。
  • 授权服务器 :提供 OAuth 服务的服务器。
5.2 高级流程
graph LR
    A[应用请求用户授权访问 API 资源] --> B[用户提供访问,应用获得授权许可]
    B --> C[应用向授权服务器提供用户授权许可和自身客户端凭证]
    C --> D{认证是否成功}
    D -- 是 --> E[授权服务器返回访问令牌]
    D -- 否 --> C
    E --> F[应用使用访问令牌调用 API(资源服务器)进行认证]
    F --> G{访问令牌是否有效}
    G -- 是 --> H[资源服务器返回资源详细信息]
    G -- 否 --> F

具体步骤如下:
1. 应用请求用户授权访问 API 资源。
2. 用户提供访问后,应用获得授权许可。
3. 应用向授权服务器提供用户授权许可和自身客户端凭证。
4. 若认证成功,授权服务器返回访问令牌。
5. 应用使用访问令牌调用 API(资源服务器)进行认证。
6. 若访问令牌有效,资源服务器返回资源详细信息。

5.3 为服务实现 OAuth 2 认证

pom.xml 文件中添加 spring-security-oauth2 依赖:

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

由于 spring-security-oauth2 在 2017 年 6 月尚未针对 Spring Framework 5.x 和 Spring Boot 2.x 进行更新,这里使用 Spring Boot 1.5.x。为了简化,让当前 API 服务器同时充当资源服务器和授权服务器:

@EnableResourceServer
@EnableAuthorizationServer
@SpringBootApplication
public class Application {

@EnableResourceServer 启用 OAuth 2 资源服务器的 Spring Security 过滤器,通过传入的 OAuth 2 令牌进行请求认证; @EnableAuthorizationServer 启用授权服务器。

application.properties 中配置访问详细信息:

security.user.name=user-name
security.user.password=user-password
security.oauth2.client.clientId: clientId
security.oauth2.client.clientSecret: clientSecret
security.oauth2.client.authorized-grant-types: authorization_code,refresh_token,password
security.oauth2.client.scope: openid

其中, security.user.name security.user.password 是资源所有者(第三方应用的最终用户)的认证详细信息; security.oauth2.client.clientId security.oauth2.client.clientSecret 是客户端(第三方应用)的认证详细信息。

5.4 执行 OAuth 请求

需要两步来访问 API:
1. 获取访问令牌 :调用授权服务器( http://localhost:8080/oauth/token ),以基本认证模式提供客户端认证详细信息,并将用户凭证作为表单数据的一部分。使用 grant_type password 表示发送用户认证详细信息以获取访问令牌,请求成功后会返回类似以下的响应:

{
  "access_token": "a633dd55-102f-4f53-bcbd-a857df54b821",
  "token_type": "bearer",
  "refresh_token": "d68d89ec-0a13-4224-a29b-e9056768c7f0",
  "expires_in": 43199,
  "scope": "openid"
}

access_token 用于后续 API 调用的认证,但通常有效期较短; refresh_token 可用于向认证服务器请求新的 access_token
2. 使用访问令牌执行请求 :在请求头的 Authorization 字段中提供访问令牌,格式为 "Bearer {access_token}"

5.5 集成测试

更新集成测试以提供 OAuth 2 凭证:

@Test
public void retrieveTodos() throws Exception {
  String expected = "["
    + "{id:1,user:Jack,desc:\"Learn Spring MVC\",done:false}" + ","
    + "{id:2,user:Jack,desc:\"Learn Struts\",done:false}" + "]";
  String uri = "/users/Jack/todos";
  ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
  resource.setUsername("user-name");
  resource.setPassword("user-password");
  resource.setAccessTokenUri(createUrl("/oauth/token"));
  resource.setClientId("clientId");
  resource.setClientSecret("clientSecret");
  resource.setGrantType("password");
  OAuth2RestTemplate oauthTemplate = new OAuth2RestTemplate(resource, new DefaultOAuth2ClientContext());
  ResponseEntity<String> response = oauthTemplate.getForEntity(createUrl(uri), String.class);
  JSONAssert.assertEquals(expected, response.getBody(), false);
}

关键步骤如下:
1. 设置 ResourceOwnerPasswordResourceDetails 包含用户凭证和客户端凭证。
2. 配置认证服务器的 URL。
3. 使用 OAuth2RestTemplate 支持 OAuth 2 协议。

6. 国际化

国际化(i18n)是开发应用程序和服务,使其能够针对不同语言和文化进行定制的过程,也称为本地化。Spring Boot 内置了对国际化的支持。

6.1 配置

Application.java 中添加 LocaleResolver 和消息源:

@Bean
public LocaleResolver localeResolver() {
  SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
  sessionLocaleResolver.setDefaultLocale(Locale.US);
  return sessionLocaleResolver;
}

@Bean
public ResourceBundleMessageSource messageSource() {
  ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
  messageSource.setBasenames("messages");
  messageSource.setUseCodeAsDefaultMessage(true);
  return messageSource;
}

重要配置说明:
- sessionLocaleResolver.setDefaultLocale(Locale.US) :设置默认区域为美国。
- messageSource.setBasenames("messages") :设置消息源的基本名称为 messages ,根据不同区域(如 fr )使用不同的属性文件( message_fr.properties ),若消息不存在则使用默认的 message.properties
- messageSource.setUseCodeAsDefaultMessage(true) :若未找到消息,则返回代码作为默认消息。

6.2 配置消息

messages.properties 中设置默认消息:

welcome.message=Welcome in English

messages_fr.properties 中设置法语消息:

welcome.message=Welcome in French
6.3 创建服务

创建一个根据请求头中的 Accept-Language 返回特定消息的服务:

@GetMapping("/welcome-internationalized")
public String msg(@RequestHeader(value = "Accept-Language", required = false) Locale locale) {
  return messageSource.getMessage("welcome.message", null, locale);
}

@RequestHeader 从请求头中获取区域信息,若未指定则使用默认区域; messageSource.getMessage 根据给定区域获取欢迎消息。

7. 缓存

缓存服务数据对于提高应用程序的性能和可扩展性至关重要。Spring Boot 提供了基于注解的缓存抽象,这里先介绍 Spring 缓存注解,后续会引入 JSR - 107 缓存注解并与 Spring 抽象进行比较。

7.1 添加 Spring-boot-starter-cache

pom.xml 中添加以下依赖:

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

添加此依赖会引入启用 JSR - 107 和 Spring 缓存注解所需的所有依赖。

7.2 启用缓存

在应用程序中启用缓存:

@EnableCaching
@SpringBootApplication
public class Application {

@EnableCaching 会在 Spring Boot 应用程序中启用缓存,Spring Boot 会自动配置合适的 CacheManager 框架作为相关缓存的提供者。

8. 缓存管理器的工作机制

Spring Boot 自动配置 CacheManager 时,会根据类路径中的依赖来选择合适的实现。常见的 CacheManager 实现及其依赖如下表所示:
| CacheManager 实现 | 依赖 |
| ---- | ---- |
| SimpleCacheManager | 无额外依赖,使用简单的内存缓存 |
| ConcurrentMapCacheManager | 使用 ConcurrentMap 存储缓存项,适用于开发和测试环境 |
| EhCacheCacheManager | 需要 ehcache 依赖,提供更高级的缓存配置 |
| RedisCacheManager | 需要 spring-boot-starter-data-redis 依赖,使用 Redis 作为缓存存储 |

例如,如果类路径中存在 ehcache 依赖,Spring Boot 会自动配置 EhCacheCacheManager

8.1 使用 Spring 缓存注解

Spring 提供了一系列注解来简化缓存操作,常用注解如下:
- @Cacheable :标记方法的返回值可以被缓存,下次调用相同参数的方法时,直接从缓存中获取结果。

@Cacheable("todos")
public Todo getTodoById(Long id) {
    // 从数据库或其他数据源获取 Todo
    return todoRepository.findById(id).orElse(null);
}
  • @CachePut :更新缓存中的值,无论缓存中是否存在,都会执行方法并更新缓存。
@CachePut("todos")
public Todo updateTodo(Todo todo) {
    // 更新数据库中的 Todo
    return todoRepository.save(todo);
}
  • @CacheEvict :从缓存中移除指定的缓存项。
@CacheEvict("todos")
public void deleteTodo(Long id) {
    // 从数据库中删除 Todo
    todoRepository.deleteById(id);
}
8.2 缓存注解的参数配置

这些注解还支持一些参数配置,例如:
- key :指定缓存的键,可以使用 SpEL 表达式。

@Cacheable(value = "todos", key = "#id")
public Todo getTodoById(Long id) {
    // ...
}
  • condition :指定缓存的条件,只有满足条件时才会进行缓存操作。
@Cacheable(value = "todos", condition = "#id > 0")
public Todo getTodoById(Long id) {
    // ...
}
9. 比较 Spring 缓存抽象与 JSR - 107 缓存注解

JSR - 107(JCache)是 Java 缓存标准,定义了一组缓存 API 和注解。Spring 缓存抽象与 JSR - 107 缓存注解有一些相似之处,但也存在一些差异。

9.1 相似点

两者都提供了基于注解的缓存操作方式,都可以标记方法进行缓存、更新缓存和清除缓存。

9.2 差异点
比较项 Spring 缓存抽象 JSR - 107 缓存注解
注解名称 @Cacheable @CachePut @CacheEvict @CacheResult @CachePut @CacheRemove
配置灵活性 支持更多的参数配置,如 condition unless 相对较少的参数配置
集成性 与 Spring 框架紧密集成,使用方便 更注重标准性,可与不同的缓存实现集成

例如,使用 JSR - 107 缓存注解的示例代码如下:

import javax.cache.annotation.CacheResult;

@CacheResult(cacheName = "todos")
public Todo getTodoById(Long id) {
    // 从数据库或其他数据源获取 Todo
    return todoRepository.findById(id).orElse(null);
}
10. 缓存的最佳实践

在使用缓存时,需要注意以下最佳实践:
- 合理选择缓存策略 :根据业务需求选择合适的缓存策略,例如对于频繁读取且不经常变化的数据可以使用缓存。
- 设置合理的缓存过期时间 :避免缓存数据过期后仍然被使用,导致数据不一致。
- 处理缓存击穿和缓存雪崩 :可以使用分布式锁、限流等技术来处理缓存击穿和缓存雪崩问题。

11. 总结

本文介绍了微服务扩展的多个方面,包括 Swagger 文档的使用和自定义、Spring Security 对 REST 服务的安全保护(基本认证和 OAuth 2 认证)、国际化的实现以及缓存的配置和使用。通过这些技术,可以提高微服务的安全性、可维护性和性能。

  • Swagger 文档可以帮助开发者更好地理解和使用 API,通过注解可以进一步定制文档。
  • Spring Security 提供了多种认证方式,确保服务的安全性。
  • 国际化使服务能够适应不同语言和文化的用户。
  • 缓存可以显著提高应用程序的性能和可扩展性。

在实际开发中,需要根据具体需求选择合适的技术和配置,以构建高效、安全、易用的微服务系统。

graph LR
    A[微服务扩展] --> B[Swagger 文档]
    A --> C[Spring Security 认证]
    A --> D[国际化]
    A --> E[缓存]
    B --> B1[定义 Todo Bean]
    B --> B2[使用 Swagger UI]
    B --> B3[自定义文档注解]
    C --> C1[基本认证]
    C --> C2[OAuth 2 认证]
    D --> D1[配置 LocaleResolver 和消息源]
    D --> D2[配置消息文件]
    D --> D3[创建国际化服务]
    E --> E1[添加 Spring - boot - starter - cache]
    E --> E2[启用缓存]
    E --> E3[使用缓存注解]
    E --> E4[比较 Spring 与 JSR - 107 注解]

通过以上流程图,可以清晰地看到微服务扩展各个部分之间的关系和主要步骤。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值