4 changed files with 115 additions and 8 deletions
-
39src/main/java/com/spring/common/typehandler/EncryptTypeHandler.java
-
66src/main/java/com/spring/common/utils/AesUtils.java
-
4src/main/java/com/spring/modules/sys/entity/SysUserEntity.java
@ -0,0 +1,39 @@ |
|||
package com.spring.common.typehandler; |
|||
|
|||
import com.spring.common.utils.AesUtils; |
|||
import org.apache.ibatis.type.BaseTypeHandler; |
|||
import org.apache.ibatis.type.JdbcType; |
|||
import org.apache.ibatis.type.MappedTypes; |
|||
|
|||
import java.sql.CallableStatement; |
|||
import java.sql.PreparedStatement; |
|||
import java.sql.ResultSet; |
|||
import java.sql.SQLException; |
|||
|
|||
/** |
|||
* MyBatis TypeHandler:在 SQL 层面自动加解密字符串字段。 |
|||
* 写入数据库时加密,读取时解密,Java 层始终操作明文。 |
|||
*/ |
|||
@MappedTypes(String.class) |
|||
public class EncryptTypeHandler extends BaseTypeHandler<String> { |
|||
|
|||
@Override |
|||
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { |
|||
ps.setString(i, AesUtils.encrypt(parameter)); |
|||
} |
|||
|
|||
@Override |
|||
public String getNullableResult(ResultSet rs, String columnName) throws SQLException { |
|||
return AesUtils.decrypt(rs.getString(columnName)); |
|||
} |
|||
|
|||
@Override |
|||
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { |
|||
return AesUtils.decrypt(rs.getString(columnIndex)); |
|||
} |
|||
|
|||
@Override |
|||
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { |
|||
return AesUtils.decrypt(cs.getString(columnIndex)); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
package com.spring.common.utils; |
|||
|
|||
import javax.crypto.Cipher; |
|||
import javax.crypto.spec.SecretKeySpec; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.Base64; |
|||
|
|||
/** |
|||
* AES对称加密工具类,用于IFS密码等需要可逆加密的场景。 |
|||
* 已加密的值以 "ENC:" 前缀标识,保持对旧明文数据的向下兼容。 |
|||
*/ |
|||
public class AesUtils { |
|||
|
|||
/** 16字节 = 128位 AES密钥,生产环境建议通过配置文件注入 */ |
|||
private static final String AES_KEY = "PLMIfsKey@202501"; |
|||
private static final String ALGORITHM = "AES/ECB/PKCS5Padding"; |
|||
private static final String ENC_PREFIX = "ENC:"; |
|||
|
|||
/** |
|||
* 加密明文,返回带 ENC: 前缀的密文字符串 |
|||
*/ |
|||
public static String encrypt(String plaintext) { |
|||
if (plaintext == null || plaintext.isEmpty()) { |
|||
return plaintext; |
|||
} |
|||
if (plaintext.startsWith(ENC_PREFIX)) { |
|||
return plaintext; |
|||
} |
|||
try { |
|||
SecretKeySpec keySpec = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES"); |
|||
Cipher cipher = Cipher.getInstance(ALGORITHM); |
|||
cipher.init(Cipher.ENCRYPT_MODE, keySpec); |
|||
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); |
|||
return ENC_PREFIX + Base64.getEncoder().encodeToString(encrypted); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException("AES加密失败", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 解密带 ENC: 前缀的密文,返回明文。 |
|||
* 若值不含前缀(旧明文数据),直接原样返回,保持向下兼容。 |
|||
*/ |
|||
public static String decrypt(String ciphertext) { |
|||
if (ciphertext == null || ciphertext.isEmpty()) { |
|||
return ciphertext; |
|||
} |
|||
if (!ciphertext.startsWith(ENC_PREFIX)) { |
|||
return ciphertext; |
|||
} |
|||
try { |
|||
String base64 = ciphertext.substring(ENC_PREFIX.length()); |
|||
SecretKeySpec keySpec = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES"); |
|||
Cipher cipher = Cipher.getInstance(ALGORITHM); |
|||
cipher.init(Cipher.DECRYPT_MODE, keySpec); |
|||
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(base64)); |
|||
return new String(decrypted, StandardCharsets.UTF_8); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException("AES解密失败", e); |
|||
} |
|||
} |
|||
|
|||
public static boolean isEncrypted(String value) { |
|||
return value != null && value.startsWith(ENC_PREFIX); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue