1.Mavne导入加密解密所需的依赖

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-io</artifactId>
  <version>1.3.2</version>
</dependency>

2.在aspect切面包下建立 入参切面与出参切面

DecodeRequestBodyAdvice.class  入参拦截 
流程:获取本次请求->判断是否包含加密/解密注解->对请求字符串解密->重构请求过来的加密字符串为实体类对象
package com.iliebe.web.aspect;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.icbc.api.internal.util.codec.Base64;
import com.iliebe.web.annotation.SecurityParameter;
import com.iliebe.web.constants.SystemConstant;
import com.iliebe.web.util.EncryptForPKCS7Util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.apache.commons.io.IOUtils; import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.security.NoSuchAlgorithmException; /**
* @Date: 2020/6/27 下午3:59
* @Description: TODO(请求参数加密)
*/
@Slf4j
@ControllerAdvice(basePackages = "com.iliebe.web.controller")
public class DecodeRequestBodyAdvice implements RequestBodyAdvice { @Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
} @Override
public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
} @Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
try {
boolean encode = false;
String apiPath = "";
// 方法是否 包含注解
if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
//获取注解配置的包含和去除字段
SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
//入参是否需要解密
encode = serializedField.inDecode();
apiPath = serializedField.apiPath();
}
if (encode) {
return new MyHttpInputMessage(inputMessage,apiPath);
} else {
return inputMessage;
}
} catch (Exception e) {
e.printStackTrace();
log.error("方法method :【{}】参数解密出现异常:{}", methodParameter.getMethod().getName(), e.getMessage());
return inputMessage;
}
} @Override
public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return body;
} class MyHttpInputMessage implements HttpInputMessage { private HttpHeaders headers; private InputStream body; public MyHttpInputMessage(HttpInputMessage inputMessage,String apiPath) throws Exception {
this.headers = inputMessage.getHeaders();
// 获取加密 json中 值
String src = easpString(IOUtils.toString(inputMessage.getBody(), "UTF-8"));
// 解密加密字符串
String decryptStr = null;
if (StringUtils.isBlank(apiPath)){
decryptStr = EncryptForPKCS7Util.Decrypt(src, SystemConstant.AES_KEY);
}
if (StringUtils.isBlank(decryptStr)){
throw new RuntimeException("参数【requestData】解密异常!");
}
this.body = IOUtils.toInputStream(decryptStr, "UTF-8");
} @Override
public InputStream getBody() {
return body;
} @Override
public HttpHeaders getHeaders() {
return headers;
} /**
* 获取requestData数据
*/
public String easpString(String requestData) {
if (StrUtil.isNotEmpty(requestData)) {
JSONObject jsonObject = new JSONObject(requestData);
String requestDataStr = jsonObject.getStr("requestData");
if (StrUtil.isBlank(requestDataStr)) {
throw new RuntimeException("参数【requestData】缺失异常!");
}
return requestDataStr;
}
return "";
}
} public static String genAesSecret(){
try {
KeyGenerator kg = KeyGenerator.getInstance("AES");
//下面调用方法的参数决定了生成密钥的长度,可以修改为128, 192或256
kg.init(128);
SecretKey sk = kg.generateKey();
byte[] b = sk.getEncoded();
String secret = Base64.encodeBase64String(b);
return secret;
}
catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("没有此算法");
}
} public static void main(String[] args) {
System.out.println(EncryptForPKCS7Util.Decrypt("jH+iZFzwlWyStzAfHaLW4eQ2MoStfSAg0hsXk6miSvfm7C+oRED1zSI1uT0wkYbX4Q0YeVqA7JM/kxC0kSrOJA3v3mByGBo8DmlS349DvpY=", "Q6Oxh8pknkr3/B+0ukKVqg=="));
} }
EncodeResponseBodyAdvice.class 出参拦截
流程:获取本次响应->判断是否包含加密/解密注解->对响应数据加密->返回响应数据为加密后的字符串
package com.iliebe.web.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.iliebe.web.annotation.SecurityParameter;
import com.iliebe.web.constants.SystemConstant;
import com.iliebe.web.util.EncryptForPKCS7Util;
import com.iliebe.web.util.JsonConfig;
import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
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; /**
* @Date: 2020/6/27 下午3:59
* @Description: TODO(返回数据解密)
*/
@Slf4j
@ControllerAdvice(basePackages = "com.iliebe.web.controller")
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice { @Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
} @Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
boolean encode = false;
if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
//获取注解配置的包含和去除字段
SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
//出参是否需要加密
encode = serializedField.outEncode();
}
if (encode) {
ObjectMapper objectMapper = JsonConfig.getObjectMapper();
try {
String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
return EncryptForPKCS7Util.Encrypt(result, SystemConstant.AES_KEY);
} catch (Exception e) {
e.printStackTrace();
log.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());
}
}
return body;
}
}

