目标
本章我们将编写一个starter,目标如下:
1、对外提供@OpenAPI 注解,使用此注解它会对接收的请求数据进行解密,对要返回的数据进行加密。
2、完成服务端使用示例
3、完成前端调用示例
加密规则
1、对业务数据进行AES加密,示意代码:encryptData=AES("业务数据", aesKey)
2、对AES的key进行公钥加密,示意代码:encryptKey=RSA(aesKey, 公钥)
3、签名sign=md5(encryptData+encryptKey)
加密后请求示例
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
请求参数
encryptData: p7gqncRzLliA/u/zY4vXeUn
encryptKey: Moxi6Q570jgW2zE+LEZCONd1Zk=
sign: 47e42d6e6daa68c35f858fd69e1adddf服务端返回示例
-- 未使用@OpenAPI,data为明文的数据
{code: 200, msg: '成功', data: Object}
-- 使用@OpenAPI,data如下
{code: 200, msg: '成功', data: {
encryptData: p7gqncRzOIqUn
encryptKey: Moxi6Q570JvL4RIqzZ5d1Zk=
sign: 47e42d6e6daa68c35f858fd69e1adddf
}}pom.xml
<?xml version="1.0" encoding="UTF-8"?>
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.0.3.RELEASE
com.v5ba
openapi-spring-boot-starter
0.0.1-SNAPSHOT
jar
openapi-spring-boot-starter
1.8
UTF-8
1.8
1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-aop
cn.hutool
hutool-all
5.8.3
定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OpenAPI {
}配置公私钥
@ConfigurationProperties("com.v5ba.common.openapi")
public class OpenAPIProperties {
private String privateKey;
private String publicKey;
//... get and set ...
}具体代码逻辑
public class OpenAPIAdvice implements MethodBeforeAdvice, AfterReturningAdvice {
private OpenAPIProperties openAPIProperties;
public OpenAPIAdvice(OpenAPIProperties openAPIProperties) {
this.openAPIProperties = openAPIProperties;
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
if (returnValue instanceof ResponseVO) {
ResponseVO responseVO = (ResponseVO)returnValue;
Object obj = responseVO.getData();
if (obj != null) {
// 加密数据
byte[] keyByte = GengerCode.getCode(16).getBytes();
AES aes = new AES(Mode.ECB, Padding.ISO10126Padding, keyByte);
EncryptVO encryptVO = new EncryptVO();
encryptVO.setEncryptData(aes.encryptBase64(JSONUtil.toJsonStr(obj)));
// 加密key
RSA rsa = SecureUtil.rsa(null, openAPIProperties.getPublicKey());
encryptVO.setEncryptKey(rsa.encryptBase64(keyByte, KeyType.PublicKey));
// 签名
encryptVO.setSign(SecureUtil.md5(encryptVO.getEncryptData() + encryptVO.getEncryptKey()));
responseVO.setData(encryptVO);
}
}
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// 解密
Map requestParam = decryptRequest(attr.getRequest());
resetParam(method, args, requestParam);
}
/**
* 初始化请求参数
*
* 将解密后的参数,设置到入参的对应属性中
* @param method
* @param args
* @param requestParam 解密后的参数
*/
private void resetParam(Method method, Object[] args, Map requestParam) {
Parameter[] parameters = method.getParameters();
Class<?>[] paramClass = method.getParameterTypes();
for (int index = 0; index < paramClass.length; index++) {
Class<?> clazz = paramClass[index];
Parameter paramObj = parameters[index];
Annotation[] annotations = paramObj.getAnnotations();
if (annotations.length != 0) {
continue;
}
if (clazz == HttpServletRequest.class || clazz == HttpServletResponse.class) {
continue;
}
if (ObjectUtil.isBaseType(clazz)) {
String attrName = paramObj.getName();
Object attrVal = requestParam.get(attrName);
args[index] = ObjectUtil.getBaseTypeVal(attrVal, clazz);
} else {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
field.set(args[index], ObjectUtil.getBaseTypeVal(requestParam.get(field.getName()), field.getType()));
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("参数:"+field.getName()+"设值失败");
}
}
}
}
}
/**
* 将请求参数解密
* @param request
* @return
*/
private Map decryptRequest(HttpServletRequest request){
String sign = request.getParameter("sign");
String encryptData = request.getParameter("encryptData");
String encryptKey = request.getParameter("encryptKey");
// 检查签名
String signParamContent = encryptData + encryptKey;
String signStr = SecureUtil.md5(signParamContent);
if (signStr == null || !signStr.equals(sign)){
throw new RuntimeException("验签失败");
}
// 解密key 获取AES密钥
byte[] keyByte = null;
try {
RSA rsa = SecureUtil.rsa(openAPIProperties.getPrivateKey(), null);
keyByte = rsa.decrypt(encryptKey, KeyType.PrivateKey);
}catch (Exception e){
throw new RuntimeException("参数错误,解密key失败");
}
// 解密encrypt
try {
AES aes = new AES(Mode.ECB, Padding.ISO10126Padding, keyByte);
String _data = aes.decryptStr(encryptData);
return JSONUtil.toBean(_data, Map.class);
}catch (Exception e){
throw new RuntimeException( "参数错误,解密encrypt失败");
}
}
}
public class ObjectUtil {
public static boolean isBaseType(Class clazz) {
if (clazz == String.class || clazz == Byte.class || clazz == Short.class
|| clazz == Integer.class ||clazz == Long.class || clazz == Double.class) {
return true;
}
return false;
}
public static T getBaseTypeVal(Object attrVal, Class clazz) {
if (attrVal == null) {
return null;
}
T t = null;
if (clazz == String.class) {
t = (T) attrVal.toString();
} else if (clazz == Byte.class) {
t = (T) Byte.valueOf(attrVal.toString());
} else if (clazz == Short.class) {
t = (T) Short.valueOf(attrVal.toString());
} else if (clazz == Integer.class) {
t = (T) Integer.valueOf(attrVal.toString());
} else if (clazz == Long.class) {
t = (T) Long.valueOf(attrVal.toString());
} else if (clazz == Double.class) {
t = (T) Double.valueOf(attrVal.toString());
}
return t;
}
} 定义切面和自动装配
@Configuration
public class OpenAPIConfiguration {
public static final String traceExecution = "@annotation(com.v5ba.common.openapi.OpenAPI)";
@Bean
public OpenAPIProperties getOpenAPIProperties(){
return new OpenAPIProperties();
}
@Bean
public DefaultPointcutAdvisor openAPIPointcutAdvisor(OpenAPIProperties openAPIProperties) {
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(traceExecution);
advisor.setPointcut(pointcut);
advisor.setAdvice(new OpenAPIAdvice(openAPIProperties));
return advisor;
}
}spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.v5ba.common.openapi.OpenAPIConfiguration服务端修改
在业务项目中引入jar 包
com.v5ba
openapi-spring-boot-starter
0.0.1-SNAPSHOT
配置公私钥
com:
v5ba:
common:
openapi:
privateKey: 设置你的私钥
publicKey: 设置你的公钥改动的地方很少,只需要在原接口增加@OpenAPI注解即可
@RestController
public class TestController {
@OpenAPI
@PostMapping("get")
public ResponseVO get(String name, User user) throws BaseException {
return ResponseVO.of();
}
}前端修改
修改前代码
//let param = {name: '张三'}}
post: (param) {
Http.axios.post(url, Qs.stringify(param), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
})
.then(resp => {
if(!resp || resp.status != "200"){
alert("网络异常")
return false;
}
const respData = resp.data;
if (respData.code == '200') {
let d = respData.data // 这个就是返回的明文数据
}
})
.catch(error => {
})
},修改后要先对参数加密,然后对返回数据解密
//let param = {name: '张三'}}
post: (param) {
// 将请求参数加密
let encryptParam = beforePostEncrypt(param)
Http.axios.post(url, Qs.stringify(encryptParam), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
})
.then(resp => {
if(!resp || resp.status != "200"){
alert("网络异常")
return false;
}
const respData = resp.data;
if (respData.code == '200') {
// param是解密后的明文数据
let param = afterPostEncrypt(respData.data)
}
})
.catch(error => {
})
},
beforePostEncrypt: (param) => {
let reqParams = {
encryptData: "", // AES加密param
encryptKey: ""
}
// console.log("param 加密前:"+JSON.stringify(param))
// 业务数据加密
let keyStr = Util.randomStr(16);
reqParams.encryptData = Encrypt.encryptAES(JSON.stringify(param), keyStr);
// 密钥加密
reqParams.encryptKey = Encrypt.encryptRSA(keyStr)
// 请求签名
reqParams.sign = Encrypt.md5Str(reqParams.encryptData+reqParams.encryptKey)
return reqParams;
},
afterPostEncrypt: (result) => {
let response = undefined
if (!result) {
return response;
}
const sign = Encrypt.md5Str(result.encryptData+result.encryptKey)
if (sign != result.sign){
alert("验签失败")
return response;
}
const key = Encrypt.decryptRSA(result.encryptKey);
const encryptStr = Encrypt.decryptAESFromBase64(result.encryptData, key);
if(!encryptStr){
alert("未找到响应数据")
return response;
}
response = JSON.parse(encryptStr)
return response
}安装两个加密库
npm install jsencrypt
npm install crypto-js封装RSA、AES和Base64加解密
import JsEncrypt from 'jsencrypt'
import Base64 from 'crypto-js/enc-base64';
import AES from 'crypto-js/aes';
import MD5 from 'crypto-js/md5';
import ENC_UTF8 from 'crypto-js/enc-utf8'
// 加密模式
import MODE from 'crypto-js/mode-ecb'
// 填充方式
import PAD_PKCS from 'crypto-js/pad-iso10126'
const _privKey = '私钥';
const _pubKey = '公钥';
let Encrypt = {
/**
* 加密
* return string
*/
encryptRSA: function (str) {
// eslint-disable-next-line
let jse = new JSEncrypt()
jse.setPublicKey(_pubKey)
return jse.encrypt(str)
},
/**
* 解密
* return {}
*/
decryptRSA: function (str) {
// eslint-disable-next-line
let jse = new JSEncrypt()
jse.setPrivateKey(_privKey)
return jse.decrypt(str)
},
md5Str: function (str) {
return MD5(str).toString()
},
encryptAES: function (str, key) {
return AES.encrypt(ENC_UTF8.parse(str), ENC_UTF8.parse(key), {
// iv: Encrypt.aesIv,
mode: MODE,
padding: PAD_PKCS
}).toString()
},
/**
* 解密
* return {}
*/
decryptAESFromBase64: function (base64Str, key) {
return AES.decrypt(base64Str, ENC_UTF8.parse(key), {
// iv: Encrypt.aesIv,
mode: MODE,
padding: PAD_PKCS
}).toString(ENC_UTF8)
},
/**
* 加密base64
* @param {} str
*/
encryptBase64: function(str){
return Base64.stringify(str)
},
/**
* 解密base64
* @param {*} str
*/
decryptBase64: function(str){
return Base64.parse(str).toString()
}
};
export default Encrypt; | 留言与评论(共有 0 条评论) “” |