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部分數據,使用系統(tǒng)默認配置的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數據區(qū)。
使用時機:
返回的數據不是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方法,結果經過不斷的嘗試發(fā)現(xiàn)有點行不通。
一個原因是參數必須添加@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想通過重寫這個接口下面的實現(xiàn)類,來統(tǒng)一解密,但是由于請求參數的各種content-type和參數類型不同,會調用多種不同的HttpMessageConverter實現(xiàn)類。以及使用轉化器時必須加注解等一系列因素放棄了該方案
(2)接著查詢spring mvc參數解析原理,查到了HandlerMethodArgumentResolver接口
參考:http://www.cnblogs.com/sunny3096/p/7215906.html。從中了解到ServletModelAttributeMethodProcessor類是還在沒有在注解的情況下,先實例化參數對象,然后按字段名注入字段,本想重寫相關方法嘗試統(tǒng)一解密,結果內部的方法啟動后之后初始化調用一次,就不再調用由于項目時間比較趕,也沒有時間研究相關原理,有知道了的朋友可以留言說明一些或者給個博客鏈接也可以。于是就放棄了這了思路。
(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如何配置
最后實現(xiàn)的方法是攔截到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,創(chuàng)建裝飾類,以達到修改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.itdecent.cn/p/84652f51ed8f
簡單的流程是這樣的:
一對rsa公鑰私鑰,約定一種(AES,DES)對稱式加密方案。公鑰在客戶端保存,私鑰在服務端保存
請求案例:
客戶端
1、客戶端代碼生成一個隨機碼當做對稱式加密的key
2、用key對請求的參數進行約定的對稱加密
3、利用公鑰對key進行rsa加密
4、將加密后的key放在header或cookie中發(fā)起請求
服務端
5、服務端接收到請求,在攔截其中取到加密的key,利用私鑰解密得到隨機碼
6、利用該隨機碼對請求參數的所有value解密
7、返回數據時,再利用該隨機碼對返回的json串加密
客戶端
8、客戶端接收到數據時,用自己生成的隨機碼進行解密

參考:http://www.scienjus.com/custom-http-message-converter/
http://www.cnblogs.com/qq78292959/p/3760651.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容