一、对全局返回json加密
1、了解HttpMessageConverter接口
HttpMessageConverter接口提供了 5 个方法:
canRead:判断该转换器是否能将请求内容转换成 Java 对象
canWrite:判断该转换器是否可以将 Java 对象转换成返回内容
getSupportedMediaTypes:获得该转换器支持的 MediaType 类型
read:读取请求内容并转换成 Java 对象
write:将 Java 对象转换后写入返回内容
当返回参数使用@ResponseBody或者@RestController时,返回对象是使用HttpMessageConverter接口体系将其转成json输出。加密思路继承重写相关类的方法加密json输出即可
@RequestBody
作用:
i) 该注解用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上;
ii) 再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上。
使用时机:
A) GET、POST方式提时, 根据request header Content-Type的值来判断:
application/x-www-form-urlencoded, 可选(即非必须,因为这种情况的数据@RequestParam, @ModelAttribute也可以处理,当然@RequestBody也能处理);
multipart/form-data, 不能处理(即使用@RequestBody不能处理这种格式的数据);
其他格式, 必须(其他格式包括application/json, application/xml等。这些格式的数据,必须使用@RequestBody来处理);
B) PUT方式提交时, 根据request header Content-Type的值来判断:
application/x-www-form-urlencoded, 必须;
multipart/form-data, 不能处理;
其他格式, 必须;
说明:request的body部分的数据编码格式由header部分的Content-Type指定;
@ResponseBody
作用:
该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。
使用时机:
返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;
ByteArrayHttpMessageConverter: 负责读取二进制格式的数据和写出二进制格式的数据;
StringHttpMessageConverter: 负责读取字符串格式的数据和写出二进制格式的数据;
ResourceHttpMessageConverter:负责读取资源文件和写出资源文件数据;
FormHttpMessageConverter: 负责读取form提交的数据(能读取的数据格式为 application/x-www-form-urlencoded,不能读取multipart/form-data格式数据);负责写入application/x-www-from-urlencoded和multipart/form-data格式的数据;
MappingJacksonHttpMessageConverter: 负责读取和写入json格式的数据;
SouceHttpMessageConverter: 负责读取和写入 xml 中javax.xml.transform.Source定义的数据;
Jaxb2RootElementHttpMessageConverter: 负责读取和写入xml 标签格式的数据;
AtomFeedHttpMessageConverter: 负责读取和写入Atom格式的数据;
RssChannelHttpMessageConverter: 负责读取和写入RSS格式的数据;
当使用@RequestBody和@ResponseBody注解时,RequestMappingHandlerAdapter就使用它们来进行读取或者写入相应格式的数据。
2、加密思路
(1)返回json时使用的是MappingJackson2HttpMessageConverter类,我们继承该类重写
writeInternal:方法,将自定义的HttpMessageConverter增加到配置中
加密后输出
(2)本来参数解密也想用重写HttpMessageConverter中read方法,结果经过不断的尝试发现有点行不通。
一个原因是参数必须添加@RequestBody注解才会只用HttpMessageConverter解析参数,项目中所有的接口参数都没有加该注解修改会很麻烦
第二个人原因是:增加该注解后不同的请求content-type,以及参数类型,所用的解析器不是同一个,解密重写不知道具体重写哪一个解析器相对比较麻烦。
web conf
package com.ruanmeng.ningchao.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
/**
* Created by huangxiongbiao on 2017/8/18.
*/
@Configuration
public class MyAdapter extends WebMvcConfigurerAdapter{
@Override
public void addViewControllers( ViewControllerRegistry registry ) {
// redirect forward
registry.addViewController( "/" ).setViewName( "redirect:pc/index.html" );
registry.setOrder( Ordered.HIGHEST_PRECEDENCE );
super.addViewControllers( registry );
}
// @Bean
// public CustomHttpMessageConverter byteArrayHttpMessageConverter() {
// return new CustomHttpMessageConverter();
// }
@Override
public void configureMessageConverters(List> converters) {
converters.add(new CustomHttpMessageConverter());
super.configureMessageConverters(converters);
}
// @Override
// public void extendMessageConverters(List> converters) {
// converters.clear();
// converters.add(new CustomHttpMessageConverter());
// super.extendMessageConverters(converters);
// }
}
自定义解析器
package com.ruanmeng.ningchao.config;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruanmeng.ningchao.util.AESUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* Created by huangxiongbiao on 2017/10/23.
*/
public class CustomHttpMessageConverter extends MappingJackson2HttpMessageConverter{
static String key = "";
@Override
protected Object readInternal(Class> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
// 解密
byte[] bytes = null;
inputMessage.getBody().read(bytes);
String json = new String(AESUtil.decrypt(bytes,key));
JavaType javaType = getJavaType(clazz, null);
//转换
return this.objectMapper.readValue(json, javaType);
}
@Override
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//使用Jackson的ObjectMapper将Java对象转换成Json String
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(object);
//加密
// byte[] bytes = AESUtil.encrypt(json,key);
//输出
outputMessage.getBody().write(json.getBytes());
}
}
二、对请求参数全部解密
1、探索过程
(1)起初研究返回json加密的时候,看到的是HttpMessageConverter想通过重写这个接口下面的实现类,来统一解密,但是由于请求参数的各种content-type和参数类型不同,会调用多种不同的HttpMessageConverter实现类。以及使用转化器时必须加注解等一系列因素放弃了该方案
(2)接着查询spring mvc参数解析原理,查到了HandlerMethodArgumentResolver接口
参考:http://www.cnblogs.com/sunny3096/p/7215906.html。从中了解到ServletModelAttributeMethodProcessor类是还在没有在注解的情况下,先实例化参数对象,然后按字段名注入字段,本想重写相关方法尝试统一解密,结果内部的方法启动后之后初始化调用一次,就不再调用由于项目时间比较赶,也没有时间研究相关原理,有知道了的朋友可以留言说明一些或者给个博客链接也可以。于是就放弃了这了思路。
(3)然后回到了最原始的方法通过拦截器拦截HttpServletRequest 对其修改解密,最初考虑过这个方法,由于在HttpServletRequest接口中没有看到可以修改参数的接口方法,以及最初想的方案是参数整体一起加密而不是每个参数的value加密就没有考虑这个方法。
(4)最后无奈选择拦截器选择参数对应的每个字段加密,采用继承OncePerRequestFilter实行自定义的filter,拦截到request后百度查找了下如何修改request参数,文章很多如:
http://983836259.blog.51cto.com/7311475/1877592 如何修改
http://jamesdev.blog.51cto.com/2066624/1876756 spring boot如何配置
最后实现的方法是拦截到requst然后通过,自定义HttpServletRequest修改内部参数
代码如下:
配置web conf
package com.ruanmeng.ningchao.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruanmeng.ningchao.interceptor.DecrypFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Filter;
/**
* Created by huangxiongbiao on 2017/8/18.
*/
@Configuration
public class MyAdapter extends WebMvcConfigurerAdapter{
@Override
public void addViewControllers( ViewControllerRegistry registry ) {
// redirect forward
registry.addViewController( "/" ).setViewName( "redirect:pc/index.html" );
registry.setOrder( Ordered.HIGHEST_PRECEDENCE );
super.addViewControllers( registry );
}
/*使用annotation tag来取代 */
// @Bean()
// public OncePerRequestFilter AuthFilter() {
// return new DecrypFilter();
// }
@Override
public void addArgumentResolvers(List argumentResolvers) {
argumentResolvers.add(new CustomServletModelAttributeMethodProcessor(true));
super.addArgumentResolvers(argumentResolvers);
}
// @Bean
// public CustomHttpMessageConverter byteArrayHttpMessageConverter() {
// return new CustomHttpMessageConverter();
// }
@Override
public void configureMessageConverters(List> converters) {
CustomHttpMessageConverter converter = new CustomHttpMessageConverter();
// converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED));
converters.add(converter);
converters.add(new CustomFormHttpMessageConverter());
super.configureMessageConverters(converters);
}
// @Override
// public void extendMessageConverters(List> converters) {
// converters.clear();
// converters.add(new CustomHttpMessageConverter());
// super.extendMessageConverters(converters);
// }
}
拦截器方法
package com.ruanmeng.ningchao.interceptor;
import com.ruanmeng.ningchao.util.AESUtil;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Map;
import java.util.Vector;
/**
* Created by huangxiongbiao on 2017/10/24.
*/
public class DecrypFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ModifyParametersWrapper mParametersWrapper = new ModifyParametersWrapper(request);
filterChain.doFilter(mParametersWrapper, response);
}
/**
* 继承HttpServletRequestWrapper,创建装饰类,以达到修改HttpServletRequest参数的目的
*/
private class ModifyParametersWrapper extends HttpServletRequestWrapper {
private Map parameterMap; // 所有参数的Map集合
public ModifyParametersWrapper(HttpServletRequest request) {
super(request);
parameterMap = request.getParameterMap();
}
// 重写几个HttpServletRequestWrapper中的方法
/**
* 获取所有参数名
*
* @return 返回所有参数名
*/
@Override
public Enumeration getParameterNames() {
Vector vector = new Vector(parameterMap.keySet());
return vector.elements();
}
/**
* 获取指定参数名的值,如果有重复的参数名,则返回第一个的值 接收一般变量 ,如text类型
*
* @param name
* 指定参数名
* @return 指定参数名的值
*/
@Override
public String getParameter(String name) {
String[] results = parameterMap.get(name);
if (results == null || results.length <= 0)
return null;
else {
System.out.println("修改之前: " + results[0]);
return modify(results[0]);
}
}
/**
* 获取指定参数名的所有值的数组,如:checkbox的所有数据
* 接收数组变量 ,如checkobx类型
*/
@Override
public String[] getParameterValues(String name) {
String[] results = parameterMap.get(name);
if (results == null || results.length <= 0)
return null;
else {
int length = results.length;
for (int i = 0; i < length; i++) {
System.out.println("修改之前2: " + results[i]);
results[i] = modify(results[i]);
}
return results;
}
}
/**
* 自定义的一个简单修改原参数的方法,即:给原来的参数值前面添加了一个修改标志的字符串
*
* @param string
* 原参数值
* @return 修改之后的值
*/
private String modify(String string) {
String result = new String(AESUtil.decrypt(string.getBytes(),""));
return result;
}
}
}
三:加密方法的bug探索
探索:
(1)后来有想到,加密方案如果使用aes,des等对称式加密,在app端还好可以把秘钥保存在手机内相对还算安全,但是在web端秘钥的保存就有很大的危险bug相当于秘钥公布给别人了
(2)考虑rsa非对称加密,参数加解密过程私钥保存在服务端,公钥在客户端,用户请求数据用公钥将参数加密到服务端,服务端用私钥解密。解决了一半的问题。但是返回的结果假设重新生成一对公钥私钥,私钥在客户端用于解密,公钥服务端用于加密。私钥相当于公布出去了对于返回的结果加密就没有保障了
(3)思考到https的加密过程想到的方案,参考:http://www.jianshu.com/p/84652f51ed8f
简单的流程是这样的:
一对rsa公钥私钥,约定一种(AES,DES)对称式加密方案。公钥在客户端保存,私钥在服务端保存
请求案例:
客户端
1、客户端代码生成一个随机码当做对称式加密的key
2、用key对请求的参数进行约定的对称加密
3、利用公钥对key进行rsa加密
4、将加密后的key放在header或cookie中发起请求
服务端
5、服务端接收到请求,在拦截其中取到加密的key,利用私钥解密得到随机码
6、利用该随机码对请求参数的所有value解密
7、返回数据时,再利用该随机码对返回的json串加密
客户端
8、客户端接收到数据时,用自己生成的随机码进行解密
参考:http://www.scienjus.com/custom-http-message-converter/
http://www.cnblogs.com/qq78292959/p/3760651.html