数据加解密

This commit is contained in:
haozq 2024-03-22 15:16:34 +08:00
parent 4e67f58655
commit f16c58f460
8 changed files with 546 additions and 18 deletions

View File

@ -17,10 +17,7 @@ import io.jsonwebtoken.Claims;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@ -56,7 +53,6 @@ public class TokenController {
}
/**
* 本地推出登录
* @param request

View File

@ -0,0 +1,52 @@
package com.securitycontrol.common.core.utils;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @Author: meng
* @Description: 常用变量
* @Date: 2023/3/30 10:29
* @Version: 1.0
*/
@Component
public class CommonConstant {
//JWT密钥
public static final String JWT_TOKEN = "jwt-token";
//请求头中的token
public static final String X_TOKEN = "X-TOKEN";
//请求头中的sign
public static final String X_SIGN = "X-SIGN";
public static final String X_APPID = "X-APPID";
public static final String CODE = "code";
public static final String MESSAGE = "message";
public static final String UTF8 = "UTF-8";
public static final String RSA_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDFJIl4il6nDBlF/3byWB/KXRqfEXkviz7ZvO7TU7JBfh7sFqfgLtJFDSA33+qTHOtYTCjCrwl6oWWX7Aff39HiFW1IBnhKjYdSK5/8ruQY+Y2xbpBMgslA0m2euOv3XPJUXWh0JGBqPllgzvtbtUA1iBELAHVYBACuQPYP2VcPeQIDAQAB";
public static final String RSA_PRIVATE_KEY = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMUkiXiKXqcMGUX/dvJYH8pdGp8ReS+LPtm87tNTskF+HuwWp+Au0kUNIDff6pMc61hMKMKvCXqhZZfsB9/f0eIVbUgGeEqNh1Irn/yu5Bj5jbFukEyCyUDSbZ646/dc8lRdaHQkYGo+WWDO+1u1QDWIEQsAdVgEAK5A9g/ZVw95AgMBAAECgYABvRrBR2ciTgcDCQfBh2lwXXXYpUzOUIoTXYk1r+1IipY3OtPsND2CgmUgWQc2mPCybKmHXgfVXwsIVfqTzOOK+PEMVGYNflUdXgV3hNffRzl/nfPdpqhb2ALu8ftPwiGq5QN2PqaRgY9kM67Ye/cCjFzm/kLIqsNuXLKiQc1ioQJBAO7g4ZBcG/D0IxtiR4RdXYtr4wQc+cmscSKj5RPNBwn0bh9psOSg2loS/wWUmCnYSncsLGgMzPl+yPkTLwGryH0CQQDTRduiOzu6bFdOw6tI6eOxHB5h0kfcim4VT/Huh5RyP+GC7kLBmknbBO/tQXxSDVaG81Pkr+INHxJmctfKik+tAkEAtBIrl0IIAhRXnp3wYXRsPtxeLkyVc5SdWEqKNen5Y2Sx2tY2dbJXx0zIl3FTXz/fqoRPGUSFA5Kydygh6DWRlQJBAMmOfOHB9tJ8Z7LJ85AFKucdt1KFpW8eVbVZZqq0iAeTMBaULfW7tzgO9sJ3Vh6FgQYP//pNXbA883XvnDUrTKUCQQDgLO7mThmy7iqqo0be4a2ycy9fvORFYzSq1t6mTd+gr73CMCy2bTmyv/Qp4QsuPIKea0iE+HA/la5zlM8eAxOq";
//公共返回方法
public static Mono<Void> buildResponse(ServerWebExchange exchange, int code, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
JSONObject jsonObject = new JSONObject();
jsonObject.put(CODE, code);
jsonObject.put(MESSAGE, message);
DataBuffer bodyDataBuffer = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
return response.writeWith(Mono.just(bodyDataBuffer));
}
}

View File

