SpringBoot接口加密解密統(tǒng)一處理
我們與客戶端的接口交互中,為了更高的安全性,我們可能需要對接口加密(請求參數(shù)加密,服務(wù)端解密)、返回信息加密(服務(wù)端加密,客戶端解密),但是也不是所有的接口都這樣,有些接口可能不需要,我們可以使用注解來輕松達(dá)到此要求。
將接口參數(shù)的加密解密和返回信息的加密解密分開,分別定義注解,利用Controller的ControllerAdvice來攔截所有的請求,在其中判斷是否需要加密解密,即可達(dá)到要求。
使用方法:使用 DecryptRequest 和 EncryptResponse 注解即可,可以放在Controller的類和方法上,其中一個為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 {
/**
* 是否對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 {
/**
* 是否對結(jié)果加密
*/
boolean value() default true;
}
這兩個注解可以放在類和方法上,遵循一樣的邏輯,即:類上的注解 && 方法上的注解,一方?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(){}
/**
* 是否需要對結(jié)果加密
* 1.類上標(biāo)注或者方法上標(biāo)注,并且都為true
* 2.有一個標(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.有一個標(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,對于請求解密的,定義 DecryptRequestBodyAdvice ,實現(xiàn) RequestBodyAdvice 。
/**
* 請求數(shù)據(jù)接收處理類<br>
*
* 對加了@Decrypt的方法的數(shù)據(jù)進(jìn)行解密操作<br>
*
* 只對 @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的時候才開啟解密功能,一個配置即可打開或者關(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();
}
}
對于返回值加密,定義 EncryptResponseBodyAdvice,實現(xiàn) ResponseBodyAdvice。
/**
* 請求響應(yīng)處理類<br>
*
* 對加了@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;
}
//只針對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 ,這是一個加密解密的接口,有很多實現(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");
}
}
至此,一個完美的對接口的加密解密就實現(xiàn)了。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java并發(fā)程序刺客之假共享的原理及復(fù)現(xiàn)
前段時間在各種社交平臺“雪糕刺客”這個詞比較火,而在并發(fā)程序中也有一個刺客,那就是假共享。本文將通過示例詳細(xì)講解假共享的原理及復(fù)現(xiàn),需要的可以參考一下2022-08-08
IDEA+Maven創(chuàng)建Spring項目的實現(xiàn)步驟
這篇文章主要介紹了IDEA+Maven創(chuàng)建Spring項目的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Mybatis中foreach標(biāo)簽帶來的空格\換行\(zhòng)回車問題及解決方案
這篇文章主要介紹了解決Mybatis中foreach標(biāo)簽帶來的空格,換行,回車問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04
Java中FileWriter的用法及wirte()重載方法詳解
這篇文章主要介紹了Java中FileWriter的用法及wirte()重載方法詳解,FileWriter是Java編程語言中的一個類,用于將字符寫入文件,它提供了一種簡單而方便的方式來創(chuàng)建、打開和寫入文件,通過使用FileWriter,我們可以將字符數(shù)據(jù)寫入文本文件,需要的朋友可以參考下2023-10-10

