1. 场景
1.要求对接口中部分输入和输出参数进行加解密。如密码要求前端加密后传给后端进行解密。
2.对整个请求头requestBody和返回内容ResponseBody进行加解密,如涉及到金额时,整个请求内容都加密传输。
2.解决方案
为了减少代码的侵入,采用注解形式进行处理。
/**
* 进行参数解密
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DecryptField {
String algorithm() default "AES";
}
/**
* 进行参数加密
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptField {
String algorithm() default "AES";
}
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DecryptRequestBody {
boolean decryptBody() default false;
boolean decryptField() default false;
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EncryptResponseBody {
boolean encryptBody() default false;
boolean encryptField() default false;
}
public class CryptoAnnotationHandler {
private final static Logger logger = LogManager.getLogger(CryptoAnnotationHandler.class);
public static boolean handle(Object obj) {
return handle(obj,EncryptUtils.KEY);
}
@SuppressWarnings("unchecked")
public static boolean handle(Object obj,String key) {
if (obj instanceof Collection) {
Collection<Object> objects = (Collection<Object>) obj;
for (Object object : objects) {
try {
endecryptObject(object,key);
} catch (Exception e1) {
logger.error("加解密出错",e1);
return false;
}
}
} else {
try {
endecryptObject(obj,key);
} catch (Exception e1) {
logger.error("加解密出错",e1);
return false;
}
}
return true;
}
/**
* 只支持最一层会字符串的才可以加解密操作
*
* @param requestObj
* @throws IllegalAccessException
*/
private static void endecryptObject(Object requestObj,String key) throws Exception {
if (Objects.isNull(requestObj)) {
return;
}
Field[] fields = requestObj.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getType().equals(String.class)) {
// 若该字段被EncryptField注解,则进行解密
DecryptField deAnnotation = AnnotationUtils.findAnnotation(field, DecryptField.class);
if (null != deAnnotation) {
// 设置private类型允许访问
field.setAccessible(Boolean.TRUE);
if(AlgorithmEnum.DES.getCode().equals(deAnnotation.algorithm())){
field.set(requestObj, EncryptUtils.decryptByDes((String) field.get(requestObj),key));
}else if(AlgorithmEnum.AES.getCode().equals(deAnnotation.algorithm())){
field.set(requestObj, EncryptUtils.decryptByAes((String) field.get(requestObj),key));
}else{
throw new CryptoException("不支持算法:"+deAnnotation.algorithm());
}
field.setAccessible(Boolean.FALSE);
}
EncryptField enAnnotation = AnnotationUtils.findAnnotation(field, EncryptField.class);
if (null != enAnnotation) {
// 设置private类型允许访问
field.setAccessible(Boolean.TRUE);
if(AlgorithmEnum.DES.getCode().equals(enAnnotation.algorithm())){
field.set(requestObj, EncryptUtils.encryptByDes((String) field.get(requestObj),key));
}else if(AlgorithmEnum.AES.getCode().equals(enAnnotation.algorithm())){
field.set(requestObj, EncryptUtils.encryptByAes((String) field.get(requestObj),key));
}else{
throw new CryptoException("不支持算法:"+enAnnotation.algorithm());
}
field.setAccessible(Boolean.FALSE);
}
}
}
}
}
@RestControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return returnType.hasMethodAnnotation(EncryptResponseBody.class)
&& returnType.hasMethodAnnotation(ResponseBody.class);
}
@Override
public Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
EncryptResponseBody a = returnType.getMethodAnnotation(EncryptResponseBody.class);
if (null != obj) {
try {
if(a.encryptField()){
CryptoAnnotationHandler.handle(obj,Constant.AES_KEY);
}
String content = (objectMapper.writeValueAsString(obj));
if(a.encryptBody()){
content = EncryptUtils.encryptByAes(content,Constant.AES_KEY);
}
if(logger.isDebugEnabled()){
logger.debug("返回加密数据:{}",content);
}
} catch (IOException e) {
throw new CryptoException("加密失败");
}
}
return obj;
}
}
@RestControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return methodParameter.hasParameterAnnotation(DecryptRequestBody.class)
&& methodParameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter,
Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter,
Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
DecryptRequestBody a = methodParameter.getParameterAnnotation(DecryptRequestBody.class);
return new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
String srcReqestBody = IOUtils.toString(httpInputMessage.getBody(), "UTF-8");
if(logger.isDebugEnabled()){
logger.debug("原始加密请求数据:{}",srcReqestBody);
}
if (a.decryptBody()) {
try {
String decrpyt = EncryptUtils.decryptByAes(
srcReqestBody, Constant.AES_KEY);
return new ByteArrayInputStream(decrpyt.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new CryptoException("解密失败");
}
} else {
return new ByteArrayInputStream(srcReqestBody.getBytes(
StandardCharsets.UTF_8));
}
}
@Override
public HttpHeaders getHeaders() {
return httpInputMessage.getHeaders();
}
};
}
@Override
public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter,
Type type, Class<? extends HttpMessageConverter<?>> aClass) {
DecryptRequestBody a = methodParameter.getParameterAnnotation(DecryptRequestBody.class);
if (a.decryptField()) {
boolean r = CryptoAnnotationHandler.handle(o, Constant.AES_KEY);
if (!r) {
throw new CryptoException("解密失败");
}
}
return o;
}
}
参考: https://gitee.com/pengchua/restapi/tree/master/openapi
来源:oschina
链接:https://my.oschina.net/pengchanghua/blog/4324652