微服务扩展: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 注解]
通过以上流程图,可以清晰地看到微服务扩展各个部分之间的关系和主要步骤。
2879

被折叠的 条评论
为什么被折叠?



