java使用mybatis拦截器对数据库敏感字段进行加密存储并解密
生活随笔
收集整理的這篇文章主要介紹了
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 @SensitiveClass public 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 "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/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 @Slf4j public 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}) }) @Component public 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攔截器對指定敏感字段進行加密解密的過程。
總結
以上是生活随笔為你收集整理的java使用mybatis拦截器对数据库敏感字段进行加密存储并解密的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: tmm-jellyfin转移演员照片(解
- 下一篇: html静态网站简单的学生网页作业源码