网关加密/解密拦截器
使用aes加密,加密工具使用Hutool
- 解密接口的url参数
- 解密接口的body参数
- 加密接口返回数据
1.yml配置
#接口加密
config:
crypto:
#是否启动加密
enabled: true
#加密信息字段
cryptoParam: cryptoParam
#AES加密key
appkey: 123456
logging:
level:
#日志级别 debug:输出加密过程 info:不输出
com.ty.filter.CryptoFilter: info
2.配置类 工具类
@Data
@Configuration
@ConfigurationProperties(prefix = "config.crypto")
public class CryptoConfigProperties {
/**
* 加密字段,用哪个字段显示加密的数据
*/
private String cryptoParam;
/**
* AES加密key
*/
private String appkey;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CryptoVo {
private String body;
/**
* 前端是否加密,加密则响应体也加密 1:url加密 2:body加密
*/
private Integer encryptFlag;
private MediaType contentType;
}
@Slf4j
public class CryptoUtil {
private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();
private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder();
public static final int REQ_URL = 0;
public static final int REQ_BODY = 1;
public static final int RES_DATA = 2;
/**
* 解密
* 6ase64 + aes
*
* @param data
* @param key
* @return
*/
public static String decrypt(String data, String key) throws Exception {
if (StringUtils.isEmpty(data) || StringUtils.isEmpty(key)) {
return data;
}
String data64 = new String(BASE64_DECODER.decode(data), StandardCharsets.UTF_8);
String result = AESForJsUtil.decryptAES(data64, key);
if (StringUtils.isEmpty(result)) {
throw new RuntimeException("解密失败," + data);
}
return result;
}
/**
* 加密
* aes + 6ase64
*
* @param data
* @param key
* @return
*/
public static String encrypt(String data, String key) throws Exception {
if (StringUtils.isEmpty(data) || StringUtils.isEmpty(key)) {
return data;
}
String result = AESForJsUtil.encryptAES(data, key);
result = new String(BASE64_ENCODER.encode(result.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
if (StringUtils.isEmpty(result)) {
throw new RuntimeException("加密失败," + data);
}
return result;
}
public static void main(String[] args) throws Exception {
}
3.拦截器
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
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.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.RequestPath;
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.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.net.URI;
import java.nio.charset.StandardCharsets;
/**
* 加密接口-解密请求拦截器
* 注意:get请求,body不能传值
* 1.解密接口的url参数
* 2.解密接口的body参数
* 3.加密接口返回数据
*
* @date 2023-12-28
*/
@Slf4j
@Configuration
@ConditionalOnProperty(value = "config.crypto.enabled", havingValue = "true", matchIfMissing = false)
public class CryptoFilter implements GlobalFilter, Ordered {
/**
* 加密配置属性
*/
@Resource
private CryptoConfigProperties cryptoConfigProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = request.getHeaders();
CryptoVo cryptoVo = new CryptoVo();
cryptoVo.setEncryptFlag(0);
//对url的参数进行解密
updateUrlParam(exchange, cryptoVo);
//不存在body则直接返回
if (HttpMethod.GET.equals(method) || !MediaType.APPLICATION_JSON.isCompatibleWith(headers.getContentType())) {
if (cryptoVo.getEncryptFlag() > 0) {
//响应体加密
exchange = exchange.mutate().response(serverHttpResponseDecorator(response, cryptoVo)).build();
}
return chain.filter(exchange);
}
//application/json对body的参数解密
Flux<DataBuffer> body = request.getBody();
Mono<DataBuffer> dataBufferMono = DataBufferUtils.join(body);
ServerWebExchange finalExchange = exchange;
return dataBufferMono.flatMap(dataBuffer -> {
try {
// 获取请求参数
String originalRequestBody = getOriginalRequestBody(dataBuffer);
cryptoVo.setBody(originalRequestBody);
// 解密请求参数
String decryptRequestBody = decryptRequest(request, cryptoVo);
// 重写请求和响应方式
return getEnResponseBody(finalExchange, chain, request, decryptRequestBody, response, cryptoVo);
} catch (Exception e) {
log.error("密文过滤器加解密错误", e);
return Mono.error(e);
} finally {
DataBufferUtils.release(dataBuffer);
}
});
}
/**
* 返回加密响应体
*
* @param exchange
* @param chain
* @param request
* @param decryptRequestBody
* @param response
* @param cryptoVo
* @return
*/
private Mono<Void> getEnResponseBody(ServerWebExchange exchange, GatewayFilterChain chain, ServerHttpRequest request, String decryptRequestBody, ServerHttpResponse response, CryptoVo cryptoVo) {
// 新的请求体
ServerHttpRequestDecorator requestDecorator = serverHttpRequestDecorator(request, decryptRequestBody);
//加密响应体
ServerHttpResponseDecorator serverHttpResponseDecorator = serverHttpResponseDecorator(response, cryptoVo);
ServerWebExchange serverWebExchange = exchange.mutate().request(requestDecorator).response(serverHttpResponseDecorator).build();
// 使用新的请求和响应转发
return chain.filter(serverWebExchange);
}
/**
* body解密
*
* @param request
* @param cryptoVo
* @return
*/
private String decryptRequest(ServerHttpRequest request, CryptoVo cryptoVo) {
String body = cryptoVo.getBody();
if (!JSON.isValid(body)) {
return body;
}
JSONObject jsonObject = JSON.parseObject(body);
String cryptoParam = jsonObject.getString(cryptoConfigProperties.getCryptoParam());
if (!StringUtils.isEmpty(cryptoParam)) {
cryptoVo.setEncryptFlag(2);
try {
body = CryptoUtil.decrypt(cryptoParam, cryptoConfigProperties.getAppkey());
cryptorDetail(request.getPath(), cryptoParam, body, CryptoUtil.REQ_BODY);
} catch (Exception e) {
log.error("【body解密失败】路径:{},参数:{}", request.getPath(), body, e);
throw new RuntimeException(e.getMessage(), e);
}
}
return body;
}
/**
* 新建请求体
*
* @param originalRequest
* @param decryptRequestBody
* @return
*/
private ServerHttpRequestDecorator serverHttpRequestDecorator(ServerHttpRequest originalRequest, String decryptRequestBody) {
return new ServerHttpRequestDecorator(originalRequest) {
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
if (decryptRequestBody == null) {
return super.getBody();
}
byte[] bytes = decryptRequestBody.getBytes(StandardCharsets.UTF_8);
return Flux.just(new DefaultDataBufferFactory().wrap(bytes));
}
};
}
/**
* 新建响应体
*
* @param originalResponse
* @param cryptoVo
* @return
*/
private ServerHttpResponseDecorator serverHttpResponseDecorator(ServerHttpResponse originalResponse, CryptoVo cryptoVo) {
DataBufferFactory dataBufferFactory = originalResponse.bufferFactory();
return new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
//encryptFlag 前端加密,响应体也加密
if (body instanceof Flux && cryptoVo.getEncryptFlag() > 0) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.buffer().handle((dataBuffers, sink) -> {
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] byteArray = new byte[join.readableByteCount()];
join.read(byteArray);
DataBufferUtils.release(join);
String originalResponseBody = new String(byteArray, StandardCharsets.UTF_8);
byte[] encryptedByteArray;
try {
String encryptResult = CryptoUtil.encrypt(originalResponseBody, cryptoConfigProperties.getAppkey());
//将响应体加密后 包装成json格式
JSONObject jsonObject = new JSONObject();
jsonObject.put(cryptoConfigProperties.getCryptoParam(), encryptResult);
encryptedByteArray = jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8);
cryptorDetail(null, originalResponseBody, encryptResult, CryptoUtil.RES_DATA);
} catch (Exception e) {
log.error("加密失败,{}", originalResponseBody, e);
sink.error(new RuntimeException(e.getMessage(), e));
return;
}
originalResponse.getHeaders().setContentLength(encryptedByteArray.length);
originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
sink.next(dataBufferFactory.wrap(encryptedByteArray));
}));
}
return super.writeWith(body);
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.putAll(originalResponse.getHeaders());
return headers;
}
};
}
/**
* 获取原始的请求参数
*
* @param dataBuffer 数据缓冲
* @return 原始的请求参数
*/
private String getOriginalRequestBody(DataBuffer dataBuffer) {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
return new String(bytes, StandardCharsets.UTF_8);
}
/**
* 对url的参数进行解密
*/
private void updateUrlParam(ServerWebExchange exchange, CryptoVo encryptFlag) {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
String query = uri.getQuery();
String param = "";
if (!StringUtils.isEmpty(query) && query.contains(cryptoConfigProperties.getCryptoParam())) {
encryptFlag.setEncryptFlag(1);
try {
String[] split = query.split("=");
String dataSec = split[1];
param = CryptoUtil.decrypt(dataSec, cryptoConfigProperties.getAppkey());
Field targetQuery = uri.getClass().getDeclaredField("query");
targetQuery.setAccessible(true);
targetQuery.set(uri, param);
cryptorDetail(request.getPath(), dataSec, param, CryptoUtil.REQ_URL);
} catch (Exception e) {
log.error("【url解密失败】路径:{},参数:{}", request.getPath(), param);
throw new RuntimeException(e.getMessage(), e);
}
}
}
/**
* 输出详细日志
*
* @param path
* @param before
* @param after
* @param type 0:请求url 1请求body 2 响应
*/
private void cryptorDetail(RequestPath path, String before, String after, Integer type) {
if (!log.isDebugEnabled()) {
return;
}
switch (type) {
case CryptoUtil.REQ_URL:
log.info("【请求url解密】路径:{},解密前:{},解密后:{}", path, before, after);
break;
case CryptoUtil.REQ_BODY:
log.info("【请求body解密】路径:{},解密前:{},解密后:{}", path, before, after);
break;
case CryptoUtil.RES_DATA:
log.info("【响应加密】加密前:{},加密后:{}", before, after);
break;
default:
break;
}
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}