为了保护数据库敏感字段数据安全,有时候我们需要将敏感数据加密入库,查询时再解密成明文。我们可以利用Mybatis自定义TypeHandler来处理,下面我们来具体实现一下。

定义KeyCenterUtils加解密工具类

import org.springframework.stereotype.Service;
import java.util.Base64;

@Service
public class KeyCenterUtils {

    public  String encrypt(String src) {
        try {
            String result = Base64.getEncoder().encodeToString(src.getBytes("UTF-8"));
            return result;
        } catch (Exception e) {
            throw new RuntimeException("encrypt fail!", e);
        }
    }

    public  String decrypt(String src) {
        try {
            byte[] asBytes = Base64.getDecoder().decode(src);
            String result = new String(asBytes, "UTF-8");
            return result;
        } catch (Exception e) {
            throw new RuntimeException("decrypt fail!", e);
        }
    }
}

自定义Handler类实现数据库字段加解密

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.mk.util.KeyCenterUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CustomTypeHandler<T> extends BaseTypeHandler<T> {

    @Autowired
    private KeyCenterUtils keyCenterUtils;

    public CustomTypeHandler() {
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, this.keyCenterUtils.encrypt((String)parameter));
    }
    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String columnValue = rs.getString(columnName);
        //有一些可能是空字符
        return StringUtils.isBlank(columnValue) ? (T)columnValue : (T)this.keyCenterUtils.decrypt(columnValue);
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String columnValue = rs.getString(columnIndex);
        return StringUtils.isBlank(columnValue) ? (T)columnValue : (T)this.keyCenterUtils.decrypt(columnValue);
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String columnValue = cs.getString(columnIndex);
        return StringUtils.isBlank(columnValue) ? (T)columnValue : (T)this.keyCenterUtils.decrypt(columnValue);
    }
}

因为我用的是Mybatis-Plus,所以可以使用Mybatis-Plus的@TableField的注解通过typeHandler属性指定上面自定义的Handler即可。

实体类添加注解

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "lemon_user", autoResultMap = true)
public class User {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String username;

    @TableField(typeHandler = CustomTypeHandler.class)
    private String password;

    private String salt;
}

注意:上面的@TableName注解设置了autoResultMap = true的属性值,这样通过Mybatis-Plus的BaseMapper查询出来的数据才会将加密字段进行解密,默认不生效。

如果不是Mybatis-Plus的 BaseMapper内部的方法,则需要我们在查询时在resultMap的属性中指定我们自定义的typeHandler,如下:

<resultMap id="baseResultMap" type="com.mk.entity.User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password" typeHandler="com.mk.handler.CustomTypeHandler"/>
</resultMap>

<select id="getUserByName" resultMap="baseResultMap">
    select * from lemon_user where username = #{username}
</select>