数据加密和完整性校验

This commit is contained in:
jiang 2024-08-06 15:45:35 +08:00
parent c3c2e4be0a
commit a0e999f670
9 changed files with 323 additions and 339 deletions

View File

@ -100,7 +100,7 @@ public class AesCbcUtils {
*/
public static String decrypt(String data) {
try{
String encryptStr="";
String encryptStr="";
if(StringUtils.isNotEmpty(data)){
encryptStr=data.replace(" ","+");
}

View File

@ -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("="))

View File

@ -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);
}
/**

View File

@ -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);

View File

@ -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()) {
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;
} 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(), "请输入正确的请求参数");
}
}
}
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
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 如果解密和完整性校验均未启用则直接通过过滤链
if (!encryptRequest && !integrity) {
return chain.filter(exchange);
}
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 {
return chain.filter(exchange);
}
}
@Override
@ -230,41 +96,176 @@ 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);
/// 注意因为webflux的响应式编程 不能再采取原先的编码方式 即应该先将gatewayContext放入exchange中否则其他地方可能取不到
/**
* save gateway context into exchange
*/
exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT, gatewayContext);
// 处理参数
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);
// 去掉多余的引号如果有
if (requestBody.startsWith("\"") && requestBody.endsWith("\"")) {
requestBody = requestBody.substring(1, requestBody.length() - 1);
}
if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType) || contentType.toString().contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
return readFormData(exchange, chain, gatewayContext);
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());
});
}
/**
* 创建包含新请求体的请求
*
* @param exchange 当前服务器交换
* @param newBody 新的请求体数据缓冲区
* @return 新的ServerHttpRequest对象
*/
private ServerHttpRequest createNewRequest(ServerWebExchange exchange, DataBuffer newBody) {
return new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return Flux.just(newBody);
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
headers.setContentLength(newBody.readableByteCount());
return headers;
}
};
}
/**
* 处理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("请求参数不正确");
}
log.debug("[GatewayContext]ContentType:{},Gateway context is set with {}", contentType, gatewayContext);
return chain.filter(exchange);
}
}

View File

@ -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) {
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());
}
// 检查请求头中是否包含加密标志并且系统是否启用了加密功能
if (shouldEncrypt(headers)) {
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
// 设置响应头
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;
}
}

View File

@ -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);
};

View File

@ -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);
}
}

View File

@ -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