網(wǎng)關(guān)加密/解密攔截器
使用aes加密寡喝,加密工具使用Hutool
- 解密接口的url參數(shù)
- 解密接口的body參數(shù)
- 加密接口返回?cái)?shù)據(jù)
1.yml配置
#接口加密
config:
crypto:
#是否啟動(dòng)加密
enabled: true
#加密信息字段
cryptoParam: cryptoParam
#AES加密key
appkey: 123456
logging:
level:
#日志級(jí)別 debug:輸出加密過程 info:不輸出
com.ty.filter.CryptoFilter: info
2.配置類 工具類
@Data
@Configuration
@ConfigurationProperties(prefix = "config.crypto")
public class CryptoConfigProperties {
/**
* 加密字段盾饮,用哪個(gè)字段顯示加密的數(shù)據(jù)
*/
private String cryptoParam;
/**
* AES加密key
*/
private String appkey;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CryptoVo {
private String body;
/**
* 前端是否加密硝清,加密則響應(yīng)體也加密 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參數(shù)
* 2.解密接口的body參數(shù)
* 3.加密接口返回?cái)?shù)據(jù)
*
* @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);
//對(duì)url的參數(shù)進(jìn)行解密
updateUrlParam(exchange, cryptoVo);
//不存在body則直接返回
if (HttpMethod.GET.equals(method) || !MediaType.APPLICATION_JSON.isCompatibleWith(headers.getContentType())) {
if (cryptoVo.getEncryptFlag() > 0) {
//響應(yīng)體加密
exchange = exchange.mutate().response(serverHttpResponseDecorator(response, cryptoVo)).build();
}
return chain.filter(exchange);
}
//application/json對(duì)body的參數(shù)解密
Flux<DataBuffer> body = request.getBody();
Mono<DataBuffer> dataBufferMono = DataBufferUtils.join(body);
ServerWebExchange finalExchange = exchange;
return dataBufferMono.flatMap(dataBuffer -> {
try {
// 獲取請求參數(shù)
String originalRequestBody = getOriginalRequestBody(dataBuffer);
cryptoVo.setBody(originalRequestBody);
// 解密請求參數(shù)
String decryptRequestBody = decryptRequest(request, cryptoVo);
// 重寫請求和響應(yīng)方式
return getEnResponseBody(finalExchange, chain, request, decryptRequestBody, response, cryptoVo);
} catch (Exception e) {
log.error("密文過濾器加解密錯(cuò)誤", e);
return Mono.error(e);
} finally {
DataBufferUtils.release(dataBuffer);
}
});
}
/**
* 返回加密響應(yīng)體
*
* @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);
//加密響應(yīng)體
ServerHttpResponseDecorator serverHttpResponseDecorator = serverHttpResponseDecorator(response, cryptoVo);
ServerWebExchange serverWebExchange = exchange.mutate().request(requestDecorator).response(serverHttpResponseDecorator).build();
// 使用新的請求和響應(yīng)轉(zhuǎn)發(fā)
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解密失敗】路徑:{},參數(shù):{}", 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));
}
};
}
/**
* 新建響應(yīng)體
*
* @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 前端加密彼棍,響應(yīng)體也加密
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());
//將響應(yīng)體加密后 包裝成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;
}
};
}
/**
* 獲取原始的請求參數(shù)
*
* @param dataBuffer 數(shù)據(jù)緩沖
* @return 原始的請求參數(shù)
*/
private String getOriginalRequestBody(DataBuffer dataBuffer) {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
return new String(bytes, StandardCharsets.UTF_8);
}
/**
* 對(duì)url的參數(shù)進(jìn)行解密
*/
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解密失敗】路徑:{},參數(shù):{}", request.getPath(), param);
throw new RuntimeException(e.getMessage(), e);
}
}
}
/**
* 輸出詳細(xì)日志
*
* @param path
* @param before
* @param after
* @param type 0:請求url 1請求body 2 響應(yīng)
*/
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("【響應(yīng)加密】加密前:{},加密后:{}", before, after);
break;
default:
break;
}
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}