自定义注解+拦截器实现参数解密
1、首先定义自定义注解类
// 定义注解使用的位置方法和参数上,保留到运行时期
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CipherParam {
/**
* 入参是否解密,默认解密
*/
boolean inDecode() default true;
}
2、定义RequestBodyAdvice实现请求前的解密操作
/**
* @Description 拦截controller层方法,根据自定义注解CipherParam配置,做入参解密处理
*/
@Component
//basePackages 需要拦截的包
@ControllerAdvice(basePackages = { "com.*.*"})
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {
private static final Logger LOG = LoggerFactory.getLogger(DecodeRequestBodyAdvice.class);
@Autowired
private RsaUtils rsaUtils;
@Value("")
private String dev;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class extends HttpMessageConverter>> converterType) {
return true;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage request, MethodParameter parameter, Type targetType,
Class extends HttpMessageConverter>> converterType) {
return body;
}
/**
* 入参解密
*/
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type,
Class extends HttpMessageConverter>> aClass) {
//获取自定义注解
CipherParam cipherParam = methodParameter.getMethodAnnotation(CipherParam.class);
// controller方法没配置加解密
if (cipherParam == null) {
return inputMessage;
}
if (cipherParam.inDecode() == false) {
return inputMessage;
}
try {
LOG.info("对方法 :【" + methodParameter.getMethod().getName() + "】入参进行解密");
return new MyHttpInputMessage(inputMessage);
} catch (Exception e) {
return inputMessage;
}
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class extends HttpMessageConverter>> converterType) {
return body;
}
class MyHttpInputMessage implements HttpInputMessage {
private HttpHeaders headers;
private InputStream body;
public MyHttpInputMessage(HttpInputMessage inputMessage) throws Exception {
this.headers = inputMessage.getHeaders();
String data = IOUtils.toString(inputMessage.getBody(), "UTF-8");
//使用工具类实现解密 --------------- rsaUtils.decrypt(data) ----------------------
this.body = IOUtils.toInputStream(rsaUtils.decrypt(data), "UTF-8");
}
@Override
public InputStream getBody() throws IOException {
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}
}
加在方法上的注解
升级解密(单个属性加解密)
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CipherField {
/**
* 入参是否解密,默认解密
*/
boolean inDecode() default true;
/**
* 对象
*/
Class> clazz();
/**
* 对象类型
*/
BodyType bodyType() default BodyType.OBJECT;
/**
* 请求对象类型
* @author FeiFei.Huang
* @date 2022年06月14日
*/
enum BodyType {
/**
* 对象
*/
OBJECT,
/**
* 数据
*/
ARRAY,
;
}
}
加在字段上的注解
/**
* 字段解密
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CipherField {
/**
* 入参是否解密,默认解密
*/
boolean inDecode() default true;
/**
* 对象
*/
Class> clazz();
/**
* 对象类型
*/
BodyType bodyType() default BodyType.OBJECT;
enum BodyType {
/**
* 对象 就是遍历字段寻找有自定义注解的字段去解密--适用于几个字段加密
*/
OBJECT,
/**
* 数据 就是整个实体类加密
*/
ARRAY,
;
}
}
拦截方法
@Slf4j
@Component
@ControllerAdvice(basePackages = { "com.*.*"})
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {
@Value("${spring.profiles.active}")
private String dev;
@Value("${crypt.rsa.privateKey}")
private String privateKey;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class extends HttpMessageConverter>> converterType) {
return true;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage request, MethodParameter parameter, Type targetType,
Class extends HttpMessageConverter>> converterType) {
return body;
}
/**
* 入参解密
*/
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type,
Class extends HttpMessageConverter>> aClass) {
CipherType cipherType = this.getCipherType(methodParameter);
if (cipherType == null) {
return inputMessage;
}
if (!this.isDecode(inputMessage)) {
return inputMessage;
}
log.info("对方法 :【" + methodParameter.getMethod().getName() + "】入参进行解密");
try {
switch (cipherType) {
case BODY:
return new BodyInputMessage(inputMessage, privateKey);
case FIELD:
return new FieldInputMessage(inputMessage, methodParameter, privateKey);
default:
return inputMessage;
}
} catch (Exception e) {
log.error("接口 :【" + methodParameter.getMethod().getName() + "】入参解密出现异常", e);
return inputMessage;
}
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class extends HttpMessageConverter>> converterType) {
return body;
}
/**
* 获取类型
*
* @param methodParameter methodParameter
* @return CipherType
* @author FeiFei.Huang
* @date 2022年06月14日
*/
private CipherType getCipherType(MethodParameter methodParameter) {
CipherParam cipherParam = methodParameter.getMethodAnnotation(CipherParam.class);
if (cipherParam != null) {
return CipherType.BODY;
}
CipherField cipherField = methodParameter.getMethodAnnotation(CipherField.class);
if (cipherField != null) {
return CipherType.FIELD;
}
return null;
}
/**
* 是否需要解密
*
* @param inputMessage inputMessage
* @return true=表示要解密, false=表示不需要解密
* @author FeiFei.Huang
* @date 2022年06月15日
*/
private boolean isDecode(HttpInputMessage inputMessage) {
boolean isDev = CommonConstant.DEV.equals(dev) || CommonConstant.TEST.equals(dev);
if (!isDev) {
return true;
}
HttpHeaders headers = inputMessage.getHeaders();
List inDecodes = headers.get("in_decode");
if (!CollectionUtils.isEmpty(inDecodes)) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
}
展示其中一个解密方式
package com.loan.app.intercepter.support;
import com.alibaba.fastjson.JSONObject;
import com.loan.app.intercepter.DecodeInputMessage;
import com.loan.business.utils.RsaUtils;
import com.loan.common.annotation.CipherField;
import com.loan.common.annotation.FieldEncrypt;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import java.io.InputStream;
import java.lang.reflect.Field;
/**
* 请求参数
*/
@Slf4j
public class FieldInputMessage extends DecodeInputMessage implements HttpInputMessage {
private static final Logger LOG = LoggerFactory.getLogger(FieldInputMessage.class);
public FieldInputMessage(HttpInputMessage inputMessage, MethodParameter methodParameter, String privateKey) throws Exception {
super(inputMessage);
CipherField cipherField = methodParameter.getMethodAnnotation(CipherField.class);
String inputStream = this.getInputStream();
LOG.info("接收入参解密 data:{}", inputStream);
String decrypt = this.decodeMessage(inputStream, privateKey, cipherField);
this.body = this.setInputStream(decrypt);
}
@Override
public InputStream getBody() {
return this.body;
}
@Override
public HttpHeaders getHeaders() {
return this.headers;
}
/**
* 解密消息
*
* @param inputStream 请求体
* @param privateKey 私钥
* @param cipherField cipherField
* @return 解密后的消息
*/
private String decodeMessage(String inputStream, String privateKey, CipherField cipherField) throws Exception {
Object objBody = null;
if (cipherField.bodyType() == CipherField.BodyType.ARRAY) {
objBody = JSONObject.parseArray(inputStream, cipherField.clazz());
} else {
objBody = JSONObject.parseObject(inputStream, cipherField.clazz());
}
Field[] allFields = objBody.getClass().getDeclaredFields();
for (int i = 0; i < allFields.length; i++) {
Field field = allFields[i];
if (!field.isAnnotationPresent(FieldEncrypt.class)) {
continue;
}
field.setAccessible(true);
String value = field.get(objBody).toString();
if (StringUtils.isBlank(value)) {
continue;
}
String decrypt = RsaUtils.decrypt(value, privateKey);
if (decrypt.startsWith("\"")){
decrypt = decrypt.replaceAll("\"","");
}
field.set(objBody, decrypt);
}
return JSONObject.toJSONString(objBody);
}
}
使用演示
//注解默认是对字段解密
@CipherField(clazz = H5RegisterDTO.class)
@PostMapping("/registerAndApplyV2")
public GlobalResponseEntity registerAndApplyV2(@Valid @RequestBody H5RegisterDTO paramsDTO) {
//部分解密演示
}
实体类 只需要对手机号和身份证加密,名称不加密
@Data
public class H5RegisterDTO {
/**
* 手机号
*/
@FieldEncrypt(algorithm = EncryptAlgorithm.RSA)
private String mobile;
/**
* 用户名称
*/
private String userName;
/**
* 身份证
*/
@FieldEncrypt(algorithm = EncryptAlgorithm.RSA)
private String idCard;