美文网首页
java spring mvc对全局返回json加密,请求参数解

java spring mvc对全局返回json加密,请求参数解

作者: huangxiongbiao | 来源:发表于2017-10-23 15:16 被阅读2152次

    一、对全局返回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<HttpMessageConverter<?>> converters) {
            converters.add(new CustomHttpMessageConverter());
            super.configureMessageConverters(converters);
        }
    
    //    @Override
    //    public void extendMessageConverters(List<HttpMessageConverter<?>> 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></bean>*/
    //    @Bean()
    //    public OncePerRequestFilter AuthFilter() {
    //        return new DecrypFilter();
    //    }
    
    
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
            argumentResolvers.add(new CustomServletModelAttributeMethodProcessor(true));
            super.addArgumentResolvers(argumentResolvers);
        }
    
    //    @Bean
    //    public CustomHttpMessageConverter byteArrayHttpMessageConverter() {
    //        return new CustomHttpMessageConverter();
    //    }
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> 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<HttpMessageConverter<?>> 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<String, String[]> parameterMap; // 所有参数的Map集合
    
            public ModifyParametersWrapper(HttpServletRequest request) {
                super(request);
                parameterMap = request.getParameterMap();
            }
    
            // 重写几个HttpServletRequestWrapper中的方法
            /**
             * 获取所有参数名
             *
             * @return 返回所有参数名
             */
            @Override
            public Enumeration<String> getParameterNames() {
                Vector<String> vector = new Vector<String>(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

    相关文章

      网友评论

          本文标题:java spring mvc对全局返回json加密,请求参数解

          本文链接:https://www.haomeiwen.com/subject/mrlauxtx.html