3.加密注解,加在controller层中需要加密/解密的接口

SecurityParameter 
package com.iliebe.web.annotation;

import org.springframework.web.bind.annotation.Mapping;

import java.lang.annotation.*;

/**
* @Date: 2020/6/27 下午3:59
* @Description: TODO(参数加密注解)
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface SecurityParameter { /**
* 入参是否解密,默认解密
*/
boolean inDecode() default true; /**
* 出参是否加密,默认加密
*/
boolean outEncode() default true; /**
* 方法路径名
*/
String apiPath() default "";
}

4.配置类 配置Json数据编码为UTF-8

package com.iliebe.web.util;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Configuration; import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; /**
* 配置返回的json字符 key为字符串时null则不返回
*
* @Date: 2019/5/13 13:45
*/
@Configuration
public class JsonConfig { /**
* 网络层需要标识gbk,utf-8
* @return
*/
public static ObjectMapper getObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
JavaTimeModule timeModule = new JavaTimeModule();
timeModule.addDeserializer(LocalDate.class,
new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
timeModule.addSerializer(LocalDate.class,
new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(timeModule);
return objectMapper;
}
}

5.加密/解密方法

package com.iliebe.web.util;

import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Base64; /**
* @Date: 2020/6/27 下午3:59
* @Description: TODO()
*/
public class EncryptForPKCS7Util { private static final String SECRET = "AES";
private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS7Padding"; static {
Security.addProvider(new BouncyCastleProvider());
}

/**
* AES加密ECB模式PKCS7Padding填充方式
*
* @param str 字符串
* @param key 密钥
* @return 加密字符串
* @throws Exception 异常信息
*/
public static String Encrypt(String str, String key) {
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, SECRET));
byte[] doFinal = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
return new String(Base64.getEncoder().encode(doFinal));
} catch (Exception e) {
e.printStackTrace();
}
return "";
} /**
* AES解密ECB模式PKCS7Padding填充方式
*
* @param str 字符串
* @param key 密钥
* @return 解密字符串
* @throws Exception 异常信息
*/
public static String Decrypt(String str, String key) {
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, SECRET));
byte[] doFinal = cipher.doFinal(Base64.getDecoder().decode(str));
return new String(doFinal);
} catch (Exception e) {
e.printStackTrace();
}
return "";
} }

注意事项:

在Web项目中如果使用了全局异常拦截器,需要在全局异常捕获的地方也处理,如果是只针对某端的请求处理,可以通过请求header中加入标识,再根据标识判断是否加密/解密。

