数据加密和完整性校验
This commit is contained in:
parent
c3c2e4be0a
commit
a0e999f670
|
|
@ -37,8 +37,7 @@ import java.util.stream.Collectors;
|
|||
@Component
|
||||
public class AecDecryptParamFilter extends AbstractGatewayFilterFactory {
|
||||
|
||||
@Value("${system.decryptEnabled}")
|
||||
public boolean decryptEnabled;
|
||||
public boolean decryptEnabled = true;
|
||||
|
||||
public static final String HEADER_NAME = "decrypt";
|
||||
public static final String HMAC_HEADER_NAME = "Params-Hash";
|
||||
|
|
@ -140,8 +139,7 @@ public class AecDecryptParamFilter extends AbstractGatewayFilterFactory {
|
|||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
if (StringUtils.isEmpty(decryptedParam)) {
|
||||
log.error("解密后的参数为空");
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
return null;
|
||||
}
|
||||
Map<String, List<String>> queryParams = Arrays.stream(decryptedParam.split("&"))
|
||||
.map(param -> param.split("="))
|
||||
|
|
|
|||
|
|
@ -32,8 +32,6 @@ import java.util.concurrent.TimeUnit;
|
|||
@Component
|
||||
public class AuthFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Value("${system.decryptEnabled}")
|
||||
public boolean jaData;
|
||||
private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
|
||||
|
||||
/**
|
||||
|
|
@ -117,7 +115,7 @@ public class AuthFilter implements GlobalFilter, Ordered {
|
|||
|
||||
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
|
||||
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
|
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED, jaData);
|
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ import com.bonus.common.core.utils.ServletUtils;
|
|||
*/
|
||||
@Component
|
||||
public class BlackListUrlFilter extends AbstractGatewayFilterFactory<BlackListUrlFilter.Config> {
|
||||
@Value("${system.encryptEnabled}")
|
||||
public boolean encryptEnabled;
|
||||
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Config config) {
|
||||
|
|
@ -26,7 +25,7 @@ public class BlackListUrlFilter extends AbstractGatewayFilterFactory<BlackListUr
|
|||
|
||||
String url = exchange.getRequest().getURI().getPath();
|
||||
if (config.matchBlacklist(url)) {
|
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问", encryptEnabled);
|
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问", true);
|
||||
}
|
||||
|
||||
return chain.filter(exchange);
|
||||
|
|
|
|||
|
|
@ -1,42 +1,36 @@
|
|||
package com.bonus.gateway.filter;
|
||||
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.common.core.exception.CaptchaException;
|
||||
import com.bonus.common.core.utils.encryption.AesCbcUtils;
|
||||
import com.bonus.common.core.utils.global.CommonConstant;
|
||||
import com.bonus.common.core.utils.global.SystemGlobal;
|
||||
import com.bonus.gateway.module.GatewayContext;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import com.bonus.common.core.utils.encryption.Sm3Util;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
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.codec.HttpMessageReader;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.reactive.function.server.HandlerStrategies;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 请求内容存储 处理请求内容 内容放在gatewayContext中
|
||||
|
|
@ -49,180 +43,52 @@ import java.util.Map;
|
|||
@Component
|
||||
@Slf4j
|
||||
public class RequestCoverFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Value("${system.decryptEnabled}")
|
||||
public boolean decryptEnabled;
|
||||
public final static String APPLICATION_JSON_UTF8 = "application/json;charset=UTF-8";
|
||||
/**
|
||||
* default HttpMessageReader
|
||||
* 是否启用解密功能
|
||||
*/
|
||||
private static final List<HttpMessageReader<?>> MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders();
|
||||
@Value("${system.encryptRequest}")
|
||||
private boolean encryptRequest;
|
||||
/**
|
||||
* 是否启用数据完整性校验功能
|
||||
*/
|
||||
@Value("${system.integrity}")
|
||||
private boolean integrity;
|
||||
/**
|
||||
* 数据加密标志
|
||||
*/
|
||||
public static final String ENCRYPT = "data_encrypt_request";
|
||||
/**
|
||||
* 数据完整性校验标志
|
||||
*/
|
||||
public static final String INTEGRALITY = "data_integrity";
|
||||
/**
|
||||
* 完整性校验哈希值
|
||||
*/
|
||||
public static final String HMAC_HEADER_NAME = "Params-Hash";
|
||||
|
||||
/**
|
||||
* ReadFormData
|
||||
* 处理Web请求,并(可选地)通过给定的 {@link GatewayFilterChain} 委托给下一个 {@code GatewayFilter}。
|
||||
*
|
||||
* @param exchange
|
||||
* @param chain
|
||||
* @return
|
||||
* @param exchange 当前服务器交换
|
||||
* @param chain 提供委托给下一个过滤器的方式
|
||||
* @return {@code Mono<Void>} 表示请求处理完成的指示
|
||||
*/
|
||||
private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain,
|
||||
GatewayContext gatewayContext) {
|
||||
final ServerHttpRequest request = exchange.getRequest();
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
|
||||
return exchange.getFormData().doOnNext(multiValueMap -> {
|
||||
gatewayContext.setFormData(multiValueMap);
|
||||
log.debug("[GatewayContext]Read FormData:{}", multiValueMap);
|
||||
}).then(Mono.defer(() -> {
|
||||
Charset charset = headers.getContentType().getCharset();
|
||||
charset = charset == null ? StandardCharsets.UTF_8 : charset;
|
||||
String charsetName = charset.name();
|
||||
MultiValueMap<String, String> formData = gatewayContext.getFormData();
|
||||
MultiValueMap<String, String> formData2 = new LinkedMultiValueMap<>();
|
||||
/**
|
||||
* formData is empty just return
|
||||
*/
|
||||
if (null == formData || formData.isEmpty()) {
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
// 如果解密和完整性校验均未启用,则直接通过过滤链
|
||||
if (!encryptRequest && !integrity) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
//是否进行加密
|
||||
if (decryptEnabled) {
|
||||
Object obj = formData.get(SystemGlobal.FORM_DATA);
|
||||
if (!ObjectUtils.isEmpty(obj)) {
|
||||
String data = obj.toString();
|
||||
data = AesCbcUtils.decrypt(data);
|
||||
if (StringUtils.isEmpty(data)) {
|
||||
return CommonConstant.buildResponse(exchange, HttpStatus.BAD_REQUEST.value(), "请输入正确的请求参数");
|
||||
}
|
||||
String[] params = data.split("&");
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
String[] param = params[i].split("=");
|
||||
formData2.add(param[0], param[1]);
|
||||
}
|
||||
formData = formData2;
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
MediaType contentType = request.getHeaders().getContentType();
|
||||
if (contentType == null) {
|
||||
log.info("请求头中无Content-Type信息,直接继续过滤链。");
|
||||
return handleUrlParams(exchange, chain);
|
||||
} else if (contentType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
|
||||
return handleBodyRequest(exchange, chain);
|
||||
} else {
|
||||
//如果是空的 是否去除了加密
|
||||
ServerHttpRequest serverHttpRequest = exchange.getRequest();
|
||||
String head = serverHttpRequest.getHeaders().getFirst(SystemGlobal.KEY_DECRYPT);
|
||||
if (StringUtils.isNotEmpty(head) && !SystemGlobal.KEY_DECRYPT.equals(head)) {
|
||||
return CommonConstant.buildResponse(exchange, HttpStatus.BAD_REQUEST.value(), "请输入正确的请求参数");
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
StringBuilder formDataBodyBuilder = new StringBuilder();
|
||||
String entryKey;
|
||||
List<String> entryValue;
|
||||
try {
|
||||
/**
|
||||
* repackage form data
|
||||
*/
|
||||
for (Map.Entry<String, List<String>> entry : formData.entrySet()) {
|
||||
entryKey = entry.getKey();
|
||||
entryValue = entry.getValue();
|
||||
if (entryValue.size() > 1) {
|
||||
for (String value : entryValue) {
|
||||
formDataBodyBuilder.append(entryKey).append("=")
|
||||
.append(URLEncoder.encode(value, charsetName)).append("&");
|
||||
}
|
||||
} else {
|
||||
formDataBodyBuilder.append(entryKey).append("=")
|
||||
.append(URLEncoder.encode(entryValue.get(0), charsetName)).append("&");
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// ignore URLEncode Exception
|
||||
}
|
||||
/**
|
||||
* substring with the last char '&'
|
||||
*/
|
||||
String formDataBodyString = "";
|
||||
if (formDataBodyBuilder.length() > 0) {
|
||||
formDataBodyString = formDataBodyBuilder.substring(0, formDataBodyBuilder.length() - 1);
|
||||
}
|
||||
/**
|
||||
* get data bytes
|
||||
*/
|
||||
byte[] bodyBytes = formDataBodyString.getBytes(charset);
|
||||
int contentLength = bodyBytes.length;
|
||||
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(request) {
|
||||
/**
|
||||
* change content-length
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.putAll(super.getHeaders());
|
||||
if (contentLength > 0) {
|
||||
httpHeaders.setContentLength(contentLength);
|
||||
} else {
|
||||
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
|
||||
}
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* read bytes to Flux<Databuffer>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Flux<DataBuffer> getBody() {
|
||||
return DataBufferUtils.read(new ByteArrayResource(bodyBytes),
|
||||
new NettyDataBufferFactory(ByteBufAllocator.DEFAULT), contentLength);
|
||||
}
|
||||
};
|
||||
ServerWebExchange mutateExchange = exchange.mutate().request(decorator).build();
|
||||
log.info("[GatewayContext]Rewrite Form Data :{}", formDataBodyString);
|
||||
|
||||
return chain.filter(mutateExchange);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* ReadJsonBody
|
||||
*
|
||||
* @param exchange 操作的http请求数据
|
||||
* @param chain 网关过滤器链表
|
||||
* @return
|
||||
*/
|
||||
private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {
|
||||
/**
|
||||
* join the body
|
||||
*/
|
||||
return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
|
||||
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(bytes);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
|
||||
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
|
||||
DataBufferUtils.retain(buffer);
|
||||
return Mono.just(buffer);
|
||||
});
|
||||
/**
|
||||
* repackage ServerHttpRequest
|
||||
*/
|
||||
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
|
||||
@Override
|
||||
public Flux<DataBuffer> getBody() {
|
||||
return cachedFlux;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* mutate exchage with new ServerHttpRequest
|
||||
*/
|
||||
ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
|
||||
/**
|
||||
* read body string with default messageReaders
|
||||
*/
|
||||
return ServerRequest.create(mutatedExchange, MESSAGE_READERS).bodyToMono(String.class)
|
||||
.doOnNext(objectValue -> {
|
||||
gatewayContext.setCacheBody(objectValue);
|
||||
log.debug("[GatewayContext]Read JsonBody:{}", objectValue);
|
||||
}).then(chain.filter(mutatedExchange));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -230,42 +96,177 @@ public class RequestCoverFilter implements GlobalFilter, Ordered {
|
|||
return HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
/**
|
||||
* save request path and serviceId into gateway context
|
||||
* 处理请求体里的参数
|
||||
*
|
||||
* @param exchange 当前服务器交换
|
||||
* @param chain 提供委托给下一个过滤器的方式
|
||||
* @return {@code Mono<Void>} 表示请求处理完成的指示
|
||||
*/
|
||||
private Mono<Void> handleBodyRequest(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
boolean integrality = "true".equalsIgnoreCase(request.getHeaders().getFirst(INTEGRALITY));
|
||||
boolean encrypt = "true".equalsIgnoreCase(request.getHeaders().getFirst(ENCRYPT));
|
||||
|
||||
GatewayContext gatewayContext = new GatewayContext();
|
||||
String path = request.getPath().pathWithinApplication().value();
|
||||
gatewayContext.setPath(path);
|
||||
gatewayContext.getFormData().addAll(request.getQueryParams());
|
||||
gatewayContext.setIpAddress(String.valueOf(request.getRemoteAddress()));
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
gatewayContext.setHeaders(headers);
|
||||
log.debug("HttpMethod:{},Url:{}", request.getMethod(), request.getURI().getRawPath());
|
||||
return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
|
||||
byte[] body = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(body);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
String requestBody = new String(body, StandardCharsets.UTF_8);
|
||||
|
||||
// 去掉多余的引号(如果有)
|
||||
if (requestBody.startsWith("\"") && requestBody.endsWith("\"")) {
|
||||
requestBody = requestBody.substring(1, requestBody.length() - 1);
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(requestBody)) {
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
// 解密请求体
|
||||
if (encryptRequest && encrypt) {
|
||||
try {
|
||||
requestBody = AesCbcUtils.decrypt(requestBody);
|
||||
} catch (Exception e) {
|
||||
log.error("解密请求体时发生错误: {}", e.getMessage(), e);
|
||||
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
}
|
||||
|
||||
// 校验数据完整性
|
||||
if (integrity && integrality) {
|
||||
String providedHmac = exchange.getRequest().getHeaders().getFirst(HMAC_HEADER_NAME);
|
||||
integrityVerification(providedHmac, requestBody);
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(requestBody)) {
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
|
||||
// 创建新的请求体
|
||||
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
|
||||
DataBuffer newBody = bufferFactory.wrap(requestBody.getBytes(StandardCharsets.UTF_8));
|
||||
ServerHttpRequest newRequest = createNewRequest(exchange, newBody);
|
||||
|
||||
return chain.filter(exchange.mutate().request(newRequest).build());
|
||||
});
|
||||
}
|
||||
|
||||
/// 注意,因为webflux的响应式编程 不能再采取原先的编码方式 即应该先将gatewayContext放入exchange中,否则其他地方可能取不到
|
||||
/**
|
||||
* save gateway context into exchange
|
||||
* 创建包含新请求体的请求
|
||||
*
|
||||
* @param exchange 当前服务器交换
|
||||
* @param newBody 新的请求体数据缓冲区
|
||||
* @return 新的ServerHttpRequest对象
|
||||
*/
|
||||
exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT, gatewayContext);
|
||||
private ServerHttpRequest createNewRequest(ServerWebExchange exchange, DataBuffer newBody) {
|
||||
return new ServerHttpRequestDecorator(exchange.getRequest()) {
|
||||
@Override
|
||||
public Flux<DataBuffer> getBody() {
|
||||
return Flux.just(newBody);
|
||||
}
|
||||
|
||||
// 处理参数
|
||||
MediaType contentType = headers.getContentType();
|
||||
long contentLength = headers.getContentLength();
|
||||
if (contentLength > 0) {
|
||||
if (MediaType.APPLICATION_JSON.equals(contentType) || APPLICATION_JSON_UTF8.equals(contentType)) {
|
||||
return readBody(exchange, chain, gatewayContext);
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.putAll(exchange.getRequest().getHeaders());
|
||||
headers.remove(HttpHeaders.CONTENT_LENGTH);
|
||||
headers.setContentLength(newBody.readableByteCount());
|
||||
return headers;
|
||||
}
|
||||
if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType) || contentType.toString().contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
|
||||
return readFormData(exchange, chain, gatewayContext);
|
||||
};
|
||||
}
|
||||
}
|
||||
log.debug("[GatewayContext]ContentType:{},Gateway context is set with {}", contentType, gatewayContext);
|
||||
|
||||
/**
|
||||
* 处理url后拼接的参数
|
||||
*
|
||||
* @param exchange 当前服务器交换
|
||||
* @param chain 提供委托给下一个过滤器的方式
|
||||
* @return {@code Mono<Void>} 表示请求处理完成的指示
|
||||
*/
|
||||
private Mono<Void> handleUrlParams(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
try {
|
||||
ServerWebExchange updatedExchange = updateRequestParam(exchange);
|
||||
if (updatedExchange != null) {
|
||||
return chain.filter(updatedExchange);
|
||||
} else {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理 GET 请求时发生错误: {}", e.getMessage(), e);
|
||||
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新查询参数,解密和验证数据完整性
|
||||
*
|
||||
* @param exchange 当前服务器交换
|
||||
* @return 更新后的ServerWebExchange对象,如果无需更新则返回null
|
||||
*/
|
||||
private ServerWebExchange updateRequestParam(ServerWebExchange exchange) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
boolean integrality = "true".equalsIgnoreCase(request.getHeaders().getFirst(INTEGRALITY));
|
||||
boolean encrypt = "true".equalsIgnoreCase(request.getHeaders().getFirst(ENCRYPT));
|
||||
URI uri = request.getURI();
|
||||
String query = uri.getQuery();
|
||||
|
||||
if (!ObjectUtils.isEmpty(query)) {
|
||||
// 解密查询参数
|
||||
if (encryptRequest && encrypt) {
|
||||
try {
|
||||
query = AesCbcUtils.decrypt(query);
|
||||
} catch (Exception e) {
|
||||
log.error("解密查询参数时发生错误: {}", e.getMessage(), e);
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
}
|
||||
|
||||
// 校验数据完整性
|
||||
if (integrity && integrality) {
|
||||
String providedHmac = exchange.getRequest().getHeaders().getFirst(HMAC_HEADER_NAME);
|
||||
integrityVerification(providedHmac, query);
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(query)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 更新查询参数
|
||||
Map<String, List<String>> queryParams = Arrays.stream(query.split("&"))
|
||||
.map(param -> param.split("="))
|
||||
.collect(Collectors.toMap(param -> param[0], param -> Collections.singletonList(param[1])));
|
||||
URI newUri = UriComponentsBuilder.fromUri(uri)
|
||||
.replaceQueryParams(CollectionUtils.toMultiValueMap(queryParams))
|
||||
.build(true)
|
||||
.toUri();
|
||||
|
||||
ServerHttpRequest newRequest = request.mutate().uri(newUri).build();
|
||||
return exchange.mutate().request(newRequest).build();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据完整性校验
|
||||
*
|
||||
* @param providedHmac 请求头中的 HMAC 值
|
||||
* @param query 请求参数
|
||||
*/
|
||||
private void integrityVerification(String providedHmac, String query) {
|
||||
if (providedHmac == null) {
|
||||
log.error("请求头中缺少 Params-Hash");
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
String encrypt = Sm3Util.encrypt(query);
|
||||
log.debug("加密后的参数: {}", encrypt);
|
||||
log.debug("请求头中的 Params-Hash: {}", providedHmac);
|
||||
if (!encrypt.equals(providedHmac)) {
|
||||
log.error("参数校验失败");
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
package com.bonus.gateway.filter;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.nacos.shaded.com.google.common.collect.Maps;
|
||||
import com.bonus.common.core.utils.encryption.AesCbcUtils;
|
||||
import com.bonus.common.core.utils.global.SystemGlobal;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.compress.utils.Charsets;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
|
|
@ -26,11 +22,7 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 对返回的 data数据进行加密
|
||||
|
|
@ -41,105 +33,89 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
|
|||
@Slf4j
|
||||
public class ResponseEncryptFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Value("${system.encryptEnabled}")
|
||||
public boolean encryptEnabled;
|
||||
/**
|
||||
* 返回的数据 是否加密
|
||||
*/
|
||||
public final static String KEY_HEAD = "decrypt";
|
||||
@Value("${system.encryptResponse}")
|
||||
private boolean encryptResponse;
|
||||
|
||||
private static final String ENCRYPT_RESPONSE = "data_encrypt_response";
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
log.info("============================ResponseEncryptFilter start===================================");
|
||||
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
URI uri = request.getURI();
|
||||
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
if (headers != null) {
|
||||
Object object = headers.getFirst("Content-Type");
|
||||
Object head = headers.getFirst(SystemGlobal.KEY_ENCRYPT);
|
||||
if (head != null) {
|
||||
String keyHead = head.toString();
|
||||
if (SystemGlobal.KEY_ENCRYPT.equals(keyHead)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
}
|
||||
if (object != null) {
|
||||
String contentType = object.toString();
|
||||
if (contentType.contains(MULTIPART_FORM_DATA_VALUE)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
}
|
||||
}
|
||||
HttpStatus statusCode = exchange.getResponse().getStatusCode();
|
||||
if (Objects.equals(statusCode, HttpStatus.BAD_REQUEST) || Objects.equals(statusCode, HttpStatus.TOO_MANY_REQUESTS)) {
|
||||
// 如果是特殊的请求,已处理响应内容,这里不再处理
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
//是否加密
|
||||
if (!encryptEnabled) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
// 根据具体业务内容,修改响应体
|
||||
return modifyResponseBody(exchange, chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改响应体
|
||||
*
|
||||
* @param exchange
|
||||
* @param chain
|
||||
* @return
|
||||
*/
|
||||
private Mono<Void> modifyResponseBody(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
// 检查请求头中是否包含加密标志,并且系统是否启用了加密功能
|
||||
if (shouldEncrypt(headers)) {
|
||||
ServerHttpResponse originalResponse = exchange.getResponse();
|
||||
originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
|
||||
ServerHttpResponseDecorator response = buildResponse(originalResponse, bufferFactory);
|
||||
return chain.filter(exchange.mutate().response(response).build());
|
||||
// 设置响应头
|
||||
addResponseHeaders(originalResponse, headers);
|
||||
// 创建自定义的响应装饰器,用于处理响应数据
|
||||
ServerHttpResponseDecorator responseDecorator = buildResponse(originalResponse, bufferFactory);
|
||||
// 使用装饰器包装后的响应继续处理链
|
||||
return chain.filter(exchange.mutate().response(responseDecorator).build());
|
||||
}
|
||||
|
||||
// 如果不需要加密,直接继续处理链
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -5;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
/**
|
||||
* 判断是否需要对响应数据进行加密
|
||||
*
|
||||
* @param headers 请求头
|
||||
* @return 如果需要加密返回true,否则返回false
|
||||
*/
|
||||
private boolean shouldEncrypt(HttpHeaders headers) {
|
||||
return encryptResponse && Boolean.parseBoolean(headers.getFirst(ENCRYPT_RESPONSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义的响应头
|
||||
*
|
||||
* @param response 原始响应对象
|
||||
* @param requestHeaders 请求头
|
||||
*/
|
||||
private void addResponseHeaders(ServerHttpResponse response, HttpHeaders requestHeaders) {
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
response.getHeaders().add(ENCRYPT_RESPONSE, requestHeaders.getFirst(ENCRYPT_RESPONSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建自定义的响应装饰器,用于加密响应数据
|
||||
*
|
||||
* @param originalResponse 原始响应对象
|
||||
* @param bufferFactory 数据缓冲区工厂
|
||||
* @return 自定义的响应装饰器
|
||||
*/
|
||||
private ServerHttpResponseDecorator buildResponse(ServerHttpResponse originalResponse, DataBufferFactory bufferFactory) {
|
||||
return new ServerHttpResponseDecorator(originalResponse) {
|
||||
@Override
|
||||
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
||||
// 只处理状态码为200 OK的响应,并且响应体是Flux类型
|
||||
if (getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) {
|
||||
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
|
||||
|
||||
// 对响应体进行加密处理
|
||||
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
|
||||
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
|
||||
DataBuffer join = dataBufferFactory.join(dataBuffers);
|
||||
byte[] content = new byte[join.readableByteCount()];
|
||||
join.read(content);
|
||||
DataBufferUtils.release(join);
|
||||
// 流转为字符串
|
||||
String responseData = new String(content, Charsets.UTF_8);
|
||||
System.out.println(responseData);
|
||||
Map map = JSON.parseObject(responseData);
|
||||
Object encrypt = map.get(SystemGlobal.KEY_DECRYPT);
|
||||
Map maps = Maps.newHashMap();
|
||||
if (encrypt == null || encrypt == "" || SystemGlobal.TRUE_STR.equals(encrypt)) {
|
||||
responseData = AesCbcUtils.encrypt(JSON.toJSONString(map));
|
||||
maps.put("data", responseData);
|
||||
maps.put(SystemGlobal.KEY_DECRYPT, true);
|
||||
responseData = JSON.toJSONString(maps);
|
||||
} else {
|
||||
maps.put("data", responseData);
|
||||
maps.put(SystemGlobal.KEY_DECRYPT, false);
|
||||
}
|
||||
byte[] uppedContent = responseData.getBytes(Charsets.UTF_8);
|
||||
originalResponse.getHeaders().setContentLength(uppedContent.length);
|
||||
return bufferFactory.wrap(uppedContent);
|
||||
DataBuffer joinedBuffer = joinDataBuffers(dataBuffers);
|
||||
byte[] content = readContent(joinedBuffer);
|
||||
DataBufferUtils.release(joinedBuffer);
|
||||
|
||||
// 将响应数据加密
|
||||
String responseData = new String(content, StandardCharsets.UTF_8);
|
||||
responseData = AesCbcUtils.encrypt(responseData);
|
||||
byte[] encryptedContent = responseData.getBytes(StandardCharsets.UTF_8);
|
||||
// 设置加密后的内容长度
|
||||
originalResponse.getHeaders().setContentLength(encryptedContent.length);
|
||||
return bufferFactory.wrap(encryptedContent);
|
||||
}));
|
||||
} else {
|
||||
log.error("获取响应体数据 :" + getStatusCode());
|
||||
log.error("Failed to retrieve response body. Status code: {}", getStatusCode());
|
||||
}
|
||||
return super.writeWith(body);
|
||||
}
|
||||
|
|
@ -150,5 +126,31 @@ public class ResponseEncryptFilter implements GlobalFilter, Ordered {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多个DataBuffer连接成一个DataBuffer
|
||||
*
|
||||
* @param dataBuffers DataBuffer列表
|
||||
* @return 连接后的DataBuffer
|
||||
*/
|
||||
private DataBuffer joinDataBuffers(java.util.List<? extends DataBuffer> dataBuffers) {
|
||||
if (dataBuffers.size() > 1) {
|
||||
return new DefaultDataBufferFactory().join(dataBuffers);
|
||||
} else {
|
||||
return dataBuffers.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从DataBuffer中读取字节数组内容
|
||||
*
|
||||
* @param dataBuffer DataBuffer对象
|
||||
* @return 读取到的字节数组
|
||||
*/
|
||||
private byte[] readContent(DataBuffer dataBuffer) {
|
||||
byte[] content = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(content);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,25 @@
|
|||
package com.bonus.gateway.filter;
|
||||
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.common.core.exception.CaptchaException;
|
||||
import com.bonus.common.core.utils.encryption.AesCbcUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.bonus.common.core.exception.CaptchaException;
|
||||
import com.bonus.common.core.utils.ServletUtils;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.gateway.config.properties.CaptchaProperties;
|
||||
import com.bonus.gateway.service.ValidateCodeService;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* 验证码过滤器
|
||||
|
|
@ -35,8 +30,6 @@ import reactor.core.publisher.Mono;
|
|||
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> {
|
||||
private final static String[] VALIDATE_URL = new String[]{"/auth/login", "/auth/register", "/auth/getPhoneCode"};
|
||||
|
||||
@Value("${system.decryptEnabled}")
|
||||
public boolean jaData;
|
||||
|
||||
@Autowired
|
||||
private ValidateCodeService validateCodeService;
|
||||
|
|
@ -59,9 +52,6 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> {
|
|||
}
|
||||
try {
|
||||
String rspStr = resolveBodyFromRequest(request);
|
||||
if (jaData) {
|
||||
rspStr = AesCbcUtils.decrypt(rspStr);
|
||||
}
|
||||
if (StringUtils.isEmpty(rspStr)) {
|
||||
throw new CaptchaException("请求参数异常");
|
||||
}
|
||||
|
|
@ -73,7 +63,7 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> {
|
|||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage(), jaData);
|
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage(), true);
|
||||
}
|
||||
return chain.filter(exchange);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ import reactor.core.publisher.Mono;
|
|||
@Configuration
|
||||
public class GatewayExceptionHandler implements ErrorWebExceptionHandler
|
||||
{
|
||||
@Value("${system.encryptEnabled}")
|
||||
public boolean encryptEnabled;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class);
|
||||
|
||||
|
|
@ -55,6 +53,6 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler
|
|||
|
||||
log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
|
||||
|
||||
return ServletUtils.webFluxResponseWriter(response, msg, encryptEnabled);
|
||||
return ServletUtils.webFluxResponseWriter(response, msg, true);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,12 +16,10 @@ import reactor.core.publisher.Mono;
|
|||
*/
|
||||
public class SentinelFallbackHandler implements WebExceptionHandler
|
||||
{
|
||||
@Value("${system.encryptEnabled}")
|
||||
public boolean encryptEnabled;
|
||||
|
||||
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
|
||||
{
|
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试",encryptEnabled);
|
||||
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试",true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Reference in New Issue