|
本文章实现的是 网关中的 参数解密、响应数据体加密功能。

1 集成 commons-codec
commons-codec 是Apache开源组织提供的用于摘要运算、编码解码的包。常见的编码解码工具Base64、MD5、Hex、SHA1、DES等。
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>本项目中集成RSA 非对称算法,RSAUtils 工具类
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class RSAUtils {
public static final String PUBLIC_KEY = &#34;public_key&#34;;
public static final String PRIVATE_KEY = &#34;private_key&#34;;
public static Map<String, String> generateRasKey() {
Map<String, String> rs = new HashMap<>();
try {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = null;
keyPairGen = KeyPairGenerator.getInstance(&#34;RSA&#34;);
keyPairGen.initialize(1024, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到私钥 公钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到Map
rs.put(PUBLIC_KEY, publicKeyString);
rs.put(PRIVATE_KEY, privateKeyString);
} catch (Exception e) {
throw new RuntimeException(&#34;RsaUtils 生成公钥失败...&#34;);
}
return rs;
}
public static String encrypt(String str, String publicKey) {
try {
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(&#34;RSA&#34;).generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance(&#34;RSA&#34;);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
//当长度过长的时候,需要分割后加密 117个字节
byte[] resultBytes = getMaxResultEncrypt(str, cipher);
return Base64.encodeBase64String(resultBytes);
} catch (Exception e) {
log.error(&#34;加密失败:&#34;+e.getMessage());
throw new RuntimeException(&#34;RsaUtils 加密失败&#34;);
}
}
private static byte[] getMaxResultEncrypt(String str, Cipher cipher) throws IllegalBlockSizeException, BadPaddingException {
byte[] inputArray = str.getBytes(StandardCharsets.UTF_8);
int inputLength = inputArray.length;
log.info(&#34;{}|加密字节数|inputLength:{}&#34;,str, inputLength);
// 最大加密字节数,超出最大字节数需要分组加密
int MAX_ENCRYPT_BLOCK = 117;
// 标识
int offSet = 0;
byte[] resultBytes = {};
byte[] cache = {};
while (inputLength - offSet > 0) {
if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
offSet += MAX_ENCRYPT_BLOCK;
} else {
cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
offSet = inputLength;
}
resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
}
return resultBytes;
}
public static String decrypt(String str, String privateKey) {
try {
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(&#34;RSA&#34;).generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance(&#34;RSA&#34;);
cipher.init(Cipher.DECRYPT_MODE, priKey);
return new String(cipher.doFinal(inputByte));
} catch (Exception e) {
log.error(&#34;解密失败:&#34;+e.getMessage());
throw new RuntimeException(&#34;RsaUtils 解密失败.&#34;);
}
}
}然后创建一个测试类,生成一组公钥与私钥: 随机生成的公钥为:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcGdZDJOJKjcfx0zlMJxAzcZb6Hozm51L+MCyvUGsa1jaz4NVkvKsdaVny3PcGDM/DUp6tR4rtzTLDG9QX/yQI32+L4dA9xhQIvizdQxFSwj/7rJ2ecze2MHTqRCjzhQqKuWGuf/lXGlbhXY/Uf9Nn+ZJBVsdKrXPzBPpLuadn5QIDAQAB随机生成的私钥为:
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJwZ1kMk4kqNx/HTOUwnEDNxlvoejObnUv4wLK9QaxrWNrPg1WS8qx1pWfLc9wYMz8NSnq1Hiu3NMsMb1Bf/JAjfb4vh0D3GFAi+LN1DEVLCP/usnZ5zN7YwdOpEKPOFCoq5Ya5/+VcaVuFdj9R/02f5kkFWx0qtc/ME+ku5p2flAgMBAAECgYAUQP3zTvvViePhf/M1QEmdLdCAZNUKDgWkrtd9am/F1vmDXq68GAa+atxIOLIMej5oLMt4gYndz6bAeKyM7dvc3dGRZbVTR5lhYVj0nlPYwky90ZxruhRuEzIBY01yXj2HWoUq/7+dSmxKOASYDW+yKIUuE/4tZhoWZR0b24t42QJBAPb1NWe/zakFzHiTTbrffv9djLgeIuqar7B5pnZRnm/53otlsnLfDOkRLgCHnOHQp/xiHDpUtbfnxBKnx5skWnMCQQCh0QGKOCXdXzXyo1srX9Ya6LEd+gNgTpXBOn1Y3WdQ1p7kNZTcZJ61XodW4tgACv24NJUmWtEKwe/9PE8SteZHAkBy8xYlsaCf4SQYp7ARoMAzSy8Z8GUeQFwwz58NCdaulmbhCbgzQeF3htibxIPglEfs8RnkiNOAw69/Y3tEmnpDAkB6/rii7OarCzGgSlaD84Z0UaY+2Mg0LcdaZjDcmP1szpVbdPa/RqPzy/QnMKlp7vDHUQCFdMYr3RmjbHHWEPkFAkEA0e7TdHheSqyAnpy8TEXMJsmMHW/37RIVtY0OeQZz9TuXG6TtsjZIna0QviCFQtxg9Zz3oRfDIoM3IrasuDFrRA==然后保存在一个类中
public class RSAConstant {
//私钥
public static final String PRIVATE_KEY = &#34;&#34;;
//公钥
public static final String PUBLICK_KEY = &#34;&#34;;
}
2 网关项目中创建 RequestEncryptFilter
RequestEncryptFilter 在过滤器中获取请求的参数,解密后再将参数设置回去
import com.alibaba.cloud.commons.lang.StringUtils;
import com.biglead.common.utils.RSAUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
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.lang.reflect.Field;
import java.net.URI;
@Configuration
@Slf4j
public class RequestEncryptFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info(&#34;============================RequestEncryptFilter start===================================&#34;);
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
//请求方法
HttpMethod method = request.getMethod();
MediaType mediaType = request.getHeaders().getContentType();
String sign = request.getHeaders().getFirst(&#34;encrypt&#34;);
if(StringUtils.isEmpty(sign)){
log.info(&#34;不需要解密 &#34;);
return chain.filter(exchange);
}
log.info(&#34;需要解密数据 &#34;);
if (method == HttpMethod.GET) {
//1 修改请求参数,并获取请求参数
try {
updateRequestParam(exchange);
} catch (Exception e) {
return MonoUtils.invalidUrl(exchange);
}
}
if (method != HttpMethod.POST) {
log.info(&#34;非POST请求 不需要解密 &#34;);
return chain.filter(exchange);
}
//2 获取请求体,修改请求体
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
//解密请求体
String encrypt = RSAUtils.decrypt(body, RSAConstant.PRIVATE_KEY);
return Mono.just(encrypt);
});
//3 创建BodyInserter修改请求体
BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
//4 创建CachedBodyOutputMessage并且把请求param加入
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
return chain.filter(exchange.mutate().request(decorator).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(&#34;param&#34;)) {
String[] split = query.split(&#34;=&#34;);
String paramValue = split[1];
//解密请求参数
String param = RSAUtils.decrypt(paramValue, RSAConstant.PRIVATE_KEY);
//使用反射强行拿出 URI 的 query
Field targetQuery = uri.getClass().getDeclaredField(&#34;query&#34;);
//授权
targetQuery.setAccessible(true);
//重新设置参数
targetQuery.set(uri, param);
}
}
@Override
public int getOrder() {
return -1;
}
}
3 ResponseEncryptFilter 响应数据加密
ResponseEncryptFilter 主要是用来实现对响应数据体的加密 ,所以这里实现的思路是 :
- 获取响应体数据
- 获取加密标识 encrypt
- 加密
import com.alibaba.fastjson.JSON;
import com.biglead.common.utils.RSAUtils;
import com.google.common.base.Charsets;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
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.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
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 java.net.URI;
import java.util.Map;
import java.util.Objects;
@Configuration
@Slf4j
public class ResponseEncryptFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info(&#34;============================ResponseEncryptFilter start===================================&#34;);
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
String url = uri.getPath();
HttpStatus statusCode = exchange.getResponse().getStatusCode();
if(Objects.equals(statusCode, HttpStatus.BAD_REQUEST) || Objects.equals(statusCode, HttpStatus.TOO_MANY_REQUESTS)){
// 如果是特殊的请求,已处理响应内容,这里不再处理
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());
}
@Override
public int getOrder() {
return -1;
}
private ServerHttpResponseDecorator buildResponse(ServerHttpResponse originalResponse, DataBufferFactory bufferFactory) {
return new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
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(&#34;encrypt&#34;);
if(encrypt!=null){
log.info(&#34;加密响应数据 开始 :{}&#34;,responseData);
//加密数据
responseData = RSAUtils.encrypt(responseData,RSAConstant.PUBLICK_KEY);
log.info(&#34;加密响应数据 完成 :{}&#34;,responseData);
}
byte[] uppedContent = responseData.getBytes(Charsets.UTF_8);
originalResponse.getHeaders().setContentLength(uppedContent.length);
return bufferFactory.wrap(uppedContent);
}));
} else {
log.error(&#34;获取响应体数据 :&#34;+getStatusCode());
}
return super.writeWith(body);
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
};
}
}
测试获取订单详情数据 - 未加密的数据

测试获取加密的订单数据

对应的订单服务中的控制器
@Slf4j
@RestController
@RequestMapping(&#34;/order&#34;)
public class OrderController {
@Resource
private OrderService orderService;
@Value(&#34;${server.port}&#34;)
private String serverPort;
/**
* @param id 订单id
* @return 用户
*/
@GetMapping(value = &#34;/{id}&#34;)
public OrderInfo queryById(@PathVariable(&#34;id&#34;) Long id) {
log.info(&#34;查询订单信息 port {}&#34;,serverPort);
return orderService.queryById(id);
}
/**
* 返回加密的请求体
* @param id
* @return
*/
@GetMapping(value = &#34;/encrypt/{id}&#34;)
public Result queryEncryptById(@PathVariable(&#34;id&#34;) Long id) {
log.info(&#34;查询订单信息 port {}&#34;,serverPort);
OrderInfo orderInfo = orderService.queryById(id);
return Result.okEncryptData(orderInfo);
}
} |
|