Spring Boot Http通訊資料之加解密
阿新 • • 發佈:2018-11-19
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; } }