注解实现前后端交互数据的加密和解密
在实际开发中,用户的一些形如密码、手机号等敏感信息需要进行安全传输和保密,从而保护用户的个人数据安全。比如密文存储、敏感数据加密传输等。本文以注解的方式,实现对数据的加密和加密。 代码结构如下:
cipher
├─ annoation
│ ├─ CipherDecrypt.java
│ ├─ CipherEncrypt.java
│ └─ CipherField.java
├─ CipherAbstract.java
├─ CipherAspect.java
├─ constant
│ └─ CipherRange.java
└─ impl
├─ CipherStringByBase64.java
├─ CipherStringByRsa.java
└─ CipherStringByTest.jav
一、公共类
CipherRange
/**
* @author 摆渡人
* @description 密文处理范围
*/
public enum CipherRange {
/**
* 只针对请求的数据,进行加密或解密
*/
REQUEST,
/**
* 只针对响应的数据。进行加密或解密
*/
RESPONSE
}
这个可以定义加密注解或解密注解是在请求时还是在响应时触发。
二、定义声明注解
CipherDecrypt
声明对数据进行解密,需要定义解密的实现算法。
import java.lang.annotation.*;
/**
* @author 摆渡人
* @description 密文解密声明
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CipherDecrypt {
/**
* 解密的操作范围
* @return CipherRange
*/
CipherRange cipherRange() default CipherRange.RESPONSE;
/**
* 加密或解密的实现算法,优先级低于@CipherField
* @return Class<? extends CipherAbstract>
*/
Class<? extends CipherAbstract> using();
}
CipherEncrypt
声明对数据进行加密,需要定义加密的实现算法。
import java.lang.annotation.*;
/**
* @author 摆渡人
* @description 密文加密声明
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CipherEncrypt {
/**
* 加密的操作范围
* @return CipherRange
*/
CipherRange cipherRange() default CipherRange.REQUEST;
/**
* 加密或解密的实现算法,优先级低于@CipherField
* @return Class<? extends CipherAbstract>
*/
Class<? extends CipherAbstract> using();
}
CipherField
声明对哪些字段进行密文操作。
import java.lang.annotation.*;
/**
*
* 只对基本类型或String生效
* 若是一个集合、或一个对象,需在该字段是加上此注解
* 此时,对象或集合的每一个对象,会找到有此注解的基本类型或string,并进行加密或解密操作
*
* @author 摆渡人
* @description 密文字段声明
* @date 2023/6/29 16:04
*/
@Target({ElementType.FIELD, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CipherField {
/**
* 加密或解密的实现算法
* 优先级: CipherField.using() > CipherDecrypt.using() = CipherEncrypt.using()
* @return Class<? extends CipherAbstract>
*/
Class<? extends CipherAbstract> using() default CipherAbstract.None.class;
}
三、抽象类CipherAbstract<T>
实现该抽象类,从而自定义加密、解密算法
/**
* @author 摆渡人
* @description 抽象类,自定义密文算法。
*/
public abstract class CipherAbstract<T> {
public CipherAbstract() {
}
/**
* 加密
* @param value 原数据
* @return 加密后的数据
*/
public abstract T encrypt(T value);
/**
* 解密
* @param value 原数据
* @return 解密后的数据
*/
public abstract T decrypt(T value);
/**
* 默认,不加密/不解密,直接返回原数据
*/
public abstract static class None extends CipherAbstract<Object> {
private None() {
}
}
}
四、切面CipherAspect
/**
* @author 摆渡人
* @description 密文切面
*/
@Order(-100)
@Aspect
@Component
@Slf4j
public class CipherAspect {
/**
* 加密切点
*/
@Pointcut("@annotation(cipher.annoation.CipherEncrypt)")
public void encryptPointCut() {}
/**
* 解密切点
*/
@Pointcut("@annotation(cipher.annoation.CipherDecrypt)")
public void decryptPointCut() {}
/**
* 加密切点执行
* @param point 切点
* @param encrypt 加密注解
* @return Object
*/
@Around("encryptPointCut() && @annotation(encrypt)")
public Object encryptAround(ProceedingJoinPoint point, CipherEncrypt encrypt) {
Object response = null;
CipherRange cipherRange = encrypt.cipherRange();
try {
if(CipherRange.REQUEST.equals(cipherRange)) {
// 对请求数据进行加密
Object[] args = point.getArgs();
if(args != null && args.length != 0) {
Arrays.stream(args).forEach(f -> cipherObject(f,true,encrypt.using()));
}
}
response = point.proceed();
if(CipherRange.RESPONSE.equals(cipherRange)) {
// 对响应数据进行加密
cipherObject(response,true,encrypt.using());
}
} catch (Throwable e) {
return ResponseDTO.userErrorParam("加密失败[{}]".replaceAll("\\{}",e.getMessage()));
}
return response;
}
/**
* 解密切点执行
* @param point 切点
* @param decrypt 解密注解
* @return Object
*/
@Around("decryptPointCut() && @annotation(decrypt)")
public Object decryptAround(ProceedingJoinPoint point, CipherDecrypt decrypt) {
Object response = null;
CipherRange cipherRange = decrypt.cipherRange();
try {
if(CipherRange.REQUEST.equals(cipherRange)) {
// 对请求数据进行解密
Object[] args = point.getArgs();
if(args != null && args.length != 0) {
Arrays.stream(args).forEach(f -> cipherObject(f,false,decrypt.using()));
}
}
response = point.proceed();
if(CipherRange.RESPONSE.equals(cipherRange)) {
// 对响应数据进行解密
cipherObject(response,false,decrypt.using());
}
} catch (Throwable e) {
return ResponseDTO.userErrorParam("解密失败[{}]".replaceAll("\\{}",e.getMessage()));
}
return response;
}
/**
* 对单个object进行密文操作
* @param object 数据
* @param encryptFlag true:加密 false:解密
* @param fatherCipher CipherAbstract<T>的实现类
*/
private void cipherObject(Object object,boolean encryptFlag,Class<? extends CipherAbstract> fatherCipher) {
if(object == null) {
return;
}
Field[] fields = object.getClass().getDeclaredFields();
//遍历字段,找出需要解密的字段
Arrays.stream(fields).filter(field -> field != null && field.isAnnotationPresent(CipherField.class)).forEach(field -> {
// accessible 标志被设置为true,那么反射对象在使用的时候,不会去检查Java语言权限控制(private之类的)
field.setAccessible(true);
try {
// 获取字段值明文
Object plaintext = field.get(object);
if(plaintext == null) {
return;
}
// 符合条件,进行密文操作
if(isPrimitive(plaintext)) {
CipherField cipherField = field.getAnnotation(CipherField.class);
Class<? extends CipherAbstract> sonAbstract = cipherField.using();
if(sonAbstract == CipherAbstract.None.class){
if(fatherCipher == CipherAbstract.None.class) {
return;
}
CipherAbstract fatherInstance = fatherCipher.newInstance();
Object ciphertext = encryptFlag ? fatherInstance.encrypt(plaintext) : fatherInstance.decrypt(plaintext);
field.set(object,ciphertext);
return;
}
CipherAbstract sonInstance = sonAbstract.newInstance();
Object ciphertext = encryptFlag ? sonInstance.encrypt(plaintext) : sonInstance.decrypt(plaintext);
field.set(object,ciphertext);
return;
}
// 获取密文,并重新赋值
if(plaintext instanceof Collection) {
// 情况1:集合
Collection<?> collection = (Collection<?>) plaintext;
collection.forEach(f -> cipherObject(f,encryptFlag,fatherCipher));
} else if (plaintext instanceof Array) {
// 情况2:数组
int length = Array.getLength(plaintext);
for(int i = 0; i < length; i ++) {
Object arrayValue = Array.get(plaintext, i);
cipherObject(arrayValue,encryptFlag,fatherCipher);
}
} else if(plaintext instanceof Map) {
// 情况三:map
Map<?,?> map = (Map<?, ?>) plaintext;
map.forEach((k,v) -> cipherObject(v,encryptFlag,fatherCipher));
} else {
// 情况五: 对象
cipherObject(plaintext,encryptFlag,fatherCipher);
}
} catch (Exception e) {
log.error("对单个object进行密文操作发生异常[{}]",e.getMessage());
throw new RuntimeException(e.getMessage());
}
});
}
/**
* 判断是否是基本数据类型或String类型
* @param obj
* @return
*/
public boolean isPrimitive(Object obj) {
return obj instanceof Byte ||
obj instanceof Short ||
obj instanceof Integer ||
obj instanceof Long ||
obj instanceof Float ||
obj instanceof Double ||
obj instanceof Character ||
obj instanceof Boolean ||
obj instanceof String;
}
}
千万不要忘了修改这两个地方:
/**
* 加密切点
*/
@Pointcut("@annotation(net.lab1024.sa.common.module.support.cipher.annoation.CipherEncrypt)")
public void encryptPointCut() {}
/**
* 解密切点
*/
@Pointcut("@annotation(net.lab1024.sa.common.module.support.cipher.annoation.CipherDecrypt)")
public void decryptPointCut() {}
五、实现样例:RSA
源码参考RSA 加解密(Java 实现)
/**
* @author 摆渡人
* @description 基于rsa算法对String类型的进行密文操作
* @date 2023/7/5 17:56
*/
@Slf4j
public class CipherStringByRsa extends CipherAbstract<String> {
private final String PRIVATE = "";//私钥
private final String PUBLIC = "";//公钥
@Override
public String encrypt(String value) {
return encrypt(value,PUBLIC);
}
@Override
public String decrypt(String value) {
return decrypt(value,PRIVATE);
}
public static final String KEY_ALGORITHM = "RSA";
private static final String PUBLIC_KEY = "RSAPublicKey";
private static final String PRIVATE_KEY = "RSAPrivateKey";
// 1024 bits 的 RSA 密钥对,最大加密明文大小
private static final int MAX_ENCRYPT_BLOCK = 117;
// 1024 bits 的 RSA 密钥对,最大解密密文大小
private static final int MAX_DECRYPT_BLOCK = 128;
// 生成密钥对
public Map<String, Object> initKey(int keysize) throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
// 设置密钥对的 bit 数,越大越安全
keyPairGen.initialize(keysize);
KeyPair keyPair = keyPairGen.generateKeyPair();
// 获取公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
// 获取私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
// 获取公钥字符串
public String getPublicKeyStr(Map<String, Object> keyMap) {
// 获得 map 中的公钥对象,转为 key 对象
Key key = (Key) keyMap.get(PUBLIC_KEY);
// 编码返回字符串
return encryptBASE64(key.getEncoded());
}
// 获取私钥字符串
public String getPrivateKeyStr(Map<String, Object> keyMap) {
// 获得 map 中的私钥对象,转为 key 对象
Key key = (Key) keyMap.get(PRIVATE_KEY);
// 编码返回字符串
return encryptBASE64(key.getEncoded());
}
// 获取公钥
public PublicKey getPublicKey(String publicKeyString) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] publicKeyByte = Base64.getDecoder().decode(publicKeyString);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyByte);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePublic(keySpec);
}
// 获取私钥
public PrivateKey getPrivateKey(String privateKeyString) throws Exception {
byte[] privateKeyByte = Base64.getDecoder().decode(privateKeyString);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyByte);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
}
/**
* BASE64 编码返回加密字符串
*
* @param key 需要编码的字节数组
* @return 编码后的字符串
*/
public String encryptBASE64(byte[] key) {
return new String(Base64.getEncoder().encode(key));
}
/**
* BASE64 解码,返回字节数组
*
* @param key 待解码的字符串
* @return 解码后的字节数组
*/
public byte[] decryptBASE64(String key) {
return Base64.getDecoder().decode(key);
}
/**
* 公钥加密
*
* @param text 待加密的明文字符串
* @param publicKeyStr 公钥
* @return 加密后的密文
*/
public String encrypt(String text, String publicKeyStr) {
try {
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKeyStr));
byte[] tempBytes = cipher.doFinal(text.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(tempBytes);
} catch (Exception e) {
log.error("公钥加密失败[{}]",e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
/**
* 私钥解密
*
* @param secretText 待解密的密文字符串
* @param privateKeyStr 私钥
* @return 解密后的明文
*/
public String decrypt(String secretText, String privateKeyStr) {
try {
// 生成私钥
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKeyStr));
// 密文解码
byte[] secretTextDecoded = Base64.getDecoder().decode(secretText.getBytes("UTF-8"));
byte[] tempBytes = cipher.doFinal(secretTextDecoded);
return new String(tempBytes);
} catch (Exception e) {
log.error("私钥解密失败[{}]",e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
}
这里填入生成的公钥和密钥。公钥交由前端,前端根据需求,对特定字段的数据,利用公钥进行密文操作。密钥保存在服务端,且千万不可泄漏。
private final String PRIVATE =
private final String PUBLIC =
测试案例
1.定义接口
@CipherDecrypt(cipherRange = CipherRange.REQUEST,using = CipherStringByRsa.class)
@NoNeedLogin
@PostMapping("/login")
@ApiOperation("登录")
public ResponseDTO<LoginEmployeeDetail> login(@Valid @RequestBody LoginForm loginForm) {
log.info("收到的数据体:{}",loginForm);
return ResponseDTO.ok();
}
@Data
public class LoginForm {
@ApiModelProperty("登录名")
@NotBlank(message = "登录名不能为空")
@Length(max = 1024, message = "登录账号最多1024字符")
@CipherField
private String loginName;
@ApiModelProperty("密码")
@NotBlank(message = "密码不能为空")
@CipherField
private String password;
}
2.生成密钥
@Test
public void initRsa() throws Exception{
CipherStringByRsa cipherStringByRsa = new CipherStringByRsa();
Map<String, Object> stringObjectMap = cipherStringByRsa.initKey(1024);
log.info("公钥:{}",cipherStringByRsa.getPublicKeyStr(stringObjectMap));
log.info("私钥:{}",cipherStringByRsa.getPrivateKeyStr(stringObjectMap));
}
[INFO ][main] 公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZpJ+rK23/Ys3GWfRQS3RFvioGFFn6RFsMw1FEY3xsv6Y9lTIhSty/CP154CfO90N4bmIDijkAHu/kmCHcGfJdfCys3MH+VlptVeCuLUOOu0H14jek+NuUurxY5jFT4vYGF3nx4x0skLCbOyfPhnnQNfqSN26DpmKJKT/GIkrQPwIDAQAB (CipherTest.java:26)
[INFO ][main] 私钥:MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANmkn6srbf9izcZZ9FBLdEW+KgYUWfpEWwzDUURjfGy/pj2VMiFK3L8I/XngJ873Q3huYgOKOQAe7+SYIdwZ8l18LKzcwf5WWm1V4K4tQ467QfXiN6T425S6vFjmMVPi9gYXefHjHSyQsJs7J8+GedA1+pI3boOmYokpP8YiStA/AgMBAAECgYARC9jJokwqPYXabD87br17nOpTsZJejK4I8N9TmOss5VHS7vWPL/rcJ4sa2ZikmC87poGcv125ibLGV7gHMi0OSmlJ2tk2vbGfCIYf1cKwUQiQRfvoqHjcUMvWunCtH2OouNrpZ2wUTv8YMWUB4QiGlntov/xZPvHOInYgeyMw4QJBAPBHHUDklYfM32ljQfrqFYs62WKnws1KNKWxDm5jDE67zqGW3XSC8yj+kxwx9UQAtFPsF+xrPW6b0oIYxbbok48CQQDn4lsV2gnaQU/GOqyWxLKBht8gcccerLdtdj48L9qNdJzglZ0Rg76ptSoPcJPJXEBKSsNWeo9RTxA2U2RNeeBRAkEA1rsnKJxgCfoM2zlKePwD/ua76c/1ZtPfUwh11i8Mi7yJwJ18EikJOGSygesfvwNGJQ4En1ODljsJmRycbYdSfwJBANwBo7Ij4V02wl2tpbqWfVbvhBvgV6/+5znwCjRIou+7SGy9Bv1rJicWQAXOJinSYR0buiH+vqPCg/tHO89WZ0ECQQC50PRcMBpW7FLOMMgt0kRvhOs/Cdfw6IiLwYrSG42vTFUb7jH5QPpaC/n4lb0CthORoQJdEm61tA7rdjlY8rH3 (CipherTest.java:27)
3.公钥加密
我们假设账号密码为:
{
"loginName": "loginName",
"password": "password"
}
经过加密后可得到:
@Test
public void testEncrypt() {
CipherStringByRsa cipherStringByRsa = new CipherStringByRsa();
log.info("加密后的账号为:{}",cipherStringByRsa.encrypt("loginName"));
log.info("加密后的密码为:{}",cipherStringByRsa.encrypt("password"));
}
[INFO ][main] 加密后的账号为:zmUAo699M0BsYwko1R3htnaNDbRvg3W7Kri8vaWYZIXRMKv2j45/F1GPTK2sJXMhGZqz29LpzMiDLiuYAMZCoAG01uaD/zJTzJkIgYgxQ4G/Mgj3L2ezRBGPUXaAGjgk5l0fqwJShuUopa1YQqPlfMXx1mlMFF+YQGhQhABuKkw= (CipherTest.java:33)
[INFO ][main] 加密后的密码为:AJFpCyUZF2LXdPfZUExm8og1JYzzdVFgkU5gBqOPUTQ2CNxRJUW7j8cboxtaAUi9yyTUBPBszQM6QmWhUSAghPVHTMHd4eaEybzTZ/y0CfDo19nwcuYMRwSbKPovpZEA2YiVV/3IjFad3VGlvZiJNnY8uo2sMsDPs2Y8GBPVTE8= (CipherTest.java:34)
4.发送请求
请求体如下:
{
"loginName": "zmUAo699M0BsYwko1R3htnaNDbRvg3W7Kri8vaWYZIXRMKv2j45/F1GPTK2sJXMhGZqz29LpzMiDLiuYAMZCoAG01uaD/zJTzJkIgYgxQ4G/Mgj3L2ezRBGPUXaAGjgk5l0fqwJShuUopa1YQqPlfMXx1mlMFF+YQGhQhABuKkw=",
"password": "AJFpCyUZF2LXdPfZUExm8og1JYzzdVFgkU5gBqOPUTQ2CNxRJUW7j8cboxtaAUi9yyTUBPBszQM6QmWhUSAghPVHTMHd4eaEybzTZ/y0CfDo19nwcuYMRwSbKPovpZEA2YiVV/3IjFad3VGlvZiJNnY8uo2sMsDPs2Y8GBPVTE8="
}
服务端收到的数据体为:
[INFO ] 收到的数据体:LoginForm(loginName=loginName, password=password)
服务端利用密钥解密后得到的数据与根据公钥加密前的数据一致,案例通过。