1. 程式人生 > >Spring Boot Http通訊資料之加解密

Spring Boot Http通訊資料之加解密

1、應用背景

    Spring Boot開發基於restful型別的API,我們再處理JSON請求時通常使用@RequestBody和@ResponseBody註解,

針對HTTP JSON請求需要解密和返回的JSON資料我們經常需要對資料進行加密,有的時候我們還必須過濾掉一些物件欄位的值來減少網路流量。

2、解決方案

對HTTP JSON請求資料解密 和 對返回的JSON資料進行加密,Spring提供了RequestBodyAdvice和ResponseBodyAdvice兩個介面 供開發者實現。

3、原始碼

maven專案依賴

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> </dependencies>

加解密工具類:


 

 

 
import org.apache.tomcat.util.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.SecureRandom;

/**
 * @author huang_guoqiang
 * @desc DES加解密
 * @date 2017/11/14 11:38
 *
 * DES加密介紹
 * DES是一種對稱加密演算法,所謂對稱加密演算法即:加密和解密使用相同金鑰的演算法。DES加密演算法出自IBM的研究,
 * 後來被美國政府正式採用,之後開始廣泛流傳,但是近些年使用越來越少,因為DES使用56位金鑰,以現代計算能力,
 * 24小時內即可被破解。雖然如此,在某些簡單應用中,我們還是可以使用DES加密演算法,本文簡單講解DES的JAVA實現。
 * 注意:DES加密和解密過程中,金鑰長度都必須是8的倍數
 *
 */
public class DESHelper {

    private final static String DES = "DES";
    private final static String KEY = "www.com.cn";

    public DESHelper() {
    }

    public static String encrypt(String pliantext) throws Exception {
        return encodeBase64(encryptDES(pliantext,KEY));
    }

    public static String encrypt(String pliantext,String key) throws Exception {
        return encodeBase64(encryptDES(pliantext,key));
    }

    public  static String decrypt(String ciphertext) throws Exception{
        return decryptDES(decodeBase64(ciphertext.getBytes()),KEY);
    }
    public static String decrypt(String ciphertext, String key) throws Exception {
        return decryptDES(decodeBase64(ciphertext.getBytes()), key);
    }

    /**
     *  base64編碼
     * @param binaryData
     * @return
     * @throws Exception
     */
    private static String encodeBase64(byte[] binaryData)throws Exception{
        try{
            return Base64.encodeBase64String(binaryData);
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("BASE64編碼失敗!");
        }
    }

    /**
     * Base64解碼
     * @param binaryData
     * @return
     */
    private static byte[] decodeBase64(byte[] binaryData){
        try {
            return Base64.decodeBase64(binaryData);
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("BASE64解碼失敗!");
        }
    }

    public static byte[] encryptDES(String data, String key){

        try {
            // 生成一個可信任的隨機數源 ,  SHA1PRNG: 僅指定演算法名稱
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            // 從原始金鑰資料建立DESKeySpec物件
            DESKeySpec deskey = new DESKeySpec(key.getBytes("UTF-8"));

            //建立一個密匙工廠,然後用它把DESKeySpec轉換成
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
            SecretKey secretKey = keyFactory.generateSecret(deskey);
            //Cipher物件實際完成加密操作
            Cipher cipher = Cipher.getInstance(DES);
            //用密匙初始化Cipher物件,
            cipher.init(Cipher.ENCRYPT_MODE,secretKey,random);
            //現在,獲取資料並加密
            //正式執行加密操作
            return cipher.doFinal(data.getBytes("UTF-8"));

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decryptDES(byte[] data ,String key){

        try {
            // 演算法要求有一個可信任的隨機數源,  SHA1PRNG: 僅指定演算法名稱
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            // 建立一個DESKeySpec物件
            DESKeySpec desKeySpec = new DESKeySpec(key.getBytes("UTF-8"));
            // 建立一個密匙工廠
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
            // 將DESKeySpec物件轉換成SecretKey物件
            SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
            // Cipher物件實際完成解密操作
            Cipher cipher = Cipher.getInstance(DES);
            // 用密匙初始化Cipher物件
            cipher.init(Cipher.DECRYPT_MODE,secretKey,random);
            // 真正開始解密操作
            return new String(cipher.doFinal(data),"UTF-8");

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws Exception {
        String enString = encrypt("{\"pageEntity\":{\"pageSize\":\"20\",\"pageIndex\":\"0\"},\"instance\":{\"status\":\"7\"}}");
        System.out.println("加密後的字串是:" + enString);
        String deString = decrypt(enString);
        System.out.println("解密後的字串是:" + deString);

    }


}

註解類:

import org.springframework.web.bind.annotation.Mapping;
import java.lang.annotation.*;

/**
 * @author huang_guoqiang
 * @desc 註解
 * @date 2018/3/2 11:03
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface SecurityParameter {

    /**
     * 入參是否解密,預設解密
     */
    boolean inDecode() default true;

    /**
     * 出參是否加密,預設加密
     */
    boolean outEncode() default true;
}

 

 
import com.ykc.api.SecurityParameter;
import com.ykc.utils.DESHelper;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;

/**
 * @author huang_guoqiang
 * @desc 請求資料解密
 * @date 2018/3/2 11:01
 */
@ControllerAdvice(basePackages = "com.ykc.bill.action")
public class MyRequestBodyAdvice implements RequestBodyAdvice {

    private static final Logger logger = LoggerFactory.getLogger(MyRequestBodyAdvice.class);

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        try {
            boolean encode = false;
            if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
                //獲取註解配置的包含和去除欄位
                SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
                //入參是否需要解密
                encode = serializedField.inDecode();
            }
            if (encode) {
                logger.info("對方法method :【" + methodParameter.getMethod().getName() + "】返回資料進行解密");
                return new MyHttpInputMessage(inputMessage);
            }else{
                return inputMessage;
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("對方法method :【" + methodParameter.getMethod().getName() + "】返回資料進行解密出現異常:"+e.getMessage());
            return inputMessage;
        }
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    class MyHttpInputMessage implements HttpInputMessage {
        private HttpHeaders headers;

        private InputStream body;

        public MyHttpInputMessage(HttpInputMessage inputMessage) throws Exception {
            this.headers = inputMessage.getHeaders();
            this.body = IOUtils.toInputStream(DESHelper.decrypt(easpString(IOUtils.toString(inputMessage.getBody(), "UTF-8"))), "UTF-8");
        }

        @Override
        public InputStream getBody() throws IOException {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }

        /**
         *
         * @param requestData
         * @return
         */
        public String easpString(String requestData){
            if(requestData != null && !requestData.equals("")){
                if(!requestData.startsWith("{\"requestData\":")){
                    throw new RuntimeException("引數【requestData】缺失異常!");
                }else{
                    int closeLen = requestData.length()-2;
                    int openLen = "{\"requestData\":".length()+1;
                    return StringUtils.substring(requestData,openLen,closeLen);
                }
            }
            return "";
        }
    }
}

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ykc.api.SecurityParameter;
import com.ykc.utils.DESHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * @author huang_guoqiang
 * @desc 返回資料加密
 * @date 2018/3/2 11:12
 */
@ControllerAdvice(basePackages = "com.ykc.bill.action")
public class MyResponseBodyAdvice implements ResponseBodyAdvice {

    private final static Logger logger = LoggerFactory.getLogger(MyResponseBodyAdvice.class);

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        boolean encode = false;
        if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
            //獲取註解配置的包含和去除欄位
            SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
            //出參是否需要加密
            encode = serializedField.outEncode();
        }
        if (encode) {
            logger.info("對方法method :【" + methodParameter.getMethod().getName() + "】返回資料進行加密");
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
                return DESHelper.encrypt(result);
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("對方法method :【" + methodParameter.getMethod().getName() + "】返回資料進行解密出現異常:"+e.getMessage());
            }
        }
        return body;
    }
}