1、创建特定字段注解
字段注解
java
import java.lang.annotation.*;
@Inherited
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
}
类注解
java
import java.lang.annotation.*;
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveClass {
}
2、创建加密方法
java
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.HashMap;
import java.util.Map;
public class SM2Utils {
/**
* SM2加密算法
*
* @param publicKey 公钥
* @param data 明文数据
* @return
*/
public static String encrypt(String publicKey, String data) {
// 获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
// 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数N
ECDomainParameters domainParameters = new ECDomainParameters(
sm2ECParameters.getCurve(), sm2ECParameters.getG(),
sm2ECParameters.getN());
//提取公钥点
ECPoint pukPoint = sm2ECParameters.getCurve()
.decodePoint(Hex.decode(publicKey));
// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(
pukPoint, domainParameters);
SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
// 设置sm2为加密模式
sm2Engine.init(true,
new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
byte[] arrayOfBytes = null;
try {
byte[] in = data.getBytes();
arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
} catch (Exception e) {
System.out.println("SM2加密时出现异常:" + e.getMessage());
}
return Hex.toHexString(arrayOfBytes);
}
/**
* SM2解密算法
*
* @param privateKey 私钥
* @param cipherData 密文数据
* @return
*/
public static String decrypt(String privateKey, String cipherData) {
// 使用BC库加解密时密文以04开头,传入的密文前面没有04则补上
if (!cipherData.startsWith("04")) {
cipherData = "04" + cipherData;
}
byte[] cipherDataByte = Hex.decode(cipherData);
BigInteger privateKeyD = new BigInteger(privateKey, 16);
//获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
//构造domain参数
ECDomainParameters domainParameters = new ECDomainParameters(
sm2ECParameters.getCurve(), sm2ECParameters.getG(),
sm2ECParameters.getN());
ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(
privateKeyD, domainParameters);
SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
// 设置sm2为解密模式
sm2Engine.init(false, privateKeyParameters);
String result = "";
try {
byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0,
cipherDataByte.length);
return new String(arrayOfBytes);
} catch (Exception e) {
System.out.println("SM2解密时出现异常:" + e.getMessage());
}
return result;
}
//生成密钥
public static Map<String, String> getKey() throws Exception {
ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
// 获取一个椭圆曲线类型的密钥对生成器
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
// 使用SM2参数初始化生成器
kpg.initialize(sm2Spec);
// 获取密钥对
KeyPair keyPair = kpg.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
BCECPublicKey p = (BCECPublicKey) publicKey;
PrivateKey privateKey = keyPair.getPrivate();
BCECPrivateKey s = (BCECPrivateKey) privateKey;
Map<String, String> key = new HashMap<>();
key.put("publicKey", Hex.toHexString(p.getQ().getEncoded(false)));
key.put("privateKey", Hex.toHexString(s.getD().toByteArray()));
return key;
}
public static void main(String[] args) throws Exception {
String publicKey = "042b62ad17c0a3191bc46df6b9d93eaddc64809755a913dcb348da2c1d186f2523077cff9fa52fe9e0da2d3d4f01307397f680589";
String privateKey = "0098dd20430be7810c618201010ec6fc32868a9afd9da8";
String phone = "1358273";
String encryptRes = encrypt(publicKey, phone);
System.out.println("手机号" + phone + "加密:" + encryptRes);
System.out.println("手机号" + phone + "解密:" + decrypt(privateKey, encryptRes));
}
}
3、创建mybatis拦截器
java
import com.xinjian.common.encrypt.EncryptField;
import com.xinjian.common.encrypt.SM2Utils;
import com.xinjian.common.encrypt.SensitiveClass;
import com.xinjian.common.utils.StringUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Component
public class MybatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
//拦截sql结果处理器
if (target instanceof ResultSetHandler) {
return resultDecrypt(invocation);
}
//拦截sql参数处理器
if (target instanceof ParameterHandler) {
return parameterEncrypt(invocation);
}
return invocation.proceed();
}
/**
* 对mybatis映射结果进行字段解密
*
* @param invocation 参数
* @return 结果
* @throws Throwable 异常
*/
private Object resultDecrypt(Invocation invocation) throws Throwable {
//取出查询的结果
Object resultObject = invocation.proceed();
if (Objects.isNull(resultObject)) {
return null;
}
//基于selectList
if (resultObject instanceof ArrayList) {
ArrayList resultList = (ArrayList) resultObject;
if (CollectionUtils.isEmpty(resultList) || !needToDecrypt(resultList.get(0))) {
return resultObject;
}
for (Object result : resultList) {
//逐一解密
decrypt(result);
}
//基于selectOne
} else {
if (needToDecrypt(resultObject)) {
decrypt(resultObject);
}
}
return resultObject;
}
/**
* mybatis映射参数进行加密
*
* @param invocation 参数
* @return 结果
* @throws Throwable 异常
*/
private Object parameterEncrypt(Invocation invocation) throws Throwable {
//@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
//若指定ResultSetHandler ,这里则能强转为ResultSetHandler
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 获取参数对像,即 mapper 中 paramsType 的实例
Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
//取出实例
Object parameterObject = parameterField.get(parameterHandler);
if (null == parameterObject) {
return invocation.proceed();
}
Class<?> parameterObjectClass = parameterObject.getClass();
//校验该实例的类是否被@SensitiveEntity所注解
SensitiveClass sensitiveEntity = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveClass.class);
//未被@SensitiveEntity所注解 则为null
if (Objects.isNull(sensitiveEntity)) {
return invocation.proceed();
}
//取出当前当前类所有字段,传入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
encrypt(declaredFields, parameterObject);
return invocation.proceed();
}
public Object encrypt(Field[] declaredFields, Object paramsObject) throws Exception {
String publicKey = "042b62ad17c0a3191bceaddc64809755a913dcb348da2c1d186f2523077cff9fa52fe9e0da2d3d4f01307397f680589";
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
EncryptField sensitiveField = field.getAnnotation(EncryptField.class);
if (Objects.isNull(sensitiveField)) {
continue;
}
field.setAccessible(true);
Object object = field.get(paramsObject);
//暂时只实现String类型的加密
if (object instanceof String) {
String value = (String) object;
//如果映射字段值为空,并且以==结尾则跳过不进行加密
if (!StringUtils.isNotBlank(value)) {
continue;
}
//加密
field.set(paramsObject, SM2Utils.encrypt(publicKey, value));
}
}
return paramsObject;
}
public Object decrypt(Object result) throws Exception {
String privateKey = "0098dd20430be7818e3e64430c6ec6fc32868a9afd9da8";
//取出resultType的类
Class<?> resultClass = result.getClass();
Field[] declaredFields = resultClass.getDeclaredFields();
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
EncryptField sensitiveField = field.getAnnotation(EncryptField.class);
if (Objects.isNull(sensitiveField)) {
continue;
}
field.setAccessible(true);
Object object = field.get(result);
//只支持String的解密
if (object instanceof String) {
String value = (String) object;
//如果映射字段值为空,并且不已==结尾则跳过不进行解密
if (!StringUtils.isNotBlank(value)) {
continue;
}
//对注解的字段进行逐一解密
field.set(result, SM2Utils.decrypt(privateKey, value));
}
}
return result;
}
/**
* 判断是否包含需要加解密对象
*
* @param object 参数
* @return 结果
*/
private boolean needToDecrypt(Object object) {
Class<?> objectClass = object.getClass();
SensitiveClass sensitiveEntity = AnnotationUtils.findAnnotation(objectClass, SensitiveClass.class);
return Objects.nonNull(sensitiveEntity);
}
@Override
public Object plugin(Object target) {
return Interceptor.super.plugin(target);
}
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}
4、MyBatisConfig配置拦截器
java
import com.xinjian.common.utils.StringUtils;
import com.xinjian.framework.interceptor.MybatisInterceptor;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
/**
* Mybatis支持*匹配扫描包
*
* @author xinjian
*/
@Configuration
public class MyBatisConfig {
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
@Autowired
private Environment env;
@Autowired
private MybatisInterceptor mybatisInterceptor;
public static String setTypeAliasesPackage(String typeAliasesPackage) {
ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
List<String> allResult = new ArrayList<String>();
try {
for (String aliasesPackage : typeAliasesPackage.split(",")) {
List<String> result = new ArrayList<String>();
aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
Resource[] resources = resolver.getResources(aliasesPackage);
if (resources != null && resources.length > 0) {
MetadataReader metadataReader = null;
for (Resource resource : resources) {
if (resource.isReadable()) {
metadataReader = metadataReaderFactory.getMetadataReader(resource);
try {
result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
if (result.size() > 0) {
HashSet<String> hashResult = new HashSet<String>(result);
allResult.addAll(hashResult);
}
}
if (allResult.size() > 0) {
typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
} else {
throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
}
} catch (IOException e) {
e.printStackTrace();
}
return typeAliasesPackage;
}
public Resource[] resolveMapperLocations(String[] mapperLocations) {
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
List<Resource> resources = new ArrayList<Resource>();
if (mapperLocations != null) {
for (String mapperLocation : mapperLocations) {
try {
Resource[] mappers = resourceResolver.getResources(mapperLocation);
resources.addAll(Arrays.asList(mappers));
} catch (IOException e) {
// ignore
}
}
}
return resources.toArray(new Resource[resources.size()]);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
String mapperLocations = env.getProperty("mybatis.mapperLocations");
String configLocation = env.getProperty("mybatis.configLocation");
typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
VFS.addImplClass(SpringBootVFS.class);
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
sessionFactory.setPlugins(mybatisInterceptor);
return sessionFactory.getObject();
}
}
5、加解密类添加注解
java
import com.xinjian.common.annotation.Excel;
import com.xinjian.common.annotation.Excel.ColumnType;
import com.xinjian.common.annotation.Excel.Type;
import com.xinjian.common.annotation.Excels;
import com.xinjian.common.core.domain.BaseEntity;
import com.xinjian.common.encrypt.EncryptField;
import com.xinjian.common.encrypt.SensitiveClass;
import com.xinjian.common.xss.Xss;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.Date;
import java.util.List;
/**
* 用户对象 sys_user
*
* @author xinjian
*/
@SensitiveClass
public class SysUser extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号")
private Long userId;
/**
* 部门ID
*/
@Excel(name = "部门编号", type = Type.IMPORT)
private Long deptId;
/**
* 微信用户ID
*/
private String openid;
/**
* 用户账号
*/
@Excel(name = "登录名称")
private String userName;
/**
* 用户昵称
*/
@Excel(name = "用户名称")
private String nickName;
/**
* 用户邮箱
*/
@Excel(name = "用户邮箱")
private String email;
/**
* 手机号码
*/
@Excel(name = "手机号码")
@EncryptField
private String phonenumber;
/**
* 用户性别
*/
@Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
private String sex;
/**
* 用户头像
*/
private String avatar;
/**
* 密码
*/
private String password;
/**
* 帐号状态(0正常 1停用)
*/
@Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用")
private String status;
/**
* 删除标志(0代表存在 2代表删除)
*/
private String delFlag;
/**
* 最后登录IP
*/
@Excel(name = "最后登录IP", type = Type.EXPORT)
private String loginIp;
/**
* 最后登录时间
*/
@Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
private Date loginDate;
/**
* 部门对象
*/
@Excels({
@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
@Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
})
private SysDept dept;
/**
* 角色对象
*/
private List<SysRole> roles;
/**
* 角色组
*/
private Long[] roleIds;
/**
* 岗位组
*/
private Long[] postIds;
/**
* 角色ID
*/
private Long roleId;
public SysUser() {
}
public SysUser(Long userId) {
this.userId = userId;
}
public static boolean isAdmin(Long userId) {
return userId != null && 1L == userId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public boolean isAdmin() {
return isAdmin(this.userId);
}
public Long getDeptId() {
return deptId;
}
public void setDeptId(Long deptId) {
this.deptId = deptId;
}
@Xss(message = "用户昵称不能包含脚本字符")
@Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
@Xss(message = "用户账号不能包含脚本字符")
@NotBlank(message = "用户账号不能为空")
@Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Email(message = "邮箱格式不正确")
@Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
public String getPhonenumber() {
return phonenumber;
}
public void setPhonenumber(String phonenumber) {
this.phonenumber = phonenumber;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getDelFlag() {
return delFlag;
}
public void setDelFlag(String delFlag) {
this.delFlag = delFlag;
}
public String getLoginIp() {
return loginIp;
}
public void setLoginIp(String loginIp) {
this.loginIp = loginIp;
}
public Date getLoginDate() {
return loginDate;
}
public void setLoginDate(Date loginDate) {
this.loginDate = loginDate;
}
public SysDept getDept() {
return dept;
}
public void setDept(SysDept dept) {
this.dept = dept;
}
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
public Long[] getRoleIds() {
return roleIds;
}
public void setRoleIds(Long[] roleIds) {
this.roleIds = roleIds;
}
public Long[] getPostIds() {
return postIds;
}
public void setPostIds(Long[] postIds) {
this.postIds = postIds;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("userId", getUserId())
.append("deptId", getDeptId())
.append("userName", getUserName())
.append("nickName", getNickName())
.append("email", getEmail())
.append("phonenumber", getPhonenumber())
.append("sex", getSex())
.append("avatar", getAvatar())
.append("password", getPassword())
.append("status", getStatus())
.append("delFlag", getDelFlag())
.append("loginIp", getLoginIp())
.append("loginDate", getLoginDate())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.append("dept", getDept())
.toString();
}
}
6、实现效果