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

一、对全局返回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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容