SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。
程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发
本文章是系列文章中的一篇
- 1、SpringCloud 项目基础工程搭建 【SpringCloud系列1】
- 2、SpringCloud 集成Nacos注册中心 【SpringCloud系列2】
- 3、SpringCloud Feign远程调用 【SpringCloud系列3】
- 4、SpringCloud Feign远程调用公共类抽取 【SpringCloud系列4】
- 5、SpringCloud 整合Gateway服务网关 【SpringCloud系列5】
- 6、SpringCloud 整合 Spring Security 认证鉴权【SpringCloud系列6】
- SpringCloud网关Gateway认证鉴权【SpringCloud系列7】
- SpringCloud Gateway 通过redis实现限流【SpringCloud系列8】
- SpringCloud Gateway 整合阿里 Sentinel网关流控、熔断、降级【SpringCloud系列9】
本文章实现的是 网关中的 参数解密、响应数据体加密功能。
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 = "public_key";
public static final String PRIVATE_KEY = "private_key";
public static Map<String, String> generateRasKey() {
Map<String, String> rs = new HashMap<>();
try {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = null;
keyPairGen = KeyPairGenerator.getInstance("RSA");
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("RsaUtils 生成公钥失败...");
}
return rs;
}
public static String encrypt(String str, String publicKey) {
try {
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
//当长度过长的时候,需要分割后加密 117个字节
byte[] resultBytes = getMaxResultEncrypt(str, cipher);
return Base64.encodeBase64String(resultBytes);
} catch (Exception e) {
log.error("加密失败:"+e.getMessage());
throw new RuntimeException("RsaUtils 加密失败");
}
}
private static byte[] getMaxResultEncrypt(String str, Cipher cipher) throws IllegalBlockSizeException, BadPaddingException {
byte[] inputArray = str.getBytes(StandardCharsets.UTF_8);
int inputLength = inputArray.length;
log.info("{}|加密字节数|inputLength:{}",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("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
return new String(cipher.doFinal(inputByte));
} catch (Exception e) {
log.error("解密失败:"+e.getMessage());
throw new RuntimeException("RsaUtils 解密失败.");
}
}
}
然后创建一个测试类,生成一组公钥与私钥:
随机生成的公钥为:
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 = "";
//公钥
public static final String PUBLICK_KEY = "";
}
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("============================RequestEncryptFilter start===================================");
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
//请求方法
HttpMethod method = request.getMethod();
MediaType mediaType = request.getHeaders().getContentType();
String sign = request.getHeaders().getFirst("encrypt");
if(StringUtils.isEmpty(sign)){
log.info("不需要解密 ");
return chain.filter(exchange);
}
log.info("需要解密数据 ");
if (method == HttpMethod.GET) {
//1 修改请求参数,并获取请求参数
try {
updateRequestParam(exchange);
} catch (Exception e) {
return MonoUtils.invalidUrl(exchange);
}
}
if (method != HttpMethod.POST) {
log.info("非POST请求 不需要解密 ");
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("param")) {
String[] split = query.split("=");
String paramValue = split[1];
//解密请求参数
String param = RSAUtils.decrypt(paramValue, RSAConstant.PRIVATE_KEY);
//使用反射强行拿出 URI 的 query
Field targetQuery = uri.getClass().getDeclaredField("query");
//授权
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("============================ResponseEncryptFilter start===================================");
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("encrypt");
if(encrypt!=null){
log.info("加密响应数据 开始 :{}",responseData);
//加密数据
responseData = RSAUtils.encrypt(responseData,RSAConstant.PUBLICK_KEY);
log.info("加密响应数据 完成 :{}",responseData);
}
byte[] uppedContent = responseData.getBytes(Charsets.UTF_8);
originalResponse.getHeaders().setContentLength(uppedContent.length);
return bufferFactory.wrap(uppedContent);
}));
} else {
log.error("获取响应体数据 :"+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("/order")
public class OrderController {
@Resource
private OrderService orderService;
@Value("${server.port}")
private String serverPort;
/**
* @param id 订单id
* @return 用户
*/
@GetMapping(value = "/{id}")
public OrderInfo queryById(@PathVariable("id") Long id) {
log.info("查询订单信息 port {}",serverPort);
return orderService.queryById(id);
}
/**
* 返回加密的请求体
* @param id
* @return
*/
@GetMapping(value = "/encrypt/{id}")
public Result queryEncryptById(@PathVariable("id") Long id) {
log.info("查询订单信息 port {}",serverPort);
OrderInfo orderInfo = orderService.queryById(id);
return Result.okEncryptData(orderInfo);
}
}
在 Result.okEncryptData 中封装了加密标识
public class Result extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public static Result okEncryptData(Object data) {
Result r = new Result();
r.put("data", data);
r.put("code", 200);
r.put("message","操作成功");
r.put("encrypt",true);
return r;
}
}
本项目源码 https://gitee.com/android.long/spring-cloud-biglead/tree/master/biglead-api-10-rsa
如果有兴趣可以关注一下公众号 biglead ,每周都会有 java、Flutter、小程序、js 、英语相关的内容分享