基于Spring AOP切面实现请求入参出参加解密
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切面实现请求入参出参加解密的更多相关文章
- Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志
其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...
- 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版
欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...
- Spring AOP 切面编程记录日志和接口执行时间
最近客户现在提出系统访问非常慢,需要优化提升访问速度,在排查了nginx.tomcat内存和服务器负载之后,判断是数据库查询速度慢,进一步排查发现是因为部分视图和表查询特别慢导致了整个系统的响应时间特 ...
- 使用Spring AOP切面解决数据库读写分离
http://blog.jobbole.com/103496/ 为了减轻数据库的压力,一般会使用数据库主从(master/slave)的方式,但是这种方式会给应用程序带来一定的麻烦,比如说,应用程序如 ...
- 利用Spring AOP切面对用户访问进行监控
开发系统时往往需要考虑记录用户访问系统查询了那些数据.进行了什么操作,尤其是访问重要的数据和执行重要的操作的时候将数记录下来尤显的有意义.有了这些用户行为数据,事后可以以用户为条件对用户在系统的访问和 ...
- Spring AOP切面的时候参数的传递
Spring AOP切面的时候参数的传递 Xml: <?xml version="1.0" encoding="UTF-8"?> <beans ...
- spring AOP(切面) 表达式介绍
在 spring AOP(切面) 例子基础上对表达式进行介绍 1.添加接口删除方法 2.接口实现类 UserDaoServer 添加实现接口删除方法 3.测试类调用delUser方法 4. 输出结果截 ...
- .NET WebAPI 自定义 NullableConverter 解决请求入参 “”空字符触发转换异常问题
最近在项目中启用了Nullable 可为空的类型,这个特性确实很好用,在 WebAPI 的入参上可以直接采用 ? 来标记一个字段是否允许为空,但是使用过程中遇到了如下一个问题,比如创建部门接口 我们定 ...
- Spring的数据库编程浅入浅出——不吹牛逼不装逼
Spring的数据库编程浅入浅出——不吹牛逼不装逼 前言 上文书我写了Spring的核心部分控制反转和依赖注入,后来又衔接了注解,在这后面本来是应该写Spring AOP的,但我觉得对于初学者来说,这 ...
- Spring AOP 切面编程的方法
spring aop的使用分为两种,一种是使用注解来实现,一种是使用配置文件来实现. 先来简单的介绍一下这两种方法的实现,接下来详细的介绍各处的知识点便于查阅.目录如下: 1.基于注解实现spring ...
随机推荐
- Hadoop详解(07) - Hdfs数据压缩
Hadoop详解(07) - Hdfs数据压缩 概述 压缩技术能够有效减少底层存储系统(HDFS)读写字节数.压缩提高了网络带宽和磁盘空间的效率.在运行MR程序时,I/O操作.网络数据传输. Shuf ...
- 《Effective C++》再次探索traits技法
首先介绍C++标准程序库中的五种迭代器,关于这个可以看我的另一个笔记:http://blog.csdn.net/m0_37316917/article/details/70053513. 对于这五种分 ...
- java入门与进阶P-6.1+P-6.2
字符类型 字符型char在Java语言中占用 2 个字节,char类型的字面量必须使用半角的单引号括起来,取值范围为[ 0 - 65535 ],char 和 short 都占用 2 个字节,但是 ch ...
- get请求与post请求的区别
大小限制 get请求一般通过url传输的数据量时比较少的,最多传3~5个参数,如果要传递多个参数,要在url地址中利用"&"符号拼接多个参数, 栗子:/test/demo. ...
- Fiddler抓手机APP包
*手机和电脑连接在同一wifi下 *fiddler设置 *启动Fiddler,打开菜单栏中的 Tools > Fiddler Options,打开"Fiddler Options&qu ...
- Vue21 组件
1 模块及组件简介 组件(component)是vue.js最强大的功能之一.组件的作用就是封装可重用的代码,通常一个组件就是一个功能体,便于在多个地方都能够调用这个功能体. 每个组件都是Vue的实例 ...
- springBoot集成flowable
前言 Flowable可以十分灵活地加入你的应用/服务/构架.可以将JAR形式发布的Flowable库加入应用或服务,来嵌入引擎. 以JAR形式发布使Flowable可以轻易加入任何Java环境:Ja ...
- javaEE(网络编程、TCP、线程池优化)
网络编程 Client-Server(CS) Browser/Server(BS) 1.客户端--服务端 安装客户端 更新. 依赖PC 2.浏览器和服务端 分布式 兼容性 一站开发 网络通信: UDP ...
- 2.17 win32 入口 esp寻址 回调函数定位 具体事件定位
wWinMain(In HINSTANCE hInstance, 主函数入口的第一个参数 句柄 通过注释找到获取最后参数的地方 C/C++默认方式,参数从右向左入栈,主调函数负责栈平衡. 由此猜测下面 ...
- 滴水 10/13号完成 打印出DOS PE头 节表 开源
#include<stdio.h> #include<Windows.h> int szie2; #pragma warning(disable : 4996) LPVOID ...