Spring Gateway

Spring Gateway

Java反应式框架Reactor中的Mono和Flux - 码农小胖哥 - 博客园 (cnblogs.com)

业务

Spring Gateway

除此之外,Spring Gateway还可以通过整合其他组件来实现更为完善的功能。比如:

  1. 整合Spring Security:Spring Security是Spring生态圈中的一款强大的身份验证和授权框架,可以提供基于角色的访问控制和OAuth2认证等功能,可以与Spring Gateway集成,增强API网关的安全性。

  2. 整合OAuth2协议:OAuth2是一种流行的授权协议,可以用于客户端和API网关的交互,同时也可以为后端服务提供安全保障。Spring Gateway可以通过整合Spring Security和Spring Cloud Security等组件来实现OAuth2认证的功能。

  3. 整合服务发现和注册中心:Spring Gateway可以使用Eureka、Consul等注册中心来实现服务的发现和负载均衡,从而使API网关可以自动发现和调用服务。

  4. 整合开发框架:Spring Gateway可以通过整合Spring Boot、Spring Cloud等框架来简化开发过程,提高开发效率和代码可维护性。

总之,通过Spring Gateway和其他组件的整合,可以实现更为全面和强大的API网关功能,为企业应用架构的构建提供更多的可能性。

鉴权 + 熔断

这里提供一个实现Spring Gateway、Sentinel和Spring Security的示例代码,实现了接口访问次数统计、熔断降级、权限验证和OAuth2认证等功能。

首先是pom.xml文件的配置:

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
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<!-- Spring Cloud Gateway -->
<dependency>
<>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!-- Spring Cloud Sentinel -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

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

<!-- Spring Security OAuth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactIdspring-cloud-starter-security-oauth2</artifactId>
</dependency>

接下来是application.yml文件的配置:

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
server:
port: 8080

spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/product/**
application:
name: api-gateway
security:
oauth2:
client:
registration:
oauth-client:
provider: my-oauth2-server
client-id: client
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
scope: email, profile, openid
provider:
my-oauth2-server:
authorize-uri: http://localhost:8081/oauth/authorize
token-uri: http://localhost:8081/oauth/token
user-info-uri: http://localhost:8081/userinfo
jwk-set-uri: http://localhost:8081/.well-known/jwks.json
user-name-attribute: sub

其中,OAuth2相关配置需要根据具体的情况进行修改。

接下来是启动类的代码:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@SpringBootApplication
@EnableDiscoveryClient
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@EnableOAuth2Client
public class ApiGatewayApplication {

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

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange()
.pathMatchers("/actuator/**").permitAll() // 放行actuator节点
.pathMatchers("/login/oauth2/**").permitAll() // 放行OAuth2登录页面
.anyExchange().authenticated() // 其他路径需要认证
.and()
.oauth2Login() // 启用OAuth2登录
.and()
.oauth2ResourceServer().jwt(); // 启用OAuth2资源服务器
return http.build();
}

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service", r -> r.path("/api/user/**").filters(f -> f.circuitBreaker(c -> c.setName("user-service-cb").setFallbackUri("forward:/user-service-fallback"))).uri("lb://user-service"))
.route("product-service", r -> r.path("/api/product/**").filters(f -> f.circuitBreaker(c -> c.setName("product-service-cb").setFallbackUri("forward:/product-service-fallback"))).uri("lb://product-service"))
.build();
}

@Bean
public CustomFallbackHandler userServiceFallbackHandler() {
return new CustomFallbackHandler("user-service is unavailable, please try again later.");
}

@Bean
public CustomFallbackHandler productServiceFallbackHandler() {
return new CustomFallbackHandler("product-service is unavailable, please try again later.");
}

@Bean
public SentinelGatewayFilterFactory sentinelGatewayFilterFactory() {
return new SentinelGatewayFilterFactory();
}

@Bean
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(new DefaultBlockExceptionHandler(), new CustomBlockPageHandler());
}

@Bean
public BlockRequestHandler blockRequestHandler() {
return new CustomBlockRequestHandler();
}
}

其中,SecurityWebFilterChain负责OAuth2认证的配置,customRouteLocator负责路由规则的配置,CustomFallbackHandler负责熔断降级的处理,SentinelGatewayFilterFactory、SentinelGatewayBlockExceptionHandler和BlockRequestHandler负责Sentinel的集成和异常处理。

路由规则中,添加了熔断器的配置,当访问/user-service和/product-service服务时出现异常时,将跳转到/user-service-fallback和/product-service-fallback路径,展示友好的提示页面。

最后是熔断降级的处理类CustomFallbackHandler的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class CustomFallbackHandler implements HandlerFunction<Response> {

private final String message;

public CustomFallbackHandler(String message) {
this.message = message;
}

@Override
public Mono<Response> handle(ServerRequest serverRequest) {
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(Collections.singletonMap("message", message)));
}
}

这里定义了一个简单的处理逻辑,返回一个包含错误信息的json消息。可以根据具体需求进行定制。

至此,一个集成Spring Gateway、Sentinel和Spring Security的API网关就搭建完成了。实现了统计接口访问次数、接口隔离、权限验证和熔断降级等功能,为企业应用架构的构建提供了有力保障。

接口调用次数

是的,可以通过Redis和MySQL实现统计每个接口的调用次数。具体实现方式如下:

  1. 定义一个拦截器,用于统计每个接口的访问次数。在每个接口执行之前,从Redis中读取该接口的访问次数,并将其累加1;在接口执行完成之后,将访问次数写回Redis中。
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
@Component
public class CountInterceptor implements HandlerInterceptor {

@Autowired
private RedisTemplate<String, Integer> redisTemplate;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从Redis中读取接口的访问次数
String uri = request.getRequestURI();
Integer count = redisTemplate.opsForValue().get(uri);

if (count == null) {
// 如果Redis中没有该接口的访问次数,则新建一个
redisTemplate.opsForValue().set(uri, 1);
} else {
// 如果Redis中已经有该接口的访问次数,则将其加1
redisTemplate.opsForValue().increment(uri);
}

return true;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 将访问次数写回Redis中
String uri = request.getRequestURI();
Integer count = redisTemplate.opsForValue().get(uri);

if (count != null) {
// 如果Redis中有该接口的访问次数,则将其写回
count = redisTemplate.opsForValue().decrement(uri);
if (count <= 0) {
// 如果访问次数已经为0,则从Redis中删除该接口的访问次数
redisTemplate.delete(uri);
}
}
}
}
  1. 将拦截器注册到Spring的MVC配置中。在Spring MVC配置中添加一个interceptor bean,然后将该拦截器添加到interceptor列表中即可。
1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class MvcConfig implements WebMvcConfigurer {

@Autowired
private CountInterceptor countInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(countInterceptor).addPathPatterns("/**");
}
}
  1. 创建一个定时任务,在任务中将Redis中的接口访问次数数据写入到MySQL中。定时任务可以使用Spring的Scheduled注解实现。
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
@Component
public class DataSyncTask {

@Autowired
private RedisTemplate<String, Integer> redisTemplate;

@Autowired
private ApiCallCountRepo apiCallCountRepo;

/**
* 每60秒同步一次Redis里的数据到MySQL
*/
@Scheduled(fixedRate = 60 * 1000)
public void syncRedisToDb() {
Set<String> keys = redisTemplate.keys("*");
List<ApiCallCount> apiCallCounts = new ArrayList<>();
for (String key : keys) {
Integer count = redisTemplate.opsForValue().get(key);
ApiCallCount apiCallCount = new ApiCallCount();
apiCallCount.setUri(key);
apiCallCount.setCount(count);
apiCallCount.setCreateTime(new Date());
apiCallCounts.add(apiCallCount);
}
apiCallCountRepo.saveAll(apiCallCounts);
}
}
  1. 创建一个MySQL表,用于保存每个接口的访问次数信息。该表可以使用以下的SQL命令创建:
1
2
3
4
5
6
CREATE TABLE api_call_count (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
uri VARCHAR(255) NOT NULL,
count INT NOT NULL,
create_time TIMESTAMP NOT NULL
);
  1. 创建一个JPA实体类,用于映射MySQL表中每个接口的访问次数信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Entity
@Table(name = "api_call_count")
public class ApiCallCount {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "uri")
private String uri;

@Column(name = "count")
private Integer count;

@Column(name = "create_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;

// getter and setter methods omitted
}

至此,可以通过Redis+MySQL的方式统计每个接口的实际访问次数,并且实时保存和更新,同时还可以将数据进行持

源码解读


Spring Gateway
http://example.com/2023/06/01/分布式组件+常见组件/Spring Gateway/
作者
where
发布于
2023年6月1日
许可协议