基于Spring AOP切面实现请求入参出参加解密的更多相关文章

  1. Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志

    其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...

  2. 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版

    欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...

  3. Spring AOP 切面编程记录日志和接口执行时间

    最近客户现在提出系统访问非常慢,需要优化提升访问速度,在排查了nginx.tomcat内存和服务器负载之后,判断是数据库查询速度慢,进一步排查发现是因为部分视图和表查询特别慢导致了整个系统的响应时间特 ...

  4. 使用Spring AOP切面解决数据库读写分离

    http://blog.jobbole.com/103496/ 为了减轻数据库的压力,一般会使用数据库主从(master/slave)的方式,但是这种方式会给应用程序带来一定的麻烦,比如说,应用程序如 ...

  5. 利用Spring AOP切面对用户访问进行监控

    开发系统时往往需要考虑记录用户访问系统查询了那些数据.进行了什么操作,尤其是访问重要的数据和执行重要的操作的时候将数记录下来尤显的有意义.有了这些用户行为数据,事后可以以用户为条件对用户在系统的访问和 ...

  6. Spring AOP切面的时候参数的传递

    Spring AOP切面的时候参数的传递 Xml: <?xml version="1.0" encoding="UTF-8"?> <beans ...

  7. spring AOP(切面) 表达式介绍

    在 spring AOP(切面) 例子基础上对表达式进行介绍 1.添加接口删除方法 2.接口实现类 UserDaoServer 添加实现接口删除方法 3.测试类调用delUser方法 4. 输出结果截 ...

  8. .NET WebAPI 自定义 NullableConverter 解决请求入参 “”空字符触发转换异常问题

    最近在项目中启用了Nullable 可为空的类型,这个特性确实很好用,在 WebAPI 的入参上可以直接采用 ? 来标记一个字段是否允许为空,但是使用过程中遇到了如下一个问题,比如创建部门接口 我们定 ...

  9. Spring的数据库编程浅入浅出——不吹牛逼不装逼

    Spring的数据库编程浅入浅出——不吹牛逼不装逼 前言 上文书我写了Spring的核心部分控制反转和依赖注入,后来又衔接了注解,在这后面本来是应该写Spring AOP的,但我觉得对于初学者来说,这 ...

  10. Spring AOP 切面编程的方法

    spring aop的使用分为两种,一种是使用注解来实现,一种是使用配置文件来实现. 先来简单的介绍一下这两种方法的实现,接下来详细的介绍各处的知识点便于查阅.目录如下: 1.基于注解实现spring ...

随机推荐

  1. Spark详解(03) - Spark3.0.0运行环境安装

    Spark详解(03) - Spark3.0.0运行环境安装 Spark运行模式 Spark常见部署模式: Local模式:在本地部署单个Spark服务 所谓的Local模式,就是不需要其他任何节点资 ...

  2. Kafka相关面试题及答案

    Kafka相关面试题及答案 1. Kafka中的ISR.AR又代表什么? ISR:与leader保持同步的follower集合 AR:分区的所有副本 2. Kafka中的HW.LEO等分别代表什么? ...

  3. 手撕AVL树(C++)

    阅读本文前,请确保您已经了解了二叉搜索树的相关内容(如定义.增删查改的方法以及效率等).否则,建议您先学习二叉搜索树.本文假定您对二叉搜索树有了足够的了解. 效率? 众所周知,在平衡条件下,对二叉搜索 ...

  4. [C#]关于逆变与协变的基本概念和修饰符in与out的意义

    协变与逆变的概念 假如两个类型X和Y具有特殊关系,X类型的每个值都能转换成Y类型.我们将I<X>向I<Y>的转换称为协变转换.反之我们将I<Y>向I<X> ...

  5. 加速 Document AI (文档智能) 发展

    在企业的数字工作流中充满了各种文档,包括信件.发票.表格.报告.收据等,我们无法自动提取它们的知识.如今随着文本.视觉和多模态人工智能的进步,我们有可能解锁这些知识,这篇文章向你展示了你的团队该如何使 ...

  6. 安装部署Java项目

    开头:之前做了个文档转换的小项目,想部署在安卓手机上,自己可以随时看看,所以才有了下面这篇文章,内容或有瑕疵,望请批正.文末放我自己部署文档转换网址,仅供大家参考,谢谢! 选择:Termux 还是 L ...

  7. py教学之元组

    元组介绍 Python 的元组与列表类似,不同之处在于元组的元素不能修改. 元组使用小括号 ( ),列表使用方括号 [ ]. 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可. tup1 = ...

  8. angular组件共用服务打印日志父子组件传值2创建服务注入

  9. echart大坑总结~~echarts呈现的数据刷新不完全/echarts画图时tooltip不会更新

    谨以此文记录在使用echarts中遇到的各种大坑或者小坑,陆续更新加入新坑 欢迎朋友们一起提坑,我们势必绕过它们,愿我们永不再入坑~ 坑一:echarts图表重新赋值数据,图表显示会存在一部分原来的数 ...

  10. 带你熟悉3种AQS的线程并发工具的用法

    摘要:AQS 的全称为(AbstractQueuedSynchronizer),AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器. 本文分享自华为云社区 ...