diff --git a/README.md b/README.md index cbda796..49f7b58 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,13 @@ - - [x] AES - - [x] DES - - [x] RSA + - - [x] CUSTOM + - 可进行解密的方式有: - - [x] AES - - [x] DES - - [x] RSA + - - [x] CUSTOM ## 引入注册 ### 导入依赖 在项目的`pom.xml`中引入依赖: @@ -116,6 +119,41 @@ public class User implements Serializable { } ``` + +## 使用自定义加密/解密 +````java +@Data +@EncryptBody +@FieldBody +public class User implements Serializable { + + @CustomDecryptBody(providerClassName = "com.myorg.mypkg.MyCryptoProvider", decryptMethodName = "myDecryptionFunction") + private String name; + + @CustomEncryptBody(providerClassName = "com.myorg.mypkg.MyCryptoProvider", encryptMethodName = "myEncryptionFunction") + private String numberValue; + + + +} +```` + +使用 myEncryptionFunction 实现 MyCryptoProvider 类,如下所示。 +```java +package com.myorg.mypkg; + +public class MyCryptoProvider { + String myEncryptionFunction(String input) { + // 用于加密输入并返回预期加密数据的代码 + } + + String myDecryptionFunction(String input) { + // 解密输入并返回预期解密数据的代码 + } + +} +``` + ## 注解一览表 - [编码/加密注解一览表](https://github.com/Licoy/encrypt-body-spring-boot-starter/wiki/加密注解一览表) - [解密注解一览表](https://github.com/Licoy/encrypt-body-spring-boot-starter/wiki/解密注解一览表) diff --git a/README_EN.md b/README_EN.md index 73620d4..fbe4a1e 100644 --- a/README_EN.md +++ b/README_EN.md @@ -13,10 +13,12 @@ - - [x] AES - - [x] DES - - [x] RSA + - - [x] CUSTOM - The methods that can be decrypted are: - - [x] AES - - [x] DES - - [x] RSA + - - [x] CUSTOM ## Import registration ### Import dependencies Introduce dependencies in the project's `pom.xml`: @@ -116,6 +118,43 @@ public class User implements Serializable { } ```` + +## Using CUSTOM encryption/decryption +````java +@Data +@EncryptBody +@FieldBody +public class User implements Serializable { + + @CustomDecryptBody(providerClassName = "com.myorg.mypkg.MyCryptoProvider", decryptMethodName = "myDecryptionFunction") + private String name; + + @CustomEncryptBody(providerClassName = "com.myorg.mypkg.MyCryptoProvider", encryptMethodName = "myEncryptionFunction") + private String numberValue; + + + +} +```` + +Implement the `MyCryptoProvider` class with `myEncryptionFunction` as shown below. +```java +package com.myorg.mypkg; + +public class MyCryptoProvider { + String myEncryptionFunction(String input) { + // code to encrypt input and return the expected encrypted data + } + + String myDecryptionFunction(String input) { + // code to decrypt input and return the expected decrypted data + } + +} +``` + + + ## Annotation list - [Encryption/Encryption Annotation List](https://github.com/Licoy/encrypt-body-spring-boot-starter/wiki/加密注解一览表) - [Decryption Annotation List](https://github.com/Licoy/encrypt-body-spring-boot-starter/wiki/解密注解一览表) diff --git a/pom.xml b/pom.xml index f44eaed..e6de8f4 100644 --- a/pom.xml +++ b/pom.xml @@ -180,6 +180,32 @@ + + + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + diff --git a/src/main/java/cn/licoy/encryptbody/advice/DecryptRequestBodyAdvice.java b/src/main/java/cn/licoy/encryptbody/advice/DecryptRequestBodyAdvice.java index 6c0e2fd..09a45e5 100644 --- a/src/main/java/cn/licoy/encryptbody/advice/DecryptRequestBodyAdvice.java +++ b/src/main/java/cn/licoy/encryptbody/advice/DecryptRequestBodyAdvice.java @@ -7,6 +7,7 @@ import cn.hutool.crypto.asymmetric.RSA; import cn.licoy.encryptbody.annotation.FieldBody; import cn.licoy.encryptbody.annotation.decrypt.AESDecryptBody; +import cn.licoy.encryptbody.annotation.decrypt.CustomDecryptBody; import cn.licoy.encryptbody.annotation.decrypt.DESDecryptBody; import cn.licoy.encryptbody.annotation.decrypt.DecryptBody; import cn.licoy.encryptbody.annotation.decrypt.RSADecryptBody; @@ -203,6 +204,14 @@ private DecryptAnnotationInfoBean getDecryptAnnotation(AnnotatedElement annotate return DecryptAnnotationInfoBean.builder().decryptBodyMethod(DecryptBodyMethod.RSA).key(decryptBody.key()).rsaKeyType(decryptBody.type()).build(); } } + + if (annotatedElement.isAnnotationPresent(CustomDecryptBody.class)) { + CustomDecryptBody decryptBody = annotatedElement.getAnnotation(CustomDecryptBody.class); + if (decryptBody != null) { + return DecryptAnnotationInfoBean.builder().decryptBodyMethod(DecryptBodyMethod.CUSTOM).providerClassName(decryptBody.providerClassName()).decryptMethodName(decryptBody.decryptMethodName()).build(); + } + } + return null; } @@ -232,6 +241,15 @@ private String switchDecrypt(String formatStringBody, DecryptAnnotationInfoBean RSA rsa = CommonUtils.infoBeanToRsaInstance(infoBean); return rsa.decryptStr(formatStringBody, infoBean.getRsaKeyType().toolType); } + if (method == DecryptBodyMethod.CUSTOM) { + try { + Class clazz = Class.forName(infoBean.getProviderClassName()); + Method m = clazz.getMethod(infoBean.getDecryptMethodName(), String.class); + return m.invoke(null, formatStringBody).toString(); + } catch( Exception e) { + return "failed to encrypt: " + formatStringBody; + } + } throw new DecryptBodyFailException(); } } diff --git a/src/main/java/cn/licoy/encryptbody/advice/EncryptResponseBodyAdvice.java b/src/main/java/cn/licoy/encryptbody/advice/EncryptResponseBodyAdvice.java index 006ef33..b724347 100644 --- a/src/main/java/cn/licoy/encryptbody/advice/EncryptResponseBodyAdvice.java +++ b/src/main/java/cn/licoy/encryptbody/advice/EncryptResponseBodyAdvice.java @@ -31,6 +31,7 @@ import java.lang.reflect.Method; + /** * 响应数据的加密处理
* 本类只对控制器参数中含有{@link org.springframework.web.bind.annotation.ResponseBody} @@ -75,45 +76,55 @@ private boolean hasEncryptAnnotation(AnnotatedElement annotatedElement) { if (annotatedElement == null) { return false; } - return annotatedElement.isAnnotationPresent(EncryptBody.class) || annotatedElement.isAnnotationPresent(AESEncryptBody.class) || annotatedElement.isAnnotationPresent(DESEncryptBody.class) || annotatedElement.isAnnotationPresent(RSAEncryptBody.class) || annotatedElement.isAnnotationPresent(MD5EncryptBody.class) || annotatedElement.isAnnotationPresent(SHAEncryptBody.class); + return annotatedElement.isAnnotationPresent(EncryptBody.class) + || annotatedElement.isAnnotationPresent(AESEncryptBody.class) + || annotatedElement.isAnnotationPresent(DESEncryptBody.class) + || annotatedElement.isAnnotationPresent(RSAEncryptBody.class) + || annotatedElement.isAnnotationPresent(MD5EncryptBody.class) + || annotatedElement.isAnnotationPresent(SHAEncryptBody.class) + || annotatedElement.isAnnotationPresent(CustomEncryptBody.class); } + + @Override - public String beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body == null) { return null; } String str = CommonUtils.convertToStringOrJson(body, objectMapper); - response.getHeaders().setContentType(MediaType.TEXT_PLAIN); + Method method = returnType.getMethod(); if (method != null) { - // 从方法上 + + //check if only some fields is response should be encrypted or the whole response should be encrypted. + //if only field encryption is required, we should not change the content-type and keep the returned object intact. + Class methodReturnType = method.getReturnType(); + if (methodReturnType.isAnnotationPresent(FieldBody.class)) { + return this.eachClassField(body, method.getReturnType()); + } + EncryptAnnotationInfoBean methodAnnotation = this.getEncryptAnnotation(method); if (methodAnnotation != null) { + response.getHeaders().setContentType(MediaType.TEXT_PLAIN); return switchEncrypt(str, methodAnnotation); } - // 从方法返回值上 - Class methodReturnType = method.getReturnType(); - if (methodReturnType.isAnnotationPresent(FieldBody.class)) { - Object encryptResult = this.eachClassField(body, method.getReturnType()); - try { - return objectMapper.writeValueAsString(encryptResult); - } catch (JsonProcessingException e) { - throw new EncryptBodyFailException(e.getMessage()); - } - } else { - EncryptAnnotationInfoBean returnTypeClassAnnotation = this.getEncryptAnnotation(methodReturnType); - if (returnTypeClassAnnotation != null) { - return switchEncrypt(str, returnTypeClassAnnotation); - } + + EncryptAnnotationInfoBean returnTypeClassAnnotation = this.getEncryptAnnotation(methodReturnType); + if (returnTypeClassAnnotation != null) { + response.getHeaders().setContentType(MediaType.TEXT_PLAIN); + return switchEncrypt(str, returnTypeClassAnnotation); } } // 从声明类上 EncryptAnnotationInfoBean classAnnotation = this.getEncryptAnnotation(returnType.getDeclaringClass()); if (classAnnotation != null) { + response.getHeaders().setContentType(MediaType.TEXT_PLAIN); return switchEncrypt(str, classAnnotation); } + + throw new EncryptBodyFailException(); } @@ -197,6 +208,12 @@ private EncryptAnnotationInfoBean getEncryptAnnotation(AnnotatedElement annotate return EncryptAnnotationInfoBean.builder().encryptBodyMethod(EncryptBodyMethod.RSA).key(encryptBody.key()).rsaKeyType(encryptBody.type()).build(); } } + if (annotatedElement.isAnnotationPresent(CustomEncryptBody.class)) { + CustomEncryptBody encryptBody = annotatedElement.getAnnotation(CustomEncryptBody.class); + if (encryptBody != null) { + return EncryptAnnotationInfoBean.builder().encryptBodyMethod(EncryptBodyMethod.CUSTOM).providerClassName(encryptBody.providerClassName()).encryptMethodName(encryptBody.encryptMethodName()).build(); + } + } return null; } @@ -236,6 +253,16 @@ private String switchEncrypt(String formatStringBody, EncryptAnnotationInfoBean RSA rsa = CommonUtils.infoBeanToRsaInstance(infoBean); return rsa.encryptHex(formatStringBody, infoBean.getRsaKeyType().toolType); } + if (method == EncryptBodyMethod.CUSTOM) { + try { + Class clazz = Class.forName(infoBean.getProviderClassName()); + Method m = clazz.getMethod(infoBean.getEncryptMethodName(), String.class); + return m.invoke(null, formatStringBody).toString(); + } catch(Exception e) { + return "failed to encrypt: " + formatStringBody; + } + + } throw new EncryptBodyFailException(); } diff --git a/src/main/java/cn/licoy/encryptbody/annotation/decrypt/CustomDecryptBody.java b/src/main/java/cn/licoy/encryptbody/annotation/decrypt/CustomDecryptBody.java new file mode 100644 index 0000000..8375e95 --- /dev/null +++ b/src/main/java/cn/licoy/encryptbody/annotation/decrypt/CustomDecryptBody.java @@ -0,0 +1,33 @@ +package cn.licoy.encryptbody.annotation.decrypt; + +import java.lang.annotation.*; + +@Target(value = {ElementType.METHOD,ElementType.TYPE,ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented + +public @interface CustomDecryptBody { + /** + * Custom encryption/decryption provider class name. + * + * @return provider class name + */ + public String providerClassName() default ""; + + /** + * Name of the static method in provider class to be used for encryption. + * NOTE: encryption method should have a signature like `public String encrypt(String)`. + * + * @return + */ + public String encryptMethodName() default "encrypt"; + + /** + * Name of the static method in provider class to be used for decryption. + * NOTE: decryption method should have a signature like `public String decrypt(String)`. + * + * @return + */ + public String decryptMethodName() default "decrypt"; + +} diff --git a/src/main/java/cn/licoy/encryptbody/annotation/encrypt/CustomEncryptBody.java b/src/main/java/cn/licoy/encryptbody/annotation/encrypt/CustomEncryptBody.java new file mode 100644 index 0000000..8dc55c1 --- /dev/null +++ b/src/main/java/cn/licoy/encryptbody/annotation/encrypt/CustomEncryptBody.java @@ -0,0 +1,36 @@ +package cn.licoy.encryptbody.annotation.encrypt; + + + + +import java.lang.annotation.*; + +@Target(value = {ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CustomEncryptBody { + + /** + * Custom encryption/decryption provider class name. + * + * @return provider class name + */ + public String providerClassName() default ""; + + /** + * Name of the static method in provider class to be used for encryption. + * NOTE: encryption method should have a signature like `public String encrypt(String)`. + * + * @return + */ + public String encryptMethodName() default "encrypt"; + + /** + * Name of the static method in provider class to be used for decryption. + * NOTE: decryption method should have a signature like `public String decrypt(String)`. + * + * @return + */ + public String decryptMethodName() default "decrypt"; + +} diff --git a/src/main/java/cn/licoy/encryptbody/bean/DecryptAnnotationInfoBean.java b/src/main/java/cn/licoy/encryptbody/bean/DecryptAnnotationInfoBean.java index 5330ee9..46708b2 100644 --- a/src/main/java/cn/licoy/encryptbody/bean/DecryptAnnotationInfoBean.java +++ b/src/main/java/cn/licoy/encryptbody/bean/DecryptAnnotationInfoBean.java @@ -21,4 +21,10 @@ public class DecryptAnnotationInfoBean implements ISecurityInfo { private RSAKeyType rsaKeyType; + private String providerClassName; + + private String encryptMethodName; + + private String decryptMethodName; + } diff --git a/src/main/java/cn/licoy/encryptbody/bean/EncryptAnnotationInfoBean.java b/src/main/java/cn/licoy/encryptbody/bean/EncryptAnnotationInfoBean.java index 587a2bc..fabf175 100644 --- a/src/main/java/cn/licoy/encryptbody/bean/EncryptAnnotationInfoBean.java +++ b/src/main/java/cn/licoy/encryptbody/bean/EncryptAnnotationInfoBean.java @@ -25,4 +25,11 @@ public class EncryptAnnotationInfoBean implements ISecurityInfo { private RSAKeyType rsaKeyType; + private String providerClassName; + + private String encryptMethodName; + + private String decryptMethodName; + + } diff --git a/src/main/java/cn/licoy/encryptbody/bean/ISecurityInfo.java b/src/main/java/cn/licoy/encryptbody/bean/ISecurityInfo.java index 74139cb..766e2e7 100644 --- a/src/main/java/cn/licoy/encryptbody/bean/ISecurityInfo.java +++ b/src/main/java/cn/licoy/encryptbody/bean/ISecurityInfo.java @@ -2,6 +2,7 @@ import cn.licoy.encryptbody.enums.RSAKeyType; + import java.io.Serializable; /** @@ -26,4 +27,26 @@ public interface ISecurityInfo extends Serializable { */ RSAKeyType getRsaKeyType(); + + /** + * Get the class name of custom encryption/decryption provider + * + * @return class name + */ + String getProviderClassName(); + + /** + * Get the method name to be used to encryption. + * + * @return encryption method name. + */ + String getEncryptMethodName(); + + /** + * Get the method name to be used to decryption. + * + * @return decryption method name. + */ + String getDecryptMethodName(); + } diff --git a/src/main/java/cn/licoy/encryptbody/enums/DecryptBodyMethod.java b/src/main/java/cn/licoy/encryptbody/enums/DecryptBodyMethod.java index 796b719..f4627e3 100644 --- a/src/main/java/cn/licoy/encryptbody/enums/DecryptBodyMethod.java +++ b/src/main/java/cn/licoy/encryptbody/enums/DecryptBodyMethod.java @@ -18,6 +18,10 @@ public enum DecryptBodyMethod { /** * RAS */ - RSA + RSA, + /** + * CUSTOM + */ + CUSTOM } diff --git a/src/main/java/cn/licoy/encryptbody/enums/EncryptBodyMethod.java b/src/main/java/cn/licoy/encryptbody/enums/EncryptBodyMethod.java index b54469a..2a81de0 100644 --- a/src/main/java/cn/licoy/encryptbody/enums/EncryptBodyMethod.java +++ b/src/main/java/cn/licoy/encryptbody/enums/EncryptBodyMethod.java @@ -26,6 +26,11 @@ public enum EncryptBodyMethod { /** * RSA */ - RSA + RSA, + /** + * CUSTOM + */ + CUSTOM + } diff --git a/src/main/java/cn/licoy/encryptbody/util/CommonUtils.java b/src/main/java/cn/licoy/encryptbody/util/CommonUtils.java index fd5a293..397eeb2 100644 --- a/src/main/java/cn/licoy/encryptbody/util/CommonUtils.java +++ b/src/main/java/cn/licoy/encryptbody/util/CommonUtils.java @@ -37,19 +37,49 @@ public static String checkAndGetKey(String k1, String k2, String keyName) { */ public static RSA infoBeanToRsaInstance(ISecurityInfo info) { RSA rsa; - switch (info.getRsaKeyType()) { - case PUBLIC: - rsa = new RSA(null, SecureUtil.decode(info.getKey())); - break; - case PRIVATE: - rsa = new RSA(SecureUtil.decode(info.getKey()), null); - break; - default: - throw new IllegalSecurityTypeException(); + + try { + switch (info.getRsaKeyType()) { + case PUBLIC: + rsa = loadRsaPublicKey(info.getKey()); + break; + case PRIVATE: + rsa = loadRsaPrivateKey(info.getKey()); + break; + default: + throw new IllegalSecurityTypeException(); + } + } catch(Exception e) { + e.printStackTrace(); + throw new RuntimeException("failed to load rsa, " + e.getMessage()); } + + return rsa; } + private static RSA loadRsaPublicKey(String key) throws Exception { + //Try to load the key as value. If exception occurs, treat it as a file and try again. + try { + return new RSA(null, SecureUtil.decode(key)); + } catch (Exception e) { + key = new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(key)), java.nio.charset.StandardCharsets.UTF_8); + return new RSA(null, SecureUtil.decode(key)); + } + } + + private static RSA loadRsaPrivateKey(String key) throws Exception { + //Try to load the key as value. If exception occurs, treat it as a file and try again. + try { + return new RSA(SecureUtil.decode(key), null); + } catch (Exception e) { + key = java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(key)).toString(); + return new RSA(SecureUtil.decode(key), null); + } + + } + + /** * 是否转换为string *