gateway配置_springcloud 之 gateway

值得一提的是低版本的 springcloud 网关是 zuulspringboot 2.x 新版本的网关是 gateway

通过本文你可以学到2点:

  • 如何在网关层重写 http 请求与响应
  • 如何通过代码的方式配置路由

一、网关的引入用来解决什么问题

gateway 主要做2件事:

  • 路由
  • 过滤

二、如何使用路由

配置文件的方式使用路由的例子很多,这里我不再赘述,这里我只打算写一下通过代码完成路由配置

AuthAutoConfiguration 创建一个配置文件

import com.flashwhale.cloud.gateway.filter.EncryptionFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.ws.rs.HttpMethod;
import java.util.Objects;

/**
* 这里进行转发路由的配置
* 注意,如果路由需要有顺序匹配的,需要使用 order 方法 值越小优先匹配
*/
@Slf4j
@Configuration
public class AuthAutoConfiguration {

@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
/**
* 针对 cloud-gateway 提供 dh 服务的普通路由
*/
.route("cloud-gateway-dh", r ->
r
.path("/dh/**")
.uri("lb://cloud-gateway")
)
/**
* 针对 user-server 服务的普通路由
*/
.route("user-server", r ->
r
.path("/user_api/**")
.uri("lb://user-server")
)
/**
* 针对 user-web 服务的普通路由
*/
.route("user-web", r ->
r
.path("/user_web/**")
.uri("lb://user-web")
)
.build();
}
}
说明:
  • 一个 .route 就是一个路由规则
  • 路由中的 id 是唯一的,不一定需要与服务名对应,不重复即可
  • path 方法匹配是根据路径来的,实际上是一个通配,所以这里需要在对应的服务前加上前缀,类似这样:
server:
servlet:
context-path: /user_api
  • uri 方法是必须的,必须声明一个地址,通过 lb://服务名 可以使用到 eureka 负载均衡的转发特性,当然也可以使用类似 http://localhost:8080 等方式

二、如何使用过滤

同样的配置文件的方式使用过滤的例子很多,这里我不再赘述,这里我只打算写一下通过代码完成过滤配置
  • EncryptionFilter 编写一个网关过滤器,这里不是全局的,当前演示的过滤器是修改请求和响应体完成加密解密的过程,有关 DH 的部分请参考 dh秘钥交换算法实践[1] 如果这篇文章不可见,请移步我的公众号查看,简书的规则不知道是什么,就锁定了这篇文章。
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flashwhale.cloud.gateway.common.DHProperties;
import com.flashwhale.cloud.gateway.common.DTO.DataDTO;
import com.flashwhale.cloud.gateway.utlis.AESUtil;
import com.flashwhale.cloud.gateway.utlis.dh.server.DHService;
import io.netty.buffer.ByteBufAllocator;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.ws.rs.HttpMethod;
import java.io.InputStream;
import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;

/**
* 加密/解密输出内容
*/
@Component
@Slf4j
public class EncryptionFilter implements GatewayFilter, Ordered {

@Autowired
ObjectMapper objectMapper;
@Autowired
DHService dhService;
@Autowired
DHProperties dhProperties;


@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String urlPath = request.getURI().getPath();
String schema = request.getURI().getScheme();
if ((!"http".equals(schema) && !"https".equals(schema))) {
log.warn("当前访问【:{}】不是http请求", urlPath);
return chain.filter(exchange);
}
if (!request.getMethod().matches(HttpMethod.POST)) {
log.warn("当前访问【:{}】不是http post 请求", urlPath);
return chain.filter(exchange);
}
if (!dhProperties.getOpen()) {
log.debug("当前访问 【:{}】没有启用全局加密/解密", request.getURI());
return chain.filter(exchange);
}
log.info("当前访问【:{}】", urlPath);
//todo 暂时不使用这种方式获取 body数据
// LinkedHashMap requestBodyMap = exchange.getAttribute("cachedRequestBodyObject");
String bodyStr = resolveBodyFromRequest(request);
JsonNode parametersJson = null;
try {
parametersJson = objectMapper.readTree(bodyStr);
} catch (JsonProcessingException e) {
log.error("当前访问 【:{}】解析 body 数据发生异常", urlPath, e);
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
return chain.filter(exchange);
}
String uuid = parametersJson.get(DataDTO.UUID_NAME).asText();
String parameters = parametersJson.get(DataDTO.PARAMETER_NAME).asText();
log.info("获取的值 requestBody:{}", bodyStr);
ServerHttpRequestDecorator requestDecorator = processRequest(uuid, parameters, request, response);
ServerHttpResponseDecorator responseDecorator = processResponse(response, uuid, urlPath);
return chain.filter(exchange.mutate().request(requestDecorator).response(responseDecorator).build());
// return chain.filter(exchange.mutate().request(requestDecorator).build());
}
/**
* 构造 request
*
* @param uuid uuid
* @param parameters 要解密的信息
* @param request ServerHttpRequest
* @param response ServerHttpResponse
* @return 构造的 CustomRequestDecorator
*/
ServerHttpRequestDecorator processRequest(String uuid,
String parameters,
ServerHttpRequest request,
ServerHttpResponse response) {
URI uri = request.getURI();
URI ex = UriComponentsBuilder.fromUri(uri).build(true).toUri();
ServerHttpRequest newRequest = request.mutate().uri(ex).build();
return new ServerHttpRequestDecorator(newRequest) {
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
headers.entrySet().forEach(h -> {
System.out.println(h.getKey() + " | " + h.getValue());
});
return headers;
}
@SneakyThrows
@Override
public Flux getBody() {
//todo 另外一种获取body方式的实现
// if (null == requestBodyMap || requestBodyMap.isEmpty()) return super.getBody();
// String jsonBody = objectMapper.writeValueAsString(requestBodyMap);
// DataBuffer dataBuffer = bufferFactory.allocateBuffer();
return Flux.just(stringBuffer(decrypt(uuid, parameters, request.getPath().value(), response)));
}
};
}
/**
* 从Flux中获取字符串的方法
*
* @param serverHttpRequest ServerHttpRequest
* @return 请求体
*/
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
//获取请求体
Flux body = serverHttpRequest.getBody();
AtomicReference bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
//获取request body
return bodyRef.get();
}
/**
* 将字符串写入 DataBuffer
*
* @param value 要写入的字符串
* @return DataBuffer
*/
DataBuffer stringBuffer(String value) {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
return buffer;
}
/**
* 解密的业务
*
* @param uuid uuid
* @param parameters body块的json字符串
* @param urlPath 访问路由
* @param response ServerHttpResponse
* @return 解密后的对象
*/
String decrypt(String uuid, String parameters, String urlPath, ServerHttpResponse response) {
if (!StringUtils.hasText(parameters)) {
log.warn("当前访问 【:{}】 body 中没有信息", urlPath);
return "";
}
if (!StringUtils.hasText(uuid)) {
log.warn("当前访问 【:{}】 uuid 不存在", urlPath);
response.setStatusCode(HttpStatus.BAD_REQUEST);
return "";
}
try {
String key = dhService.getKey(uuid);
if (!StringUtils.hasText(key)) {
log.warn("当前访问 【:{}】 中没有获取到协商信息 uuid为 【:{}】", urlPath, uuid);
response.setStatusCode(HttpStatus.BAD_REQUEST);
return "";
}
String dParameters = AESUtil.decrypt(parameters, key);
log.debug("当前访问 【:{}】 解密内容为 【:{}】", urlPath, dParameters);
return dParameters;
} catch (Exception e) {
log.error("当前访问 【:{}】解析 body 数据发生未知异常", urlPath, e);
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
}
return "";
}
/**
* 构造 response
*
* @param response ServerHttpResponse
* @param uuid 客户端协商的唯一标记
* @param urlPath 访问路由
* @return 构造的 ServerHttpResponseDecorator
*/
ServerHttpResponseDecorator processResponse(ServerHttpResponse response,
String uuid,
String urlPath) {
DataBufferFactory bufferFactory = response.bufferFactory();
return new ServerHttpResponseDecorator(response) {
@SuppressWarnings("unchecked")
@Override
public Mono writeWith(Publisher extends DataBuffer> body) {
if (body instanceof Flux) {
Flux extends DataBuffer> flux = (Flux extends DataBuffer>) body;
return super.writeWith(flux.map(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
return bufferFactory.wrap(encrypt(uuid, charBuffer.toString(), urlPath).getBytes(StandardCharsets.UTF_8));
}));
}
return super.writeWith(body);
}
};
}
/**
* 响应加密
*
* @param uuid 客户端协商的唯一标记
* @param content 要加密的内容
* @param urlPath 访问路由
* @return 加密内容
*/
String encrypt(String uuid, String content, String urlPath) {
if (!StringUtils.hasText(uuid)) {
log.warn("当前请求响应 【:{}】 uuid 不存在,不进行加密", urlPath);
return content;
}
String key = dhService.getKey(uuid);
if (!StringUtils.hasText(key)) {
log.warn("当前访问 【:{}】 中没有获取到协商信息,不进行加密 uuid为 【:{}】", urlPath, uuid);
return content;
}
return AESUtil.encrypt(content, key);
}
@Override
public int getOrder() {
//由于 response的原因 需要保证顺序在 NettyWriteResponseFilter 之前
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
}
  • 增加路由配置AuthAutoConfiguration 这里我贴出完整的配置
import com.flashwhale.cloud.gateway.filter.EncryptionFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.ws.rs.HttpMethod;
import java.util.Objects;

/**
* 这里进行转发路由的配置
* 注意,如果路由需要有顺序匹配的,需要使用 order 方法 值越小优先匹配
*/
@Slf4j
@Configuration
public class AuthAutoConfiguration {
@Autowired
EncryptionFilter encryptionFilter;

@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
/**
* 针对 cloud-gateway 提供 dh 服务的普通路由
*/
.route("cloud-gateway-dh", r ->
r
.path("/dh/**")
.uri("lb://cloud-gateway")
)
/**
* 针对 user-server 服务的普通路由
*/
.route("user-server", r ->
r
.path("/user_api/**")
.uri("lb://user-server")
)
/**
* 针对 user-web 服务的普通路由
*/
.route("user-web", r ->
r
.path("/user_web/**")
.uri("lb://user-web").order(2)
)
/**
* 针对 user-web 服务需要加密的路由
*/
.route("user-web-encryption", r ->
r
.readBody(Object.class, requestBody -> true)
.and()
.path("/user_web/encryption/**")
.and()
.predicate(p -> Objects.requireNonNull(p.getRequest().getMethod()).matches(HttpMethod.POST))
.filters(f -> f.filter(encryptionFilter))
.uri("lb://user-web").order(1)
)

.build();
}
}
说明:
  • 当一个服务需要部分路由到过滤器,其他服务正常路由的时候,类似 user-webuser-web-encryption ,两个实际上是一个路由,一个是通配,一个是特殊匹配,注意最后面的 order 方法,是必须保证特殊匹配的路由要在通配之前匹配(值越小越先匹配)
  • .readBody(Object.class, requestBody -> true) 这一行用来解决高版本的 spring cloud 出现 body 有时候取不到值的情况(对,你没有看错,就是有时候取不到值)
  • .predicate(p -> Objects.requireNonNull(p.getRequest().getMethod()).matches(HttpMethod.POST)) 用来匹配 http post 方法
  • .filters(f -> f.filter(encryptionFilter)) 这就是一个过滤器的配置了,当匹配到对应的请求,就会应用到过滤器,记得过滤器中也有一个 order 方法,要特别注意顺序

到此网关基本使用完成,最后贴一下网关的关键配置:

spring:
cloud:
gateway:
discovery:
locator:
enabled: true #启用网关功能
lowerCaseServiceId: true #是否忽略服务名大小写
99f1dbf0c19c839240a72a5aa4a910b4.png
欢迎关注我的个人公众号

参考资料

[1]

dh秘钥交换算法实践: https://www.jianshu.com/p/2c45126f64a1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
配置 Spring Cloud Gateway负载均衡可以使用 Spring Cloud 提供的 Ribbon 和 LoadBalancerClient,并结合 Gateway 的 Route 来实现。 首先在 pom.xml 文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> ``` 然后在 application.yml 文件中配置 Ribbon 的服务列表和 Gateway 的 Route: ```yaml spring: cloud: gateway: routes: - id: service1_route uri: lb://service1 predicates: - Path=/service1/** - id: service2_route uri: lb://service2 predicates: - Path=/service2/** ribbon: eureka: enabled: false listOfServers: http://localhost:8081,http://localhost:8082 ``` 上面的配置中,Ribbon 的服务列表使用 `listOfServers` 配置,多个服务使用逗号分隔。如果使用 Eureka 作为服务注册中心,可以将 `eureka.enabled` 设置为 `true`,这样 Ribbon 就会自动获取服务列表。 Gateway 的 Route 配置中,`uri` 使用 `lb://` 开头表示使用 Ribbon 进行负载均衡,`id` 为 Route 的唯一标识符,`predicates` 则是匹配请求的条件,这里使用 `Path` 匹配请求路径。 最后,需要在 Gateway 的启动类上添加 `@EnableDiscoveryClient` 注解,启用服务发现功能。 ```java @SpringBootApplication @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } } ``` 这样就完成了 Spring Cloud Gateway负载均衡配置

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值