@ -38,7 +38,7 @@ public class AesCbcUtils {
/**
* AES要求密钥长度为128位或192位或256位java默认限制AES密钥长度最多128位
*/
public static String sKey = "zhst@bonus@zhst@bonus@1234567890";
public static String sKey = "zhgd@bonus@zhgd@bonus@1234567890";
/**
* 编码格式导出
@ -58,26 +58,44 @@ public class AesCbcUtils {
* @throws Exception
* @return 加密后的密文
*/
public static String encrypt(String source, String key) throws Exception {
byte[] sourceBytes = source.getBytes(ENCODING);
byte[] keyBytes = key.getBytes(ENCODING);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, "BC");
IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(ENCODING));
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, KEY_ALGORITHM), iv);
byte[] decrypted = cipher.doFinal(sourceBytes);
return Base64.encodeBase64String(decrypted);
public static String encrypt(String source ) {
try{
String key=sKey;
byte[] sourceBytes = source.getBytes(ENCODING);
byte[] keyBytes = key.getBytes(ENCODING);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, "BC");
IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(ENCODING));
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, KEY_ALGORITHM), iv);
byte[] decrypted = cipher.doFinal(sourceBytes);
return Base64.encodeBase64String(decrypted);
}catch (Exception e){
log.error(e.toString(),e);
}
return null;
}
public static void main(String[] args) {
String json="username=guest&password=admin@123";
// String json="{\"username\":\"guest\",\"password\":\"admin@123\"}";
String data=encrypt(json);
System.err.println(data);
}
/**
* AES解密
*CBC模式
* @param encryptStr 加密后的密文
* @param data 加密后的密文
* @param
* @throws Exception
* @return 源字符串
*/
public static String decrypt(String encryptStr) {
public static String decrypt(String data) {
try{
String encryptStr="";
if(StringHelper.isNotEmpty(data)){
encryptStr=data.replace(" ","+");
}
String key=sKey;
byte[] sourceBytes = Base64.decodeBase64(encryptStr);
byte[] keyBytes = key.getBytes(ENCODING);
@ -87,9 +105,10 @@ public class AesCbcUtils {
byte[] decoded = cipher.doFinal(sourceBytes);
return new String(decoded, ENCODING);
}catch (Exception e){
log.info("------------------->请求加密参数不正确");
log.error(e.toString(),e);
return null;
}
return null;
}
}

View File

@ -0,0 +1,38 @@
package com.securitycontrol.common.core.utils.aes;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
/**
* @author 黑子
*/
public final class MonoUtils {
private MonoUtils() {
}
public static Mono<Void> invalidUrl(ServerWebExchange exchange){
JSONObject json = new JSONObject();
json.put("code", 400);
json.put("msg", "无效的请求");
return buildReturnMono(json, exchange);
}
public static Mono<Void> buildReturnMono(JSONObject json, ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
byte[] bits = json.toJSONString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定编码否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}

View File

@ -0,0 +1,43 @@
package com.securitycontrol.gateway.filter;
import lombok.Data;
import org.springframework.http.HttpHeaders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* @Author: meng
* @Description: 网关上下文
* @Version: 1.0
*/
@Data
public class GatewayContext {
public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext";
/**
* cache headers
*/
private HttpHeaders headers;
/**
* cache json body
*/
private String cacheBody;
/**
* cache formdata
*/
private MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
/**
* ipAddress
*/
private String ipAddress;
/**
* path
*/
private String path;
}

View File

@ -0,0 +1,236 @@
package com.securitycontrol.gateway.filter;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
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.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
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.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
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.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
/**
* @Author: https://blog.csdn.net/zx156955/article/details/115004910
* @Description: 请求内容存储 处理请求内容 内容放在gatewayContext中
* @Date: 2023/3/30 10:11
* @Version: 1.0
*/
@Component
@Slf4j
public class RequestCoverFilter implements GlobalFilter, Ordered {
/**
* default HttpMessageReader
*/
private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
/**
* ReadFormData
*
* @param exchange
* @param chain
* @return
*/
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();
/**
* formData is empty just return
*/
if (null == formData || formData.isEmpty()) {
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
* @param chain
* @return
*/
private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {
/**
* join the body
*/
return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
/*
* read the body Flux<DataBuffer>, and release the buffer
* see PR https://github.com/spring-cloud/spring-cloud-gateway/pull/1095
*/
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, messageReaders).bodyToMono(String.class)
.doOnNext(objectValue -> {
gatewayContext.setCacheBody(objectValue);
log.debug("[GatewayContext]Read JsonBody:{}", objectValue);
}).then(chain.filter(mutatedExchange));
});
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/**
* save request path and serviceId into gateway context
*/
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
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());
/// 注意因为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) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
return readBody(exchange, chain, gatewayContext);
}
if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)) {
return readFormData(exchange, chain, gatewayContext);
}
}
log.debug("[GatewayContext]ContentType:{},Gateway context is set with {}", contentType, gatewayContext);
return chain.filter(exchange);
}
}

View File

