为防止数据库泄露,表里的敏感字段被曝光,需要对用户的重要数据做加密存取。
选择加密算法: 首先,你需要选择适合你的需求的加密算法。一些常见的加密算法包括AES、RSA、SHA等。具体的选择取决于你要加密的数据和安全需求。
引入加密库: 在你的Spring Boot项目中,引入适用于你所选加密算法的库。例如,如果你选择使用AES加密,可以引入Java的javax.crypto库。
创建加密服务: 创建一个用于加密和解密的服务类。这个服务类应该包含加密和解密方法,并将其注入到Spring容器中以供使用。
定义数据库实体类: 在数据库实体类中,将需要加密的字段定义为加密前的原始字段。在将数据保存到数据库之前,使用加密服务对这些字段进行加密。
加密和解密数据: 在业务逻辑中,调用加密服务对需要加密的数据进行加密,并在需要时进行解密。确保加密密钥的安全存储,不要将密钥硬编码在代码中。
定义接口文件--------------------------------------------------
DecryptField.java :
package xxx.xxx
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.ElementType;
//解密字段注解
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
}
EncryptField .java :
//解密字段注解
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
}
NeedDecrypt .java :
//作用于对返回值进行解密处理的方法上
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedDecrypt {
}
NeedEncrypt .java :
//作用于对参数进行加密处理的方法上
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedEncrypt {
}
AesSupport.java 加密解密算法------------------------------------------
public class AesSupport{
private static final String KEY_ALGORITHM = "AES";
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
private static final String AES_KEY = "you aes key"; // 16/24/32-character secret key
private String password;
private SecretKeySpec secretKeySpec;
private static AesSupport instance;
// 私有构造函数,防止外部创建实例
private AesSupport() {
}
// 提供获取单例实例的方法,使用双重检查锁定保证线程安全
public static AesSupport getInstance() throws NoSuchAlgorithmException {
if (instance == null) {
synchronized (AesSupport.class) {
if (instance == null) {
instance = new AesSupport(AES_KEY);
}
}
}
return instance;
}
public AesSupport(String password) throws NoSuchAlgorithmException {
if(StringUtils.isEmpty(password)){
throw new IllegalArgumentException("password should not be null!");
}
this.password = password;
this.secretKeySpec = getSecretKey(password);
}
public String encrypt(String value){
try{
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] content = value.getBytes("UTF-8");
byte[] encryptData = cipher.doFinal(content);
return Hex.bytesToHexString(encryptData);
}catch (Exception e){
throw new IllegalStateException("AES encrypt "+e.getMessage(),e);
}
}
public String decrypt(String value){
if (StringUtils.isEmpty(value)) {
return "";
}
try {
byte[] encryptData = Hex.hexStringToBytes(value);
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] content = cipher.doFinal(encryptData);
return new String(content, "UTF-8");
}catch (Exception e){
throw new IllegalStateException("AES decrypt "+e.getMessage(),e);
}
}
private SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException{
KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM);
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(password.getBytes());
kg.init(128, random);
SecretKey secretKey = kg.generateKey();
return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);
}
}
DecryptAop.java 解密Aop文件:
@Component
@Aspect
public class DecryptAop {
/**
* 定义需要解密的切入点
*/
@Pointcut(value = "@annotation(xxx..service.crypt.NeedDecrypt)")
public void pointcut() {
}
/**
* 命中的切入点时的环绕通知
*
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//执行目标方法
Object result = proceedingJoinPoint.proceed();
//判断目标方法的返回值类型
if (result instanceof List) {
for (Object tmp : ((List) result)) {
//数据脱敏处理逻辑
this.deepProcess(tmp);
}
} else {
this.deepProcess(result);
}
//log.info("//环绕通知 end");
return result;
}
public void deepProcess(Object obj) throws IllegalAccessException {
if (obj != null) {
//取出输出对象的所有字段属性,并遍历
Field[] declaredFields = obj.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
//判断字段属性上是否标记DecryptField注解
if (declaredField.isAnnotationPresent(DecryptField.class)) {
//如果判断结果为真,则取出字段属性数据进行解密处理
declaredField.setAccessible(true);
Object valObj = declaredField.get(obj);
if (valObj != null) {
String value = valObj.toString();
//加密数据的解密处理
try {
value = AesSupport.getInstance().decrypt(value);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
DecryptField annotation = declaredField.getAnnotation(DecryptField.class);
//boolean open = annotation.open();
//把解密后的数据重新赋值
declaredField.set(obj, value);
}
}
}
}
}
}
EncryptAop.java 加密Aop文件:
@Component
@Aspect
public class EncryptAop {
/**
* 定义加密切入点
*/
@Pointcut(value = "@annotation(xxx.service.crypt.NeedEncrypt)")
public void pointcut() {
}
/**
* 命中加密切入点的环绕通知
*
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//获取命中目标方法的入参数
Object[] args = proceedingJoinPoint.getArgs();
if (args.length > 0) {
for (Object arg : args) {
//按参数的类型进行判断,如果业务中还有其他的类型,可酌情增加
if (arg != null) {
if (arg instanceof List) {
for (Object tmp : ((List) arg)) {
//加密处理
this.deepProcess(tmp);
}
} else {
this.deepProcess(arg);
}
}
}
}
//对敏感数据加密后执行目标方法
Object result = proceedingJoinPoint.proceed();
return result;
}
public void deepProcess(Object obj) throws IllegalAccessException {
if (obj != null) {
//获取对象的所有字段属性并遍历
Field[] declaredFields = obj.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
//判断字段属性上是否标记了@EncryptField注解
if (declaredField.isAnnotationPresent(EncryptField.class)) {
//如果判断结果为真,则取出字段属性值,进行加密、重新赋值
declaredField.setAccessible(true);
Object valObj = declaredField.get(obj);
if (valObj != null) {
String value = valObj.toString();
//开始敏感字段属性值加密
String decrypt;
try {
decrypt = AesSupport.getInstance().encrypt(value);
//把加密后的字段属性值重新赋值
declaredField.set(obj, decrypt);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
}
通过Aop切入拦截标注了NeedEncrypt 、NeedDecrypt的接口函数,获取到参数字段判断是否标注了DecryptField、EncryptField,对标注的进行加解密操作,最后存储到数据库。
在Bean对象类进行字段标注:
public class User{
private String id;
@EncryptField
@DecryptField
private String name;
@EncryptField
@DecryptField
private String phonenum;
}
在接口方法上进行方法标注:
@NeedEncrypt
public int updateUser(User user) throws Exception {
}
@NeedDecrypt
public List
}
注意不要在接口调用路径上进行重复标注,否则会加密两次,最后导致解密不出来。