1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > java使用mybatis拦截器对数据库敏感字段进行加密存储并解密

java使用mybatis拦截器对数据库敏感字段进行加密存储并解密

时间:2023-04-01 12:47:44

相关推荐

java使用mybatis拦截器对数据库敏感字段进行加密存储并解密

记录业务中遇到的使用场景:灵活对数据库敏感字段进行加密和解密

文章目录

前言一、创建数据库表和实体类二、Mapper、Service、Controller等三、自定义注解四、加密工具类五、参数拦截器和结果集拦截器六、运行结果总结

前言

项目中遇到一个需求,要对指定的数据库表中的敏感字段进行加密存储,读取的时候再进行解密返回给前端,以下对具体的实现过程进行记录和解释。

一、创建数据库表和实体类

数据库表:

CREATE TABLE `sys_user` (`user_id` bigint(20) NOT NULL AUTO_INCREMENT,`username` varchar(60) DEFAULT NULL,`password` varchar(80) DEFAULT NULL,`address` varchar(80) DEFAULT NULL,`hobby` varchar(80) DEFAULT NULL,PRIMARY KEY (`user_id`)) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

实体类:

@Data@Accessors(chain = true)@ToString@SensitiveClasspublic class SysUser {private Long userId;private String username;@EncryptFieldprivate String password;@EncryptFieldprivate String address;private String hobby;}

二、Mapper、Service、Controller等

SysUserMapper.java

public interface SysUserMapper {List<SysUser> listAll();SysUser getById(Long userId);int insert(SysUser sysUser);int update(SysUser sysUser);}

SysUserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-////DTD Mapper 3.0//EN""/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.fanchen.mapper.SysUserMapper"><insert id="insert" parameterType="SysUser">insert into sys_user<trim prefix="(" suffix=")" suffixOverrides=","><if test="username != null and username != ''">username,</if><if test="password != null and password != ''">password,</if><if test="address != null and address != ''">address,</if><if test="hobby != null and hobby != ''">hobby,</if></trim><trim prefix="values (" suffix=")" suffixOverrides=","><if test="username != null and username != ''">#{username},</if><if test="password != null and password != ''">#{password},</if><if test="address != null and address != ''">#{address},</if><if test="hobby != null and hobby != ''">#{hobby},</if></trim></insert><update id="update" parameterType="SysUser">update sys_user<trim prefix="SET" suffixOverrides=","><if test="username != null and username != ''">username = #{username},</if><if test="password != null and password != ''">password = #{password},</if><if test="address != null and address != ''">address = #{address},</if><if test="hobby != null and hobby != ''">hobby = #{hobby},</if></trim>where user_id = #{userId}</update><select id="listAll" resultType="SysUser">select user_id, username, password, address, hobby from sys_user</select><select id="getById" resultType="SysUser" parameterType="long">select user_id, username, password, address, hobby from sys_user where user_id = #{userId}</select></mapper>

这里省略Service和Controller,没啥好写的。

三、自定义注解

EncryptField表示需要加密的字段,加在实体类参数上

@Inherited@Target({ElementType.FIELD,ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)public @interface EncryptField {}

SensitiveClass标识需要加密的实体类,加载实体类上

@Inherited@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface SensitiveClass {}

四、加密工具类

编写拦截器中需要用到的加密解密的工具类

public class EncryptUtil {public static Object encrypt(Object param) throws Exception {Class<?> paramClass = param.getClass();if (String.class == paramClass) {return AESUtil.aesDecrypt((String) param);} else {Field[] fields = paramClass.getDeclaredFields();for (Field field : fields) {EncryptField encryptField = field.getAnnotation(EncryptField.class);if (Objects.nonNull(encryptField)) {field.setAccessible(true);Object fieldInstance = field.get(param);if (fieldInstance instanceof String) {field.set(param, AESUtil.aesEncrypt((String) fieldInstance));}}}}return param;}public static Object decrypt(Object result) throws Exception {Class<?> resultClass = result.getClass();Field[] fields = resultClass.getDeclaredFields();for (Field field : fields) {EncryptField annotation = field.getAnnotation(EncryptField.class);if (Objects.nonNull(annotation)){field.setAccessible(true);Object fieldInstance = field.get(result);if (fieldInstance instanceof String){field.set(result, AESUtil.aesDecrypt((String) fieldInstance));}}}return result;}}

五、参数拦截器和结果集拦截器

参数拦截器,对敏感参数进行加密存储,目前只针对String类型进行加密解密

@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),})@Component@Slf4jpublic class ParameterInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {//@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler// 若指定ResultSetHandler,这里则能强转为ResultSetHandlerDefaultParameterHandler parameterHandler = (DefaultParameterHandler) invocation.getTarget();// 获取参数对像,即 mapper 中 paramsType 的实例Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");parameterField.setAccessible(true);// 取出实例Object parameterObject = parameterField.get(parameterHandler);try {// 搜索该方法中是否有需要加密的字段List<String> paramNames = searchParamAnnotation(parameterHandler);if (parameterObject != null) {if (!CollectionUtils.isEmpty(paramNames)) {PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];//改写参数processParam(parameterObject, paramNames);//改写的参数设置到原parameterHandler对象parameterField.set(parameterHandler, parameterObject);parameterHandler.setParameters(ps);}}} catch (Exception e) {log.error(e.getMessage());}return invocation.proceed();}private List<String> searchParamAnnotation(ParameterHandler parameterHandler) throws Exception {Class<DefaultParameterHandler> handlerClass = DefaultParameterHandler.class;Field mappedStatementFiled = handlerClass.getDeclaredField("mappedStatement");mappedStatementFiled.setAccessible(true);MappedStatement mappedStatement = (MappedStatement) mappedStatementFiled.get(parameterHandler);String methodName = mappedStatement.getId();// 获取Mapper类对象Class<?> mapperClass = Class.forName(methodName.substring(0, methodName.lastIndexOf('.')));methodName = methodName.substring(methodName.lastIndexOf('.') + 1);Method[] methods = mapperClass.getDeclaredMethods();Method method = null;for (Method m : methods) {if (m.getName().equals(methodName)) {method = m;break;}}List<String> paramList = new ArrayList<>();if (method != null) {Annotation[][] pa = method.getParameterAnnotations();Parameter[] parameters = method.getParameters();for (int i = 0; i < pa.length; i++) {Parameter parameter = parameters[i];String typeName = parameter.getParameterizedType().getTypeName();// 去除泛型导致的ClassNotFoundExceptionClass<?> parameterClass = Class.forName(typeName.contains("<") ? typeName.substring(0, typeName.indexOf("<")) : typeName);SensitiveClass sensitiveClass = AnnotationUtils.findAnnotation(parameterClass, SensitiveClass.class);if (Objects.nonNull(sensitiveClass)) {// 该类有字段需要被加密// 多个参数时 parameterObject 为 MapperMethod.ParamMap,其中的参数key为param1、param2...paramList.add("param" + (i + 1));} else {// 如果类上没有注解,那么就判断参数是否有注解for (Annotation annotation : pa[i]) {if (annotation instanceof EncryptField) {if (parameterClass == String.class) {// 目前只针对String加密paramList.add("param" + (i + 1));}}}}}}return paramList;}private void processParam(Object parameterObject, List<String> params) throws Exception {if (parameterObject instanceof MapperMethod.ParamMap) {//多个参数MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameterObject;for (String paramName : params) {Object param = paramMap.get(paramName);paramMap.put(paramName, EncryptUtil.encrypt(param));}} else {//单个参数parameterObject = EncryptUtil.encrypt(parameterObject);}}}

结果集拦截器,对敏感参数进行解密,目前只针对String类型进行加密解密

@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})@Componentpublic class ResultSetInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 取出查询的结果Object resultObject = invocation.proceed();if (Objects.isNull(resultObject)) {return null;}// 基于selectListif (resultObject instanceof List<?>) {List<?> resultList = (List<?>) resultObject;if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {for (Object obj : resultList) {EncryptUtil.decrypt(obj);}}} else {if (needToDecrypt(resultObject)) {EncryptUtil.decrypt(resultObject);}}return resultObject;}private boolean needToDecrypt(Object object) {Class<?> objectClass = object.getClass();SensitiveClass sensitiveClass = AnnotationUtils.findAnnotation(objectClass, SensitiveClass.class);return Objects.nonNull(sensitiveClass);}}

六、运行结果

以上为添加结果

以上为查询结果

总结

以上就是使用mybatis拦截器对指定敏感字段进行加密解密的过程。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。