@ -0,0 +1,144 @@
package com.securitycontrol.gateway.filter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.CryptoException;
import com.securitycontrol.common.core.utils.CommonConstant;
import com.securitycontrol.common.core.utils.StringUtils;
import com.securitycontrol.common.core.utils.aes.AesCbcUtils;
import com.securitycontrol.common.core.utils.aes.MonoUtils;
import com.securitycontrol.common.core.utils.aes.StringHelper;
import lombok.extern.slf4j.Slf4j;
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.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import java.lang.reflect.Field;
import java.net.URI;
import java.security.interfaces.RSAPrivateKey;
/**
* @Author: meng
* @Description: RSA实现对请求参数解密
* @Date: 2023/4/6 15:20
* @Version: 1.0
*/
@Slf4j
@Component
class RsaDecryptResponseGatewayFilterFactory extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
HttpHeaders header = serverHttpRequest.getHeaders();
String decrypt = serverHttpRequest.getHeaders().getFirst("decrypt");
//get请求 默认
if(HttpMethod.GET.matches(serverHttpRequest.getMethodValue())){//如果是get
if(exchange.getRequest().getQueryParams().isEmpty()){//如果参数是空的
return chain.filter(exchange);
}else{
try{
updateRequestParam(exchange);
}catch (Exception e){
log.error(e.toString(),e);
return CommonConstant.buildResponse(exchange, HttpStatus.BAD_REQUEST.value(), "请求参数异常");
}
}
}
if (!HttpMethod.POST.matches(serverHttpRequest.getMethodValue())) {// post请求
return chain.filter(exchange);
}
byte[] decrypBytes;
GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT);
if(StrUtil.isBlank(gatewayContext.getCacheBody())){
if(!exchange.getRequest().getQueryParams().isEmpty()){
try{
updateRequestParam(exchange);
}catch (Exception e){
log.error(e.toString(),e);
return CommonConstant.buildResponse(exchange, HttpStatus.BAD_REQUEST.value(), "请求参数异常");
}
}
//未强制加密
return chain.filter(exchange);
// return CommonConstant.buildResponse(exchange, HttpStatus.BAD_REQUEST.value(), "请求参数不能为空");
}
try {
// 获取request body
String requestBody = gatewayContext.getCacheBody();
String decryptMsg= AesCbcUtils.decrypt(requestBody);
gatewayContext.setCacheBody(decryptMsg);
decrypBytes = decryptMsg.getBytes();
} catch (Exception e) {
log.error("数据 解密失败:{}", e);
return CommonConstant.buildResponse(exchange, HttpStatus.BAD_REQUEST.value(), "数据解密失败");
}
// 根据解密后的参数重新构建请求
DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
Flux<DataBuffer> bodyFlux = Flux.just(dataBufferFactory.wrap(decrypBytes));
ServerHttpRequest newRequest = serverHttpRequest.mutate().uri(serverHttpRequest.getURI()).build();
newRequest = new ServerHttpRequestDecorator(newRequest) {
@Override
public Flux<DataBuffer> getBody() {
return bodyFlux;
}
};
// 构建新的请求头
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
// 由于修改了传递参数需要重新设置CONTENT_LENGTH长度是字节长度不是字符串长度
int length = decrypBytes.length;
headers.remove(HttpHeaders.CONTENT_LENGTH);
headers.setContentLength(length);
newRequest = new ServerHttpRequestDecorator(newRequest) {
@Override
public HttpHeaders getHeaders() {
return headers;
}
};
// 把解密后的数据重置到exchange自定义属性中,在之后的日志GlobalLogFilter从此处获取请求参数打印日志
exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT, gatewayContext);
return chain.filter(exchange.mutate().request(newRequest).build());
};
}
/**
* 修改前端传的参数
*/
private void updateRequestParam(ServerWebExchange exchange) throws NoSuchFieldException, IllegalAccessException {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
//请求参数
String query = uri.getQuery();
//判断是否有加密的参数 这里的约定是 param
if (StringUtils.isNotBlank(query) && query.contains("params")) {
String[] split = query.split("=");
String paramValue = split[1];
//解密请求参数
String param =AesCbcUtils.decrypt(paramValue);
//使用反射强行拿出 URI query
Field targetQuery = uri.getClass().getDeclaredField("query");
//授权
targetQuery.setAccessible(true);
//重新设置参数
targetQuery.set(uri, param);
}
}
}

View File

@ -55,7 +55,7 @@ public class TeamServiceImpl implements TeamService {
} else {
vo.setType(2);
}
vo.setIdNumber(AesCbcUtils.encrypt(vo.getIdNumber(),AesCbcUtils.sKey));
vo.setIdNumber(AesCbcUtils.encrypt(vo.getIdNumber()));
mapper.addOrUpdateTeam(vo);
} catch (Exception e) {
log.error("新增/修改班组", e);