Java请求加密 后端解密 Vue+oauth+geteway微服务 SM4国标
本来上线的微服务架构受安全检查需要 登录用户名密码加密访问 本来以为是在outh 加密原来是 从前端提交就要加密提交 后端解密授权 话不多说直接上思路和代码
思路:因为我后端用的 oauth2.0授权 所有在geteway拦截请求解密参数再转发(如果正常接口登录也是一样
因为我这边项目原因只能选择国标加密遂选择SM4 (有其他选择项的小伙伴可以自主选择
getWay拦截器重点
import cn.datax.common.exception.DataException;
import cn.datax.gateway.util.SM4Util;
import cn.hutool.core.util.StrUtil;
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.route.Route;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
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.Base64Utils;
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.net.URI;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.function.Consumer;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*;
@Slf4j
@Component
@Order(0)
public class DataGatewayRequestFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String path = request.getURI().getPath(); // 当前调用方法的url
printLog(exchange);
// request.getHeaders();
HttpHeaders header = request.getHeaders();
log.info("header = {} 请求地址:{}", header,path);
byte[] token = Base64Utils.encode((DataConstant.Security.TOKENVALUE.getVal()).getBytes());
String[] headerValues = {new String(token)};
log.info("tokenheader = {}", DataConstant.Security.TOKENHEADER.getVal());
// header.set(DataConstant.Security.TOKENHEADER.getVal(), String.valueOf(headerValues));
//
String apiKey = String.valueOf(header.get("api_key")==null?"":header.get("api_key"));
String secretKey = String.valueOf(header.get("secret_key")==null?"":header.get("secret_key"));
if (StrUtil.isBlank(apiKey) || StrUtil.isBlank(secretKey)) {
log.info("api_key或secret_key空");
}else{
// Consumer<HttpHeaders> httpHeaders=httpHeader -> {
// httpHeader.set("api_key",apiKey);
// httpHeader.set("secret_key",secretKey);
// httpHeader.set(DataConstant.Security.TOKENHEADER.getVal(), String.valueOf(headerValues));
// };
}
if(path.equals("/oauth/token")){
log.info("*********************请求拦截*********************");
return readBody(exchange, chain);
}
ServerHttpRequest build = request.mutate().header(DataConstant.Security.TOKENHEADER.getVal(), headerValues).build();
// ServerHttpRequest build = request.mutate().headers(header).build();
ServerWebExchange newExchange = exchange.mutate().request(build).build();
return chain.filter(newExchange);
}
private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain) {
//重新构造request,参考ModifyRequestBodyGatewayFilterFactory
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
//重点
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
//因为约定了终端传参的格式,所以只考虑json的情况,如果是表单传参,请自行增加
if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON_UTF8.isCompatibleWith(mediaType)|| MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) {
String newBody = null;
try{
// 解密body
log.info("--------------------{}-------------------",body);// System.out.println(body);
String key="GJstSK_YBD=gSOFT";
newBody = SM4Util.decryptEcb(key,body);
}catch (Exception e){
e.getMessage();
}
return Mono.just(newBody);
}
return Mono.empty();
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
//猜测这个就是之前报400错误的元凶,之前修改了body但是没有重新写content length
headers.remove("Content-Length");
//MyCachedBodyOutputMessage 这个类完全就是CachedBodyOutputMessage,只不过CachedBodyOutputMessage不是公共的
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage);
return returnMononew(chain, exchange.mutate().request(decorator).build());
}));
}
private Mono<Void> returnMononew(GatewayFilterChain chain,ServerWebExchange exchange){
return chain.filter(exchange).then(Mono.fromRunnable(()->{
}));
}
ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
return new ServerHttpRequestDecorator(exchange.getRequest()) {
public HttpHeaders getHeaders() {
long contentLength = headers.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0L) {
httpHeaders.setContentLength(contentLength);
} else {
httpHeaders.set("Transfer-Encoding", "chunked");
}
return httpHeaders;
}
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
}
private void printLog(ServerWebExchange exchange) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
LinkedHashSet<URI> uris = exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
URI originUri = null;
if (uris != null) {
originUri = uris.stream().findFirst().orElse(null);
}
if (url != null && route != null && originUri != null) {
log.info("转发请求:{}://{}{} --> 目标服务:{},目标地址:{}://{}{},转发时间:{}",
originUri.getScheme(), originUri.getAuthority(), originUri.getPath(),
route.getId(), url.getScheme(), url.getAuthority(), url.getPath(), LocalDateTime.now()
);
}
}
}
`
**后端加解密工具类:**
```import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
public class SM4Util {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String ENCODING = "UTF-8";
public static final String ALGORITHM_NAME = "SM4";
// 加密算法/分组加密模式/分组填充方式
// PKCS5Padding-以8个字节为一组进行分组加密
// 定义分组加密模式使用:PKCS5Padding
public static final String ALGORITHM_NAME_ECB_PADDING7 = "SM4/ECB/PKCS5Padding";
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS7Padding";
// 64-16位16进制;128-32位16进制;256-64位16进制
public static final int DEFAULT_KEY_SIZE = 128;
/**
* 生成ECB暗号
*
* @param algorithmName 算法名称
* @param mode 模式
* @param key
* @return
* @throws Exception
* @explain ECB模式(电子密码本模式:Electronic codebook)
*/
private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
}
// 产生密钥
/**
* 自动生成密钥
*
* @return
* @throws
* @throws
* @explain
*/
public static byte[] generateKey() throws Exception {
return generateKey(DEFAULT_KEY_SIZE);
}
/**
* @param keySize
* @return
* @throws Exception
* @explain
*/
public static byte[] generateKey(int keySize) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
kg.init(keySize, new SecureRandom());
return kg.generateKey().getEncoded();
}
/**
* sm4加密
*
* @param hexKey 16进制密钥(忽略大小写)
* @param paramStr 待加密字符串
* @return 返回16进制的加密字符串
* @throws Exception
* @explain 加密模式:ECB
* 密文长度不固定,会随着被加密字符串长度的变化而变化
*/
public static String encryptEcb(String hexKey, String paramStr) throws Exception {
String cipherText = "";
// 16进制字符串-->byte[]
//byte[] keyData = ByteUtils.fromHexString(hexKey);
byte[] keyData = hexKey.getBytes(ENCODING);
// String-->byte[]
byte[] srcData = paramStr.getBytes(ENCODING);
// 加密后的数组
byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
// byte[]-->hexString
//cipherText = ByteUtils.toHexString(cipherArray);
cipherText = Base64.encodeBase64String(cipherArray);
return cipherText;
}
/**
* 加密模式之Ecb
*
* @param key
* @param data
* @return
* @throws Exception
* @explain
*/
public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
/**
* sm4解密
*
* @param hexKey 16进制密钥
* @param cipherText 16进制的加密字符串(忽略大小写)
* @return 解密后的字符串
* @throws Exception
* @explain 解密模式:采用ECB
*/
public static String decryptEcb(String hexKey, String cipherText) throws Exception {
// 用于接收解密后的字符串
String decryptStr = "";
// hexString-->byte[]
//byte[] keyData = ByteUtils.fromHexString(hexKey);
byte[] keyData = hexKey.getBytes(ENCODING);
// hexString-->byte[]
//byte[] cipherData = ByteUtils.fromHexString(cipherText);
byte[] cipherData =Base64.decodeBase64(cipherText);
// 解密
byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
// byte[]-->String
decryptStr = new String(srcData, ENCODING);
return decryptStr;
}
/**
* 解密
*
* @param key
* @param cipherText
* @return
* @throws Exception
* @explain
*/
public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
/**
* 校验加密前后的字符串是否为同一数据
*
* @param hexKey 16进制密钥(忽略大小写)
* @param cipherText 16进制加密后的字符串
* @param paramStr 加密前的字符串
* @return 是否为同一数据
* @throws Exception
* @explain
*/
public static boolean verifyEcb(String hexKey, String cipherText, String paramStr) throws Exception {
// 用于接收校验结果
boolean flag = false;
// hexString-->byte[]
//byte[] keyData = ByteUtils.fromHexString(hexKey);
byte[] keyData = hexKey.getBytes(ENCODING);
// 将16进制字符串转换成数组
//byte[] cipherData = ByteUtils.fromHexString(cipherText);
byte[] cipherData = Base64.decodeBase64(cipherText);
// 解密
byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData);
// 将原字符串转换成byte[]
byte[] srcData = paramStr.getBytes(ENCODING);
// 判断2个数组是否一致
flag = Arrays.equals(decryptData, srcData);
return flag;
}
public static void main(String[] args) throws Exception {
String key="GJstSK_YBD=gSOFT";
System.out.println(decryptEcb(key,"uQ3SlpR+OrUXgYurp4Mt+cv4mkIDQu5s2JBwjw4jgFdQhiuK5Hfq0zK3/Eq/CaYKY5QJYfCb+mI87mQRYgm/Mw=="));
System.out.println(decryptEcb(key,"cjUwtOA2mKFqTdViRH7suw=="));
String a="username=admin&password=123456&grant_type=password&scope=all";
}
}``````````````
**前端加解密工具类:**
浙公网安备 33010602011771号