问题描述:
最近项目代码过三方测试(国企项目),在一系列代码扫描审计检查下,代码发现一部分修改,例如请求参数发生了编码/加密,导致后台需要对请求的参数进行解码/解密,后端那么接口,总不能挨个,挨个的去修改。
由于之前项目中,已经用了AOP进行代码日志的记录,日志记录如下
原本代码的核心逻辑如下:因此想着,既然这里已经拿到请求参数了,直接在这里统一解码/加密,就不用对每个接口解码了
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
methodApiOperation = ms.getMethod().getDeclaredAnnotation(ApiOperation.class);
if (methodApiOperation != null) {
apiOperationDes = methodApiOperation.value();
}
logger.info("start-->请求{}模块的[{}]服务",apiDes, apiOperationDes);
logger.info(" 请求地址:{}",url);
logger.info(" 请求方法:{}.{}", abbreviateName(joinPoint.getSignature().getDeclaringTypeName()), methodName);
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
logger.info(" 请求参数{}:{}",i+1,JsonUtils.javaBeanToString(args[i]));
}
}
修改思路1(利用Json格式化)(失败)
这里的请求参数是args 是Object类型,但是原始方法的请求类型肯定是各种自定的VO类
以解码html为例,我这里首先将Object[]挨个元素转成字符串,然后对整个字符串做html解码,将加码后的字符串,在创建一个新的对象赋joinPoint.getArgs的参数。结果发现修改并没有成功。
DecodeURL decodeURL = ms.getMethod().getDeclaredAnnotation(DecodeURL.class);
if(decodeURL != null) {
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
String decode = StringEscapeUtils.unescapeHtml(JsonUtils.javaBeanToString(args[i]));
logger.info(" 解码Html请求参数{}",decode);
//修改请求入参
joinPoint.getArgs()[i] = JsonUtils.stringToJavaBean(decode, args[i].getClass());
}
}
}
代码执行逻辑是:
- 切面中记录请求参数-----》原始文本
- 切面中修改请求参数-----》修改后的文本
- 实际请求的controller-----》原始文本(也就是修改没有生效)
- 切面中调用请求参数-----》修改后的文本
修改思路2(原始对象Set值)(有效,但没意义)
思路1中,我们是重新创建一个请求入参,然后把新的请求入参赋值给原始请求入参(Json格式化返回新的对象)
思路2,我们直接在原始的对象进行set值
DecodeURL decodeURL = ms.getMethod().getDeclaredAnnotation(DecodeURL.class);
if(decodeURL != null) {
for (int i = 0; i < args.length; i++) {
/*if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
String decode = StringEscapeUtils.unescapeHtml(JsonUtils.javaBeanToString(args[i]));
logger.info(" 解码Html请求参数{}",decode);
//修改请求入参
joinPoint.getArgs()[i] = JsonUtils.stringToJavaBean(decode, args[i].getClass());
}*/
if( (args[i] instanceof QueryResult)) {
QueryResult query = (QueryResult)args[i];
//修改请求入参
query.setName(StringEscapeUtils.unescapeHtml(query.getName()));
}
}
}
此时查看实际的请求接口,发现值真的被修改了。
但是这里有个问题,上诉我是通过直接指定类型,然后强转类型,接着调用原对象的set方法,这是是我已知具体类型,具体字段,这样修改,我还不如直接找到原始的接口,在原来的接口里面修改。
AOP
QueryResult query = (QueryResult)args[i];
query.setName(StringEscapeUtils.unescapeHtml(query.getName()));
实际接口
@GetMapping
@DecodeURL
public void exportNxauto(HttpServletResponse response, QueryResult queryResult) {
queryResult.setName(StringEscapeUtils.unescapeHtml(queryResult.getName()));
}
修改思路3(反射)
总结下思路1,思路1不用类型转换,也不用指定属性,格式化整个Json,然后对整个Json进行中文解码,但是转Json以后,导致重新创建了一个对象。思路2里面虽然没有创建新的对象,但是需要我们强制转化为某个类型,然后调用某个方式,实际请用场景,每个接口的入参的类型都不一样,具体是那个参数需要解码,所以也不知道调用那个Set方法。
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
logger.info(" 请求参数{}:{}",i+1,JsonUtils.javaBeanToString(args[i]));
}
}
ecodeURL decodeURL = ms.getMethod().getDeclaredAnnotation(DecodeURL.class);
if(decodeURL != null) {
for (int i = 0; i < args.length; i++) {
/*if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
String decode = StringEscapeUtils.unescapeHtml(JsonUtils.javaBeanToString(args[i]));
logger.info(" 解码Html请求参数{}",decode);
//修改请求入参 失败:这里创建了一个新的对象,原始对象没有修改,修改的是新的对象。
joinPoint.getArgs()[i] = JsonUtils.stringToJavaBean(decode, args[i].getClass());
}*/
/*if( (args[i] instanceof QueryResult)) {
QueryResult query = (QueryResult)args[i];
//修改请求入参 修改成功,但是太过于狭义,需要知道类型和具体的属性,然后调用Set方法
query.setName();
}*/
if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
Class<? extends Object> classz = args[i].getClass();
//使用反射改成功
for (Field field : classz.getDeclaredFields()) {
if(field.getType() == String.class) {
ReflectionUtils.makeAccessible(field);
Object value = field.get(args[i]);
if(value != null) {
ReflectionUtils.setField(field, args[i], StringEscapeUtils.unescapeHtml(value.toString()));
}
}else if (Collection.class.isAssignableFrom(field.getType())) {
// 字段是集合类型
}else if (List.class.isAssignableFrom(field.getType())) {
// 字段是List类型
}else if (field.getType().isArray()) {
// 字段是数组类型
}else if (field.getType().isPrimitive()) {
// 字段是基本类型
}
}
}
}
}
接下来,我们看下运行的日志,可以看到,在我们的实际controller接口中,可以看到字符串类型的已经被html进行解码。