SpringBoot接口加密解密統(tǒng)一處理
我們與客戶端的接口交互中,為了更高的安全性,我們可能需要對(duì)接口加密(請(qǐng)求參數(shù)加密,服務(wù)端解密)、返回信息加密(服務(wù)端加密,客戶端解密),但是也不是所有的接口都這樣,有些接口可能不需要,我們可以使用注解來輕松達(dá)到此要求。
將接口參數(shù)的加密解密和返回信息的加密解密分開,分別定義注解,利用Controller的ControllerAdvice來攔截所有的請(qǐng)求,在其中判斷是否需要加密解密,即可達(dá)到要求。
使用方法:使用 DecryptRequest 和 EncryptResponse 注解即可,可以放在Controller的類和方法上,其中一個(gè)為false就不執(zhí)行了。像這樣:
@RestController @RequestMapping("/test") //@DecryptRequest @EncryptResponse public class TestController { @Autowired @Qualifier("rrCrypto") private Crypto crypto; @DecryptRequest(false) @EncryptResponse(false) @RequestMapping(value = "/enc" , method = RequestMethod.POST) public String enc(@RequestBody String body){ return crypto.encrypt(body); } }
定義參數(shù)解密的注解,DecryptRequest。
/** * 解密注解 * * <p>加了此注解的接口(true)將進(jìn)行數(shù)據(jù)解密操作(post的body) 可 * 以放在類上,可以放在方法上 </p> * @author xiongshiyan */ @Target({ElementType.METHOD , ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DecryptRequest { /** * 是否對(duì)body進(jìn)行解密 */ boolean value() default true; }
定義返回信息加密的注解,EncryptResponse。
/** * 加密注解 * * <p>加了此注解的接口(true)將進(jìn)行數(shù)據(jù)加密操作 * 可以放在類上,可以放在方法上 </p> * @author 熊詩言 */ @Target({ElementType.METHOD , ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface EncryptResponse { /** * 是否對(duì)結(jié)果加密 */ boolean value() default true; }
這兩個(gè)注解可以放在類和方法上,遵循一樣的邏輯,即:類上的注解 && 方法上的注解,一方?jīng)]有即為true,都為false為false。邏輯主要在 NeedCrypto 中。
/** * 判斷是否需要加解密 * @author xiongshiyan at 2018/8/30 , contact me with email yanshixiong@126.com or phone 15208384257 */ class NeedCrypto { private NeedCrypto(){} /** * 是否需要對(duì)結(jié)果加密 * 1.類上標(biāo)注或者方法上標(biāo)注,并且都為true * 2.有一個(gè)標(biāo)注為false就不需要加密 */ static boolean needEncrypt(MethodParameter returnType) { boolean encrypt = false; boolean classPresentAnno = returnType.getContainingClass().isAnnotationPresent(EncryptResponse.class); boolean methodPresentAnno = returnType.getMethod().isAnnotationPresent(EncryptResponse.class); if(classPresentAnno){ //類上標(biāo)注的是否需要加密 encrypt = returnType.getContainingClass().getAnnotation(EncryptResponse.class).value(); //類不加密,所有都不加密 if(!encrypt){ return false; } } if(methodPresentAnno){ //方法上標(biāo)注的是否需要加密 encrypt = returnType.getMethod().getAnnotation(EncryptResponse.class).value(); } return encrypt; } /** * 是否需要參數(shù)解密 * 1.類上標(biāo)注或者方法上標(biāo)注,并且都為true * 2.有一個(gè)標(biāo)注為false就不需要解密 */ static boolean needDecrypt(MethodParameter parameter) { boolean encrypt = false; boolean classPresentAnno = parameter.getContainingClass().isAnnotationPresent(DecryptRequest.class); boolean methodPresentAnno = parameter.getMethod().isAnnotationPresent(DecryptRequest.class); if(classPresentAnno){ //類上標(biāo)注的是否需要解密 encrypt = parameter.getContainingClass().getAnnotation(DecryptRequest.class).value(); //類不加密,所有都不加密 if(!encrypt){ return false; } } if(methodPresentAnno){ //方法上標(biāo)注的是否需要解密 encrypt = parameter.getMethod().getAnnotation(DecryptRequest.class).value(); } return encrypt; } }
然后定義ControllerAdvice,對(duì)于請(qǐng)求解密的,定義 DecryptRequestBodyAdvice ,實(shí)現(xiàn) RequestBodyAdvice 。
/** * 請(qǐng)求數(shù)據(jù)接收處理類<br> * * 對(duì)加了@Decrypt的方法的數(shù)據(jù)進(jìn)行解密操作<br> * * 只對(duì) @RequestBody 參數(shù)有效 * @author xiongshiyan */ @ControllerAdvice @ConditionalOnProperty(prefix = "spring.crypto.request.decrypt", name = "enabled" , havingValue = "true", matchIfMissing = true) public class DecryptRequestBodyAdvice implements RequestBodyAdvice { @Value("${spring.crypto.request.decrypt.charset:UTF-8}") private String charset = "UTF-8"; @Autowired @Qualifier("rrCrypto") private Crypto crypto; @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @Override public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return body; } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { if( NeedCrypto.needDecrypt(parameter) ){ return new DecryptHttpInputMessage(inputMessage , charset , crypto); } return inputMessage; } @Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return body; } }
標(biāo)上注解 ConditionalOnProperty 表示只有條件為true的時(shí)候才開啟解密功能,一個(gè)配置即可打開或者關(guān)閉解密功能。真正的解密邏輯留給 DecryptHttpInputMessage , 它又委托給 Crypto。
/** * * @author xiongshiyan */ public class DecryptHttpInputMessage implements HttpInputMessage { private HttpInputMessage inputMessage; private String charset; private Crypto crypto; public DecryptHttpInputMessage(HttpInputMessage inputMessage, String charset , Crypto crypto) { this.inputMessage = inputMessage; this.charset = charset; this.crypto = crypto; } @Override public InputStream getBody() throws IOException { String content = IoUtil.read(inputMessage.getBody() , charset); String decryptBody = crypto.decrypt(content, charset); return new ByteArrayInputStream(decryptBody.getBytes(charset)); } @Override public HttpHeaders getHeaders() { return inputMessage.getHeaders(); } }
對(duì)于返回值加密,定義 EncryptResponseBodyAdvice,實(shí)現(xiàn) ResponseBodyAdvice。
/** * 請(qǐng)求響應(yīng)處理類<br> * * 對(duì)加了@Encrypt的方法的數(shù)據(jù)進(jìn)行加密操作 * * @author 熊詩言 * */ @ControllerAdvice @ConditionalOnProperty(prefix = "spring.crypto.response.encrypt", name = "enabled" , havingValue = "true", matchIfMissing = true) public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> { @Value("${spring.crypto.request.decrypt.charset:UTF-8}") private String charset = "UTF-8"; @Autowired @Qualifier("rrCrypto") private Crypto crypto; @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { boolean encrypt = NeedCrypto.needEncrypt(returnType); if( !encrypt ){ return body; } if(!(body instanceof ResponseMsg)){ return body; } //只針對(duì)ResponseMsg的data進(jìn)行加密 ResponseMsg responseMsg = (ResponseMsg) body; Object data = responseMsg.getData(); if(null == data){ return body; } String xx; Class<?> dataClass = data.getClass(); if(dataClass.isPrimitive() || (data instanceof String)){ xx = String.valueOf(data); }else { //JavaBean、Map、List等先序列化 if(List.class.isAssignableFrom(dataClass)){ xx = JsonUtil.serializeList((List<Object>) data); }else if(Map.class.isAssignableFrom(dataClass)){ xx = JsonUtil.serializeMap((Map<String, Object>) data); }else { xx = JsonUtil.serializeJavaBean(data); } } responseMsg.setData(crypto.encrypt(xx, charset)); return responseMsg; } }
真正的加密邏輯委托給 Crypto ,這是一個(gè)加密解密的接口,有很多實(shí)現(xiàn)類,參見:鏈接
/** * Request-Response加解密體系的加解密方式 * @author xiongshiyan at 2018/8/14 , contact me with email yanshixiong@126.com or phone 15208384257 */ @Configuration public class RRCryptoConfig { /** * 加密解密方式使用一樣的 */ @Bean("rrCrypto") public Crypto rrCrypto(){ return new AesCrypto("密鑰key"); } }
至此,一個(gè)完美的對(duì)接口的加密解密就實(shí)現(xiàn)了。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- SpringBoot使用AES對(duì)JSON數(shù)據(jù)加密和解密的實(shí)現(xiàn)方法
- springboot使用國產(chǎn)加密算法方式,sm2和sm3加解密demo
- Springboot接口返回參數(shù)及入?yún)SA加密解密的過程詳解
- SpringBoot實(shí)現(xiàn)接口參數(shù)加密解密的示例代碼
- 關(guān)于Springboot數(shù)據(jù)庫配置文件明文密碼加密解密的問題
- springboot實(shí)現(xiàn)敏感字段加密存儲(chǔ)解密顯示功能
- springboot實(shí)現(xiàn)注冊(cè)加密與登錄解密功能(demo)
- 在SpringBoot中通過jasypt進(jìn)行加密解密的方法
- Springboot實(shí)現(xiàn)密碼的加密解密
- SpringBoot實(shí)現(xiàn)國密SM4加密解密的使用示例
相關(guān)文章
Java常用類庫Apache Commons工具類說明及使用實(shí)例詳解
這篇文章主要介紹了Java常用類庫Apache Commons工具類說明及使用實(shí)例詳解,需要的朋友可以參考下2020-02-02解決idea中svn提交時(shí)performing vcs refresh時(shí)間很長的問題
這篇文章主要介紹了解決idea中svn提交時(shí)performing vcs refresh時(shí)間很長的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09手把手教學(xué)Win10同時(shí)安裝兩個(gè)版本的JDK并隨時(shí)切換(JDK8和JDK11)
最近在學(xué)習(xí)JDK11的一些新特性,但是日常使用基本上都是基于JDK8,因此,需要在win環(huán)境下安裝多個(gè)版本的JDK,下面這篇文章主要給大家介紹了手把手教學(xué)Win10同時(shí)安裝兩個(gè)版本的JDK(JDK8和JDK11)并隨時(shí)切換的相關(guān)資料,需要的朋友可以參考下2023-03-03SpringMVC學(xué)習(xí)之JSTL條件行為和遍歷行為詳解
這篇文章主要介紹了SpringMVC學(xué)習(xí)之JSTL條件行為和遍歷行為詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08