数据加密和完整性校验

This commit is contained in:
jiang 2024-08-07 11:27:01 +08:00
parent a7d6d78297
commit ada6ccb7a7
7 changed files with 91 additions and 38 deletions

View File

@ -1,15 +1,5 @@
package com.bonus.gateway.filter; package com.bonus.gateway.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import com.bonus.common.core.constant.CacheConstants; import com.bonus.common.core.constant.CacheConstants;
import com.bonus.common.core.constant.HttpStatus; import com.bonus.common.core.constant.HttpStatus;
import com.bonus.common.core.constant.SecurityConstants; import com.bonus.common.core.constant.SecurityConstants;
@ -20,10 +10,17 @@ import com.bonus.common.core.utils.StringUtils;
import com.bonus.common.redis.service.RedisService; import com.bonus.common.redis.service.RedisService;
import com.bonus.gateway.config.properties.IgnoreWhiteProperties; import com.bonus.gateway.config.properties.IgnoreWhiteProperties;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.util.concurrent.TimeUnit;
/** /**
* 网关鉴权 * 网关鉴权
* *
@ -115,7 +112,7 @@ public class AuthFilter implements GlobalFilter, Ordered {
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) { private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED, true); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
} }
/** /**

View File

@ -25,7 +25,7 @@ public class BlackListUrlFilter extends AbstractGatewayFilterFactory<BlackListUr
String url = exchange.getRequest().getURI().getPath(); String url = exchange.getRequest().getURI().getPath();
if (config.matchBlacklist(url)) { if (config.matchBlacklist(url)) {
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问", true); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问");
} }
return chain.filter(exchange); return chain.filter(exchange);

View File

@ -44,23 +44,51 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class RequestCoverFilter implements GlobalFilter, Ordered { public class RequestCoverFilter implements GlobalFilter, Ordered {
/** /**
* 是否启用解密功能 * 密钥算法
*/ */
@Value("${aesCbc.keyAlgorithm}")
public String keyAlgorithm;
/**
* 加密/解密算法 / 工作模式 / 填充方式
* Java 6支持PKCS5Padding填充方式
* Bouncy Castle支持PKCS7Padding填充方式
*/
@Value("${aesCbc.cipherAlgorithm}")
public String cipherAlgorithm;
/**
* 偏移量只有CBC模式才需要
*/
@Value("${aesCbc.ivParameter}")
public String ivParameter;
/**
* AES要求密钥长度为128位或192位或256位java默认限制AES密钥长度最多128位
*/
@Value("${aesCbc.sKey}")
public String sKey;
/**
* 编码格式导出
*/
@Value("${aesCbc.encoding}")
public String encoding;
/* *//**
* 是否启用解密功能
*//*
@Value("${system.encryptRequest}") @Value("${system.encryptRequest}")
private boolean encryptRequest; private boolean encryptRequest;
/** *//**
* 是否启用数据完整性校验功能 * 是否启用数据完整性校验功能
*/ *//*
@Value("${system.integrity}") @Value("${system.integrity}")
private boolean integrity; private boolean integrity;*/
/** /**
* 数据加密标志 * 数据加密标志
*/ */
public static final String ENCRYPT = "data_encrypt_request"; public static final String ENCRYPT = "encryptRequest";
/** /**
* 数据完整性校验标志 * 数据完整性校验标志
*/ */
public static final String INTEGRALITY = "data_integrity"; public static final String INTEGRALITY = "checkIntegrity";
/** /**
* 完整性校验哈希值 * 完整性校验哈希值
*/ */
@ -76,9 +104,6 @@ public class RequestCoverFilter implements GlobalFilter, Ordered {
@Override @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 如果解密和完整性校验均未启用则直接通过过滤链 // 如果解密和完整性校验均未启用则直接通过过滤链
if (!encryptRequest && !integrity) {
return chain.filter(exchange);
}
ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest request = exchange.getRequest();
MediaType contentType = request.getHeaders().getContentType(); MediaType contentType = request.getHeaders().getContentType();
if (contentType == null) { if (contentType == null) {
@ -123,9 +148,9 @@ public class RequestCoverFilter implements GlobalFilter, Ordered {
return exchange.getResponse().setComplete(); return exchange.getResponse().setComplete();
} }
// 解密请求体 // 解密请求体
if (encryptRequest && encrypt) { if ( encrypt) {
try { try {
requestBody = AesCbcUtils.decrypt(requestBody); requestBody = AesCbcUtils.decrypt(requestBody, sKey, encoding, ivParameter, cipherAlgorithm, keyAlgorithm);
} catch (Exception e) { } catch (Exception e) {
log.error("解密请求体时发生错误: {}", e.getMessage(), e); log.error("解密请求体时发生错误: {}", e.getMessage(), e);
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
@ -134,7 +159,7 @@ public class RequestCoverFilter implements GlobalFilter, Ordered {
} }
// 校验数据完整性 // 校验数据完整性
if (integrity && integrality) { if (integrality) {
String providedHmac = exchange.getRequest().getHeaders().getFirst(HMAC_HEADER_NAME); String providedHmac = exchange.getRequest().getHeaders().getFirst(HMAC_HEADER_NAME);
integrityVerification(providedHmac, requestBody); integrityVerification(providedHmac, requestBody);
} }
@ -214,9 +239,9 @@ public class RequestCoverFilter implements GlobalFilter, Ordered {
if (!ObjectUtils.isEmpty(query)) { if (!ObjectUtils.isEmpty(query)) {
// 解密查询参数 // 解密查询参数
if (encryptRequest && encrypt) { if ( encrypt) {
try { try {
query = AesCbcUtils.decrypt(query); query = AesCbcUtils.decrypt(query, sKey, encoding, ivParameter, cipherAlgorithm, keyAlgorithm);
} catch (Exception e) { } catch (Exception e) {
log.error("解密查询参数时发生错误: {}", e.getMessage(), e); log.error("解密查询参数时发生错误: {}", e.getMessage(), e);
throw new CaptchaException("请求参数不正确"); throw new CaptchaException("请求参数不正确");
@ -224,7 +249,7 @@ public class RequestCoverFilter implements GlobalFilter, Ordered {
} }
// 校验数据完整性 // 校验数据完整性
if (integrity && integrality) { if (integrality) {
String providedHmac = exchange.getRequest().getHeaders().getFirst(HMAC_HEADER_NAME); String providedHmac = exchange.getRequest().getHeaders().getFirst(HMAC_HEADER_NAME);
integrityVerification(providedHmac, query); integrityVerification(providedHmac, query);
} }

View File

@ -32,11 +32,43 @@ import java.nio.charset.StandardCharsets;
@Configuration @Configuration
@Slf4j @Slf4j
public class ResponseEncryptFilter implements GlobalFilter, Ordered { public class ResponseEncryptFilter implements GlobalFilter, Ordered {
/**
* 密钥算法
*/
@Value("${aesCbc.keyAlgorithm}")
public String keyAlgorithm;
/**
* 加密/解密算法 / 工作模式 / 填充方式
* Java 6支持PKCS5Padding填充方式
* Bouncy Castle支持PKCS7Padding填充方式
*/
@Value("${aesCbc.cipherAlgorithm}")
public String cipherAlgorithm;
/**
* 偏移量只有CBC模式才需要
*/
@Value("${aesCbc.ivParameter}")
public String ivParameter;
/**
* AES要求密钥长度为128位或192位或256位java默认限制AES密钥长度最多128位
*/
@Value("${aesCbc.sKey}")
public String sKey;
/**
* 编码格式导出
*/
@Value("${aesCbc.encoding}")
public String encoding;
/* *//**
* 返回数据是否加密
*//*
@Value("${system.encryptResponse}") @Value("${system.encryptResponse}")
private boolean encryptResponse; private boolean encryptResponse;*/
/**
private static final String ENCRYPT_RESPONSE = "data_encrypt_response"; * 加密标识
*/
private static final String ENCRYPT_RESPONSE = "encryptResponse";
@Override @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
@ -71,7 +103,7 @@ public class ResponseEncryptFilter implements GlobalFilter, Ordered {
* @return 如果需要加密返回true否则返回false * @return 如果需要加密返回true否则返回false
*/ */
private boolean shouldEncrypt(HttpHeaders headers) { private boolean shouldEncrypt(HttpHeaders headers) {
return encryptResponse && Boolean.parseBoolean(headers.getFirst(ENCRYPT_RESPONSE)); return Boolean.parseBoolean(headers.getFirst(ENCRYPT_RESPONSE));
} }
/** /**
@ -108,7 +140,7 @@ public class ResponseEncryptFilter implements GlobalFilter, Ordered {
// 将响应数据加密 // 将响应数据加密
String responseData = new String(content, StandardCharsets.UTF_8); String responseData = new String(content, StandardCharsets.UTF_8);
responseData = AesCbcUtils.encrypt(responseData); responseData = AesCbcUtils.encrypt(responseData, sKey, encoding, ivParameter, cipherAlgorithm, keyAlgorithm);
byte[] encryptedContent = responseData.getBytes(StandardCharsets.UTF_8); byte[] encryptedContent = responseData.getBytes(StandardCharsets.UTF_8);
// 设置加密后的内容长度 // 设置加密后的内容长度
originalResponse.getHeaders().setContentLength(encryptedContent.length); originalResponse.getHeaders().setContentLength(encryptedContent.length);

View File

@ -63,7 +63,7 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> {
} }
} catch (Exception e) { } catch (Exception e) {
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage(), true); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
} }
return chain.filter(exchange); return chain.filter(exchange);
}; };

View File

@ -53,6 +53,6 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler
log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage()); log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
return ServletUtils.webFluxResponseWriter(response, msg, true); return ServletUtils.webFluxResponseWriter(response, msg);
} }
} }

View File

@ -3,7 +3,6 @@ package com.bonus.gateway.handler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.bonus.common.core.utils.ServletUtils; import com.bonus.common.core.utils.ServletUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler; import org.springframework.web.server.WebExceptionHandler;
@ -19,7 +18,7 @@ public class SentinelFallbackHandler implements WebExceptionHandler
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
{ {
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试",true); return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试");
} }
@Override @Override