sygateway的介绍和使用原理请自行查阅和学习,本章内容需在对spring cloud gateway的基本的了解和一定的知识基础上进行的。附上一个可供学习的博文:SpringCloud gateway (史上最全) - 疯狂创客圈 - 博客园
需求场景:
前端对post请求的数据进行加密(主流的加密方式),后端需要解密,将接口的响应数据进行加密返回,前端解密并展示或其他操作等。出于安全性的考虑和可扩展性故使用了网关来做。
涉及到的问题:
1、request的body数据流读取只能读取一次,controller层获取不到抛异常问题等
2、加解密采用对称加密算法是否前后端通用包括:ios、android、js
设计思路:(尽量使网关通用切可配置减少网关的代码修改和上线)
1、框架为springcloud+nacos+gateway+feign 后期可以加入hystrix等
2、在网关层进行对token的验证
3、在网关层进行统一解密request数据,并将header中添加特定数据,业务系统拦截器中进行对header的数据进行识别处理
4、业务系统返回数据后在网管层中对response数据进行加密返回,前端进行解密
直接上代码:(对token的验证)
启动类很普通,注册发现nacos相关注解
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
server:
port: 8080 #网关端口号
spring:
application:
name: k_men-gateway
active: dev
cloud:
nacos:
discovery:
server-addr: localhost:8848 #本地nacos需要启动默认8848端口
config:
server-addr: localhost:8848 #本地nacos需要启动默认8848端口
file-extension: yaml
group: DEFAULT_GROUP
gateway:
discovery:
locator:
enabled: true #开启动态路由的功能
对token验证的自定义filter(代码中涉及到自定义的返回体或者返回错误码之类的自行替换)
@Slf4j
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {
@Autowired
private RedisUtils redisUtils;
public AuthGatewayFilterFactory() {
super(Config.class);
}
@Autowired
private ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory;
@Autowired
private ObjectMapper objectMapper;
/**
* 具体的过滤逻辑
* 1.如果没有登录令牌token,直接返回没有授权的信息
* 2.如果有登录令牌token
* 2.1通过令牌校验,继续向下执行
* 2.2没有通过校验,返回没有通过校验的原因
*
* @param config 自定义配置类
* @return 网关过滤器
*/
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
ServerHttpResponse resp = exchange.getResponse();
resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
try {
if (!exchange.getRequest().getPath().toString().contains("admin/login")){
if (StrUtil.isBlank(token) || JwtUtils.getAppTokenKey(token) == null || !token.equals(redisUtils.get(JwtUtils.getAppTokenKey(token)))) {
String result = JSONUtil.parseObj(R.fail(ErrorCodeEnum.TOKEN_ERROR.getCode(), "token error")).toStringPretty();
DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
return resp.writeWith(Flux.just(buffer));
}
}
return chain.filter(exchange.mutate().build());
} catch (Exception e) {
log.error(e.getMessage(), e);
String result = JSONUtil.parseObj(R.fail(ErrorCodeEnum.TOKEN_ERROR.getCode(), e.getMessage())).toStringPretty();
DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
return resp.writeWith(Flux.just(buffer));
}
};
}
/**
* 自定义配置类
*/
public static class Config {
}
}
@Configuration
public class GatewayConfig {
/**
* 注入自定义授权网关过滤器工厂
*/
@Bean
public AuthGatewayFilterFactory authGatewayFilterFactory() {
return new AuthGatewayFilterFactory();
}
}
spring:
cloud:
gateway:
routes:
- id: k_men_cp_routh_auth
uri: lb://k_men-cp-api-test-local
predicates:
- Path=/**
filters:
- Auth
实现方式:(对header添加数据上面链接中有,下面重点讲对request和response的处理基于上述部分代码)
一、使用java代码进行配置
优点:简单,容易理解,代码量少
缺点:编码频繁,不能配置于yaml文件进行热更新,新增服务需要进行编码配置,缺少灵活性
废话不多说直接上代码:
@Slf4j
public class RequestBodyRewrite implements RewriteFunction<String, String> {
public RequestBodyRewrite() {
}
@Override
public Publisher<String> apply(ServerWebExchange exchange, String body) {
try {
String data = DESedeUtils.decrypt(body);//加密代码处理
return Mono.just(data);
} catch (Exception ex) {
log.error("1. json process fail", ex);
return Mono.error(new Exception("1. json process fail", ex));
}
}
}
@Slf4j
public class ResponseBodyRewrite implements RewriteFunction<String, String> {
public ResponseBodyRewrite() {
}
@Override
public Publisher<String> apply(ServerWebExchange exchange, String body) {
try {
String data = DESedeUtils.encrypt(body);//加密代码处理
return Mono.just(data);
} catch (Exception ex) {
log.error("2. json process fail", ex);
return Mono.error(new Exception("2. json process fail", ex));
}
}
}
@Bean
public RouteLocator routes(RouteLocatorBuilder builder, ObjectMapper objectMapper) {
return builder
.routes()
.route("path_route_change",
r -> r.path("/**")
.filters(f -> f
.modifyRequestBody(String.class,String.class,new RequestBodyRewrite())
.modifyResponseBody(String.class, String.class, new ResponseBodyRewrite())
).uri("lb://k_men-cp-api-test")).build();
}
二、自定义filter及配置文件方式
优点:支持yaml配置,配置文件热更新生效,灵活通用,可扩展性好,减少网关的频繁上线
缺点:代码稍微难以理解,代码多等
废话不多说直接上代码:
启动类上加动态刷新@RefreshScope 注解
配置文件放在nacos上使用配合上面的配置文件中的名字 k_men-gateway-dev.yaml
spring:
cloud:
gateway:
routes:
- id: lc_cp_routh
uri: lb://k_men-cp-api-test-local #需要转发到的服务在nacos上的name
predicates:
- Path=/**
filters:
- DecryptRequestBody
- EncryptResponseBody
对request的body的修改解密处理
@Slf4j
public class DecryptRequestBodyGatewayFilterFactory extends AbstractGatewayFilterFactory implements Ordered {
private final List<HttpMessageReader<?>> messageReaders;
public DecryptRequestBodyGatewayFilterFactory() {
this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
}
@Override
@SuppressWarnings("unchecked")
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerRequest serverRequest = ServerRequest.create(exchange,
this.messageReaders);
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
.flatMap(originalBody -> modifyBody().apply(exchange,Mono.just(originalBody)));
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange,headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
ServerHttpRequest decorator = decorate(exchange, headers,outputMessage);
return chain.filter(exchange.mutate().request(decorator).build());
}));
};
}
private BiFunction<ServerWebExchange, Mono<String>, Mono<String>> modifyBody() {
return (exchange, body) -> {
try {
AtomicReference<String> result = new AtomicReference<>();
body.subscribe(value -> result.set(EncryptUtils.decryptStr(value)),
e -> log.error(e.getMessage(), e)
);
return Mono.just(result.get());
} catch (Exception e) {
log.error("gateway parameter decryption exception", e);
throw new DecryptParamException("Parameter decryption exception");
}
};
}
private ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers,
CachedBodyOutputMessage outputMessage) {
return new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public HttpHeaders getHeaders() {
long contentLength = headers.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0) {
httpHeaders.setContentLength(contentLength);
}
else {
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
}
@Override
public int getOrder() {
return -2;
}
}
对response的body的修改加密处理
@Slf4j
public class EncryptResponseBodyGatewayFilterFactory extends AbstractGatewayFilterFactory{
public EncryptResponseBodyGatewayFilterFactory() {
}
@Override
public GatewayFilter apply(Object config) {
return new EncryptResponseGatewayFilter();
}
public class EncryptResponseGatewayFilter implements GatewayFilter,Ordered{
@Override
public int getOrder() {
return -2;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange.mutate().response(decorate(exchange)).build());
}
@SuppressWarnings("unchecked")
private ServerHttpResponse decorate(ServerWebExchange exchange) {
return new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
String originalResponseContentType = exchange
.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE,
originalResponseContentType);
ClientResponse clientResponse = ClientResponse
.create(exchange.getResponse().getStatusCode())
.headers(headers -> headers.putAll(httpHeaders))
.body(Flux.from(body)).build();
//修改body
Mono<String> modifiedBody = clientResponse.bodyToMono(String.class)
.flatMap(originalBody -> modifyBody()
.apply(exchange,Mono.just(originalBody)));
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
String.class);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
exchange, exchange.getResponse().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
Flux<DataBuffer> messageBody = outputMessage.getBody();
HttpHeaders headers = getDelegate().getHeaders();
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
messageBody = messageBody.doOnNext(data -> headers
.setContentLength(data.readableByteCount()));
}
return getDelegate().writeWith(messageBody);
}));
}
/**
* 修改body
* @return apply 返回Mono<String>,数据是修改后的body
*/
private BiFunction<ServerWebExchange,Mono<String>,Mono<String>> modifyBody(){
return (exchange,json)-> {
AtomicReference<String> result = new AtomicReference<>();
json.subscribe(
value -> result.set(EncryptUtils.encryptHex(value)),
Throwable::printStackTrace
);
return Mono.just(result.get());
};
}
@Override
public Mono<Void> writeAndFlushWith(
Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
};
}
}
}
@Configuration
public class GatewayConfig {
/**
* 注入自定义授权网关过滤器工厂
*/
@Bean
public AuthGatewayFilterFactory authGatewayFilterFactory() {
return new AuthGatewayFilterFactory();
}
/**
* request body解密
*/
@Bean
public DecryptRequestBodyGatewayFilterFactory decryptRequestBodyGatewayFilterFactory() {
return new DecryptRequestBodyGatewayFilterFactory();
}
/**
* response body加密
*/
@Bean
public EncryptResponseBodyGatewayFilterFactory encryptResponseBodyGatewayFilterFactory() {
return new EncryptResponseBodyGatewayFilterFactory();
}
}