Vue + Springboot 前后端完整使用国密算法 SM2 数据加密 传输 交互 完整解决方案
项目外网部署的时候经常会有要求数据加密传输的情况,特别是企事业单位的项目,另为安全或者红头文件计,经常要求使用国密算法,因为涉及交互,所以使用SM2非对称加密。后端(Springboot)(1)所需主要依赖(其他如有缺失自行百度即可):<!-- hutool工具类 --><dependency><groupId>cn.hutool</groupId>
项目外网部署的时候经常会有要求数据加密传输的情况,特别是企事业单位的项目,另为安全或者红头文件计,经常要求使用国密算法,因为涉及交互,所以使用SM2非对称加密。
后端(Springboot)
(1)所需主要依赖(其他如有缺失自行百度即可):
<!-- hutool工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.10</version>
</dependency>
<!-- 第三方加密功能依赖 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.68</version>
</dependency>
(2)SM2功能方法的创建
博主这里在后端把相关的加解密功能做成了功能接口,所以这里直接展示service实现类的代码,里边的统一返回参数改成自己的类型即可
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.ruoyi.common.constant.EncryptConstant;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.encrypt.dto.ApiEncryptInfoDTO;
import com.ruoyi.common.utils.encrypt.dto.Result;
import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.stereotype.Service;
/**
* 加解密相关功能实现类
*
* @author gbx
*/
@Service
@Slf4j
public class ApiEncryptServiceImpl implements ApiEncryptService {
/**
* SM2加密
*
* @param dto 包含加解密相关参数信息的实体
* @return 处理结果
*/
@Override
public Result encrypt2Data(ApiEncryptInfoDTO dto) {
String publicKey = dto.getPublicKey();
// 若为空,使用默认
if (StringUtils.isBlank(publicKey)) {
publicKey = EncryptConstant.PUBLIC_KEY;
}
String data = dto.getData();
//创建sm2 对象
SM2 sm2 = getSM2(null, publicKey);
String dataHex = sm2.encryptBcd(data, KeyType.PublicKey);
dto.setDataHex(dataHex);
return new Result().ok(dto);
}
/**
* SM2解密
*
* @param dto 包含加解密相关参数信息的实体
* @return 处理结果
*/
@Override
public Result decrypt2Data(ApiEncryptInfoDTO dto) {
String privateKey = dto.getPrivateKey();
// 若为空,使用默认
if (StringUtils.isBlank(privateKey)) {
privateKey = EncryptConstant.PRIVATE_KEY;
}
String dataHex = dto.getDataHex();
try {
//创建sm2 对象
SM2 sm2 = getSM2(privateKey, null);
String data = StrUtil.utf8Str(sm2.decryptFromBcd(dataHex, KeyType.PrivateKey));
dto.setData(data);
} catch (Exception e) {
log.error("SM2解密失败", e);
throw new BaseException("SM2解密失败");
}
return new Result().ok(dto);
}
/**
* SM4加密
*
* @param dto 包含加解密相关参数信息的实体
* @return 处理结果
*/
@Override
public Result encrypt4Data(ApiEncryptInfoDTO dto) {
//指定的密钥
String key = dto.getKey();
// 若为空,使用默认
if (StringUtils.isBlank(key)) {
key = EncryptConstant.SM4_KEY;
}
String data = dto.getData();
try {
SymmetricCrypto sm4 = SmUtil.sm4(key.getBytes(CharsetUtil.CHARSET_UTF_8));
String dataHex = sm4.encryptHex(data);
dto.setDataHex(dataHex);
} catch (Exception e) {
log.error("加密数据异常,异常数据:" + data, e);
throw new BaseException("SM4加密数据异常");
}
return new Result().ok(dto);
}
/**
* SM4解密
*
* @param dto 包含加解密相关参数信息的实体
* @return 处理结果
*/
@Override
public Result decrypt4Data(ApiEncryptInfoDTO dto) {
//指定的密钥
String key = dto.getKey();
// 若为空,使用默认
if (StringUtils.isBlank(key)) {
key = EncryptConstant.SM4_KEY;
}
String dataHex = dto.getDataHex();
try {
SymmetricCrypto sm4 = SmUtil.sm4(key.getBytes(CharsetUtil.CHARSET_UTF_8));
String data = sm4.decryptStr(dataHex);
dto.setData(data);
} catch (Exception e) {
log.error("解密数据异常,异常数据:" + dataHex, e);
throw new BaseException("SM4解密数据异常");
}
return new Result().ok(dto);
}
/**
* 生成一对 C1C2C3 格式的SM2密钥
*
* @return 处理结果
*/
@Override
public Result getSM2Key() {
ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
//创建sm2 对象
SM2 sm2 = SmUtil.sm2();
byte[] privateKeyByte = BCUtil.encodeECPrivateKey(sm2.getPrivateKey());
//这里公钥不压缩 公钥的第一个字节用于表示是否压缩 可以不要
byte[] publicKeyByte = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false);
try {
String privateKey = HexUtil.encodeHexStr(privateKeyByte);
String publicKey = HexUtil.encodeHexStr(publicKeyByte);
dto.setPublicKey(publicKey);
dto.setPrivateKey(privateKey);
} catch (Exception e) {
log.error("获取SM2密钥出错", e);
throw new BaseException("获取SM2密钥出错");
}
return new Result().ok(dto);
}
/**
* 获取SM2加密工具对象
*
* @param privateKey 加密私钥
* @param publicKey 加密公钥
* @return 处理结果
*/
private SM2 getSM2(String privateKey, String publicKey) {
ECPrivateKeyParameters ecPrivateKeyParameters = null;
ECPublicKeyParameters ecPublicKeyParameters = null;
if (StringUtils.isNotBlank(privateKey)) {
ecPrivateKeyParameters = BCUtil.toSm2Params(privateKey);
}
if (StringUtils.isNotBlank(publicKey)) {
if (publicKey.length() == 130) {
//这里需要去掉开始第一个字节 第一个字节表示标记
publicKey = publicKey.substring(2);
}
String xhex = publicKey.substring(0, 64);
String yhex = publicKey.substring(64, 128);
ecPublicKeyParameters = BCUtil.toSm2Params(xhex, yhex);
}
//创建sm2 对象
SM2 sm2 = new SM2(ecPrivateKeyParameters, ecPublicKeyParameters);
sm2.usePlainEncoding();
sm2.setMode(SM2Engine.Mode.C1C2C3);
return sm2;
}
/**
* 获取一个随机的SM4密钥
*
* @return 处理结果
*/
@Override
public Result getSM4Key() {
String sm4Key = RandomUtil.randomString(RandomUtil.BASE_CHAR_NUMBER, 16);
ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
dto.setKey(sm4Key);
return new Result().ok(dto);
}
}
储存数据信息的类(这里使用了lombok的依赖,如果没有的话自己加上get set)
package com.ruoyi.common.utils.encrypt.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 数据加密信息DTO
*
* @author gbx
* @since 2021-05-27 10:55:32
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class ApiEncryptInfoDTO implements Serializable {
private static final long serialVersionUID = 693123123935846450L;
/**
* 加密类型(2:sm2加密,4:sm4加密)
*/
private String type;
/**
* 非对称加密私钥
*/
private String privateKey;
/**
* 非对称加密公钥
*/
private String publicKey;
/**
* 对称加密密钥
*/
private String key;
/**
* 原始数据
*/
private String data;
/**
* 加密后数据
*/
private String dataHex;
/**
* 非对称加密签名
*/
private String sign;
}
工具类
import com.ruoyi.common.utils.encrypt.dto.ApiEncryptInfoDTO;
import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;
/**
* 加解密工具类
*
* @author gbx
*/
public class EncryptUtils {
/**
* 使用SM2密钥解密数据
*
* @param sm2DataHex 密文数据
* @return 明文数据
*/
public static String getSm2Data(String sm2DataHex, ApiEncryptService encryptService, String privateKey) {
ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
dto.setPrivateKey(privateKey);
dto.setDataHex(sm2DataHex);
dto.setType("2");
encryptService.decrypt2Data(dto);
return dto.getData();
}
/**
* 使用SM2密钥加密数据
*
* @param sm2Data 明文数据
* @return 密文数据
*/
public static String getSm2DataHex(String sm2Data, ApiEncryptService encryptService, String publicKey) {
ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
dto.setPublicKey(publicKey);
dto.setData(sm2Data);
dto.setType("2");
encryptService.encrypt2Data(dto);
return dto.getDataHex();
}
/**
* 使用SM4密钥解密数据
*
* @param sm4DataHex 密文数据
* @return 明文数据
*/
public static String getSm4Data(String sm4DataHex, ApiEncryptService encryptService, String key) {
ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
dto.setKey(key);
dto.setDataHex(sm4DataHex);
dto.setType("4");
encryptService.decrypt4Data(dto);
return dto.getData();
}
/**
* 使用SM4密钥加密数据
*
* @param sm4Data 明文数据
* @return 密文数据
*/
public static String getSm4DataHex(String sm4Data, ApiEncryptService encryptService, String key) {
ApiEncryptInfoDTO dto = new ApiEncryptInfoDTO();
dto.setKey(key);
dto.setData(sm4Data);
dto.setType("4");
encryptService.encrypt4Data(dto);
return dto.getDataHex();
}
}
至此,引入功能接口,即可使用对应的加解密方法。
(3)对springboot后端指定的接口使用sm2解密请求数据,加密响应数据
ps:SM2是非对称算法,公私密钥有一对,调用之前的生成SM2密钥对的功能,生成两对密钥,一对后端使用,一对前端使用,双方各自把自己的公钥给予对方,让对方给自己传输时使用自己的公钥加密。
后端这里主要是通过添加拦截器对指定路径接口进行拦截的方式实现对前端数据的加解密的,这里前端和后端约定所有的数据(对象转为json字符串)加密后以 “data” 为key进行传输,如:
{"data": "K3KD89ASK2JKN8923JNKJF"}
这里的需要拦截加解密的接口路径是配置在yml文件内的,通过静态常量初始化类进行加载,后续可能会在之后博文中讲解具体方法,此处暂不涉及。
具体拦截器设置
(a)请求拦截解密
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;
import com.ruoyi.common.utils.encrypt.utils.EncryptDefaultUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
/**
* 请求参数解密处理
*
* @author gbx
*/
@Slf4j
public class DataEncryptionWrapper extends HttpServletRequestWrapper {
private ApiEncryptService encryptService;
private final ObjectMapper objectMapper = new ObjectMapper();
private String body = "";
/**
* 存储param,formdata,body等参数,用以获取参数的重新
*/
private Map params = new HashMap();
/**
* 统一加密请求参数的键
*/
private static final String REQ_BODY_KEY = "data";
public DataEncryptionWrapper(HttpServletRequest request, ApiEncryptService encryptService) throws IOException {
super(request);
this.encryptService = encryptService;
String contentType = request.getContentType();
if (org.apache.commons.lang3.StringUtils.isNotBlank(contentType)
&& (org.springframework.util.StringUtils.substringMatch(contentType, 0, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
) {
Map parametersMap = request.getParameterMap();
if (parametersMap.containsKey(REQ_BODY_KEY)) {
String data = ((String[]) parametersMap.get(REQ_BODY_KEY))[0];
String deJson = EncryptDefaultUtils.getSm2Data(data, encryptService);
this.params.putAll(objectMapper.readValue(deJson, Map.class));
}
//将其他form中的放进去
for (Object key : parametersMap.keySet()) {
if (!key.equals(REQ_BODY_KEY)) {
this.params.put(key, parametersMap.get(key));
}
}
} else if (org.apache.commons.lang3.StringUtils.isNotBlank(contentType)
&& org.springframework.util.StringUtils.substringMatch(contentType, 0, MediaType.APPLICATION_JSON_VALUE)) {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
String requestURI = request.getRequestURI();
//获取请求参数
String queryString = request.getQueryString();
log.info("====》 请求路径:" + requestURI + ",原请求query参数:{}", queryString);
String bodyStr = stringBuilder.toString();
bodyStr = bodyStr.startsWith("[") && bodyStr.endsWith("]") ? bodyStr.substring(1, bodyStr.length() - 1) : bodyStr;
if (StringUtils.isNotBlank(bodyStr)) {
JSONObject jsonObject = JSONObject.parseObject(bodyStr);
if (jsonObject != null) {
//获取请求body
log.info("====》 请求路径:" + requestURI + ",原请求body参数体:{}", JSONObject.toJSONString(jsonObject, SerializerFeature.WriteMapNullValue));
String enJson = jsonObject.getString(REQ_BODY_KEY);
if (org.apache.commons.lang3.StringUtils.isNotBlank(enJson)) {
String deJson = EncryptDefaultUtils.getSm2Data(enJson, encryptService);
body = deJson.startsWith("[") && deJson.endsWith("]") ? deJson.substring(1, deJson.length() - 1) : deJson;
this.params.putAll(objectMapper.readValue(body, Map.class));
}
}
}
}
// RequestDispatcher.forward parameter
renewParameterMap(request);
}
@Override
public String getParameter(String name) {
String result = "";
Object v = params.get(name);
if (v == null && StringUtils.isNotBlank(body)) {
JSONObject jsonObject = JSONObject.parseObject(body);
v = jsonObject.get(name);
}
if (v == null) {
result = null;
} else if (v instanceof String[]) {
String[] strArr = (String[]) v;
if (strArr.length > 0) {
try {
result = URLDecoder.decode(strArr[0], "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
result = null;
}
} else if (v instanceof String) {
result = (String) v;
try {
result = URLDecoder.decode(result, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
result = v.toString();
}
return result;
}
@Override
public Map getParameterMap() {
return params;
}
@Override
public Enumeration<String> getParameterNames() {
return new Vector<String>(params.keySet()).elements();
}
@Override
public String[] getParameterValues(String name) {
String[] result = null;
Object v = params.get(name);
if (v == null && StringUtils.isNotBlank(body)) {
JSONObject jsonObject = JSONObject.parseObject(body);
v = jsonObject.get(name);
}
if (v == null) {
result = null;
} else if (v instanceof String[]) {
result = (String[]) v;
for (int i = 0; i < result.length; i++) {
try {
result[i] = URLDecoder.decode(result[i], "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//增加解密param参数的操作
if (StringUtils.isNotBlank(result[i])) {
try {
result[i] = EncryptDefaultUtils.getSm2Data(result[i], encryptService);
} catch (Exception e) {
throw new BaseException("解密param请求参数失败");
}
}
}
} else if (v instanceof String) {
try {
result = new String[]{URLDecoder.decode((String) v, "utf-8")};
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//增加解密param参数的操作
if (StringUtils.isNotBlank(result[0])) {
try {
result[0] = EncryptDefaultUtils.getSm2Data(result[0], encryptService);
} catch (Exception e) {
throw new BaseException("解密param请求参数失败");
}
}
} else {
try {
result = new String[]{URLDecoder.decode(v.toString(), "utf-8")};
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//增加解密param参数的操作
if (StringUtils.isNotBlank(result[0])) {
try {
result[0] = EncryptDefaultUtils.getSm2Data(result[0], encryptService);
} catch (Exception e) {
throw new BaseException("解密param请求参数失败");
}
}
}
return result;
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes("UTF-8"));
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream(), "UTF-8"));
}
private void renewParameterMap(HttpServletRequest req) {
String queryString = req.getQueryString();
if (queryString != null && queryString.trim().length() > 0) {
String[] params = queryString.split("&");
for (int i = 0; i < params.length; i++) {
int splitIndex = params[i].indexOf("=");
if (splitIndex == -1) {
continue;
}
String key = params[i].substring(0, splitIndex);
if (!this.params.containsKey(key)) {
if (splitIndex < params[i].length()) {
String value = params[i].substring(splitIndex + 1);
try {
this.params.put(key, new String[]{URLDecoder.decode(value, "utf-8")});
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
}
}
}
import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;
import com.ruoyi.common.utils.interceptor.EncryptInterceptorUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义 DispatcherServlet 来分派 GbxHttpServletRequestWrapper
*
* @author gbx
* @date 2021-08-30 14:25
*/
public class GbxDispatcherServlet extends DispatcherServlet {
/**
* 加密功能服务接口类
*/
private ApiEncryptService encryptService;
public GbxDispatcherServlet() {
}
public GbxDispatcherServlet(ApiEncryptService encryptService) {
this.encryptService = encryptService;
}
/**
* 包装自定义的 request
*
* @param request
* @param response
* @throws Exception
*/
@Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
String requestURI = request.getRequestURI();
// 对符合要求的指定的请求路径接口进行解密操作
if (StringUtils.isNotBlank(requestURI) && EncryptInterceptorUtils.checkApi(requestURI)) {
super.doDispatch(new DataEncryptionWrapper(request, encryptService), response);
} else {
super.doDispatch(new HttpServletRequestWrapper(request), response);
}
}
}
import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;
import com.ruoyi.framework.interceptor.request.GbxDispatcherServlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* WebMVC配置,集中配置拦截器、过滤器、静态资源缓存
*
* @author gbx
* @date 2021-08-30 10:15
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private ApiEncryptService encryptService;
/**
* 使用自定义的请求转发
*
* @return
*/
@Bean
@Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
return new GbxDispatcherServlet(encryptService);
}
}
至此,通过此拦截器的数据到达 Controller Api 接口时,已经是正常的解密后的数据了,可以在相关接口内对参数进行日志打印,查看具体参数。
(b)响应拦截加密
响应参数加密比较简单,因为使用统一的返回参数体,用body方式返回,所以可以实现Springboot自带的一个返回body参数拦截接口来进行操作
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.constant.EncryptConstant;
import com.ruoyi.common.utils.encrypt.service.ApiEncryptService;
import com.ruoyi.common.utils.encrypt.utils.EncryptUtils;
import com.ruoyi.common.utils.interceptor.EncryptInterceptorUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* response 响应参数统一加密处理拦截器
*
* @author gbx
*/
@ControllerAdvice
@Slf4j
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private ApiEncryptService encryptService;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
Map<String, Object> resMap = new HashMap<>(16);
try {
String path = serverHttpRequest.getURI().getPath();
if (StringUtils.isNotBlank(path) && EncryptInterceptorUtils.checkApi(path)) {
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(body);
log.info("====》 响应路径:" + path + ",原实际返回参数:" + json);
if (StringUtils.isNotBlank(json)) {
String dataHex = EncryptUtils.getSm2DataHex(json, encryptService, EncryptConstant.UI_PUBLIC_KEY);
resMap.put("data", dataHex);
}
}
log.info("====》 响应路径:" + path + ",实际响应参数:{}", JSONObject.toJSONString(resMap, SerializerFeature.WriteMapNullValue));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
if (ObjectUtils.isNotEmpty(resMap)) {
return resMap;
}
return body;
}
}
至此,响应参数加密返回处理完毕。
前端传输,对象统一转为json串,对应"data"为key传输,param参数单独加密传输,如:
/login?validateCode=3JKD3NJKNDKJ3
前端(Vue)
之所以把前后端放一块,是因为前后端的很多设置是可变的,必须配套统一才能正常交互,比如加密策略,这里后端使用了 C1C2C3,则前端也必须使用对应的策略,另外前后端的加解密工具类还因为默认的密钥头不同而需要差异化调整。
前端引入依赖:
npm install --save sm-crypto
因为前端vue基本上使用统一的request.js,在里边的 interceptor 内处理相关参数的加解密比较简单,这里不再赘述,只把容易出问题的前端加解密方法做封装展示
前端统一加解密工具类:
const sm2 = require('sm-crypto').sm2 // 获取sm2对象
const cipherMode = 0 // 选择加密策略,1 - C1C3C2,0 - C1C2C3,默认为1
const sysPublicKey = '你对应的后台的公钥' // 系统后台公钥
const uiPrivateKey = '你自己前端的私钥' // 前端UI私钥
/**
* SM2加密string数据
* @param {string} data 原始数据
* @returns {string} 加密后数据
*/
export function getSm2DataHexByString(data) {
if (data && (typeof data === 'string') && data.constructor === String) {
return '04' + sm2.doEncrypt(data, sysPublicKey, cipherMode)
}
return null
}
/**
* SM2加密object数据
* @param {Object} data 原始数据
* @returns {string} 加密后数据
*/
export function getSm2DataHexByObject(data) {
if (data) {
return '04' + sm2.doEncrypt(JSON.stringify(data), sysPublicKey, cipherMode)
}
return null
}
/**
* SM2解密数据
* @param {string} dataHex 原始加密数据
* @returns {string} 解密后数据
*/
export function getSm2DataByString(dataHex) {
if (dataHex && (typeof dataHex === 'string') && dataHex.constructor === String) {
dataHex = dataHex.substring(2).toLocaleLowerCase()
return sm2.doDecrypt(dataHex, uiPrivateKey, cipherMode)
}
}
至此,前后端国密SM2进行数据加密传输完毕,大多数依赖都是通用依赖,如果还有哪里依赖报红,那就哪里报红搜哪里吧,毕竟咱们面向百度编程嘛(手动滑稽)!
更多推荐
所有评论(0)