首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >MyBatis 配置 typeHandler 敏感字段加解密操作

MyBatis 配置 typeHandler 敏感字段加解密操作

作者头像
oktokeep
发布2025-05-29 08:29:27
发布2025-05-29 08:29:27
23800
代码可运行
举报
文章被收录于专栏:第三方工具第三方工具
运行总次数:0
代码可运行

MyBatis 配置 typeHandler 敏感字段加解密操作

在sqlmap中加解密的逻辑:根据字段值的前缀来区分是做加密还是解密操作: 1. 加密时字段只过滤 `null` 值,明文不做任何处理直接加密 2. 解密时会判断字段是否是加密数据,如果是才会解密否则直接返回原始数据 3. fail fast 模式,当加/解密失败时,立即抛出异常

1.MyBatis JavaType 别名

代码语言:javascript
代码运行次数:0
运行
复制
package com.test.insurdock.encrypt;

import org.apache.ibatis.type.Alias;

@Alias("encrypt")
public class MyEncrypt {

}

package com.test.insurdock.encrypt;

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;


/**
 * 拦截 JavaType 为 #{@link Encrypt} 的 SQL
 * 注意:[关键]
 *   1. 加密时字段只过滤 `null` 值,明文不做任何处理直接加密
 *   2. 解密时会判断字段是否是加密数据,如果是才会解密否则直接返回原始数据
 *   3. fail fast 模式,当加/解密失败时,立即抛出异常
 *
 */
@MappedTypes(MyEncrypt.class)
public class MyEncryptTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
            throws SQLException {
        // 只要 parameter 非空都进行加密
        ps.setString(i, MyEncryptUtil.encrypt(parameter));
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String r = rs.getString(columnName);
        // 兼容待修复的数据
        return r == null ? null : (MyEncryptUtil.isEncrypted(r) ? MyEncryptUtil.decrypt(r) : r);
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String r = rs.getString(columnIndex);
        // 兼容待修复的数据
        return r == null ? null : (MyEncryptUtil.isEncrypted(r) ? MyEncryptUtil.decrypt(r) : r);
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String r = cs.getString(columnIndex);
        // 兼容待修复的数据
        return r == null ? null : (MyEncryptUtil.isEncrypted(r) ? MyEncryptUtil.decrypt(r) : r);
    }
}

2.加密工具类

代码语言:javascript
代码运行次数:0
运行
复制
package com.test.insurdock.encrypt;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyEncryptUtil {
    private static Logger log = LoggerFactory.getLogger(MyEncryptUtil.class);

    private static final String ENC_PREFIX = "ENC$";

    private MyEncryptUtil() {
        throw new UnsupportedOperationException();
    }

    public static boolean isEncrypted(String content) {
        return StringUtils.isNotEmpty(content) && content.startsWith(ENC_PREFIX);
    }

    /**
     * 加密。
     * @param content 明文
     * @return 密文
     */
    public static String encrypt(String content) {
        if (StringUtils.isEmpty(content) || isEncrypted(content)) {
            return content;
        }
        return ENC_PREFIX + MyAES.encrypt(content);
    }

    /**
     * 解密。
     * @param content 密文
     * @return 明文
     */
    public static String decrypt(String content) {
        try {
            if (isEncrypted(content)) {
                return MyAES.decrypt(StringUtils.removeStart(content, ENC_PREFIX));
            }
        } catch (Exception e) {
            log.warn("can't decrypt, turn to raw content : {}", content);
        }
        return content;
    }
}

3.加解密类

代码语言:javascript
代码运行次数:0
运行
复制
package com.test.insurdock.encrypt;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class MyAES {
    private static Logger logger = LoggerFactory.getLogger(MyAES.class);
    private static final String AES_ALG = "AES";

    //java.security.InvalidKeyException: Invalid AES key length: 10 bytes
    //16位
    private static final String AES_KEY = "test567890abcdef";

    private static final String AES_CBC_PCK_ALG = "AES/ECB/PKCS5Padding";


    public static final String CHARSET_UTF8 = "UTF-8";


    public static String encrypt(String srcContent) {
        try {
            String aesStr = aesEncrypt(srcContent, AES_KEY, CHARSET_UTF8);
            return aesStr;
        } catch (Exception e) {
            throw new SecurityException(e);
        }
    }

    public static String decrypt(String aesContent) {
        try {
            return aesDecrypt(aesContent, AES_KEY, CHARSET_UTF8);
        } catch (Exception e) {
            throw new SecurityException(e);
        }
    }

    private static String aesEncrypt(String content, String aesKey, String charset) throws Exception {
        Cipher cipher = Cipher.getInstance(AES_CBC_PCK_ALG);
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey.getBytes(), AES_ALG));
        byte[] encryptBytes = cipher.doFinal(content.getBytes(charset));
        return new String(Base64.getEncoder().encode(encryptBytes));
    }


    private static String aesDecrypt(String content, String key, String charset) throws Exception {
        Cipher cipher = Cipher.getInstance(AES_CBC_PCK_ALG);
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes(), AES_ALG));
        byte[] cleanBytes = cipher.doFinal(Base64.getDecoder().decode(content));
        return new String(cleanBytes, charset);
    }
}

4.sqlmap使用

代码语言:javascript
代码运行次数:0
运行
复制
#查询返回方法,将密文字段解密出来,查询返回给明文的字段。
<resultMap type="com.test.insurdock.model.scooterorder.SysCouponEntity" id="BaseResultMap">
    <result property="renterPlaceMobile" column="enc_renter_place_mobile" typeHandler="com.test.insurdock.encrypt.MyEncryptTypeHandler"/>
    <result property="renterPlaceName" column="enc_renter_place_name" typeHandler="com.test.insurdock.encrypt.MyEncryptTypeHandler"/>
    <result property="renterPlaceCardNo" column="enc_renter_place_card_no" typeHandler="com.test.insurdock.encrypt.MyEncryptTypeHandler"/>
</resultMap>

#添加 insert方法中,将明文中的字段加密存储到密文数据库字段中
<insert id="insert" parameterType="com.test.insurdock.model.scooterorder.SysCouponEntity" useGeneratedKeys="true" keyProperty="id">
        insert into sys_coupon
        (        
                `renter_place_mobile`,        
                `renter_place_name`,        
                `renter_place_card_no`,        
                `enc_renter_place_mobile`,        
                `enc_renter_place_name`,        
                `enc_renter_place_card_no`
            )
        values
        (
                #{renterPlaceMobile},         
                #{renterPlaceName},         
                #{renterPlaceCardNo},         
                #{renterPlaceMobile,typeHandler=com.test.insurdock.encrypt.MyEncryptTypeHandler},
                #{renterPlaceName,typeHandler=com.test.insurdock.encrypt.MyEncryptTypeHandler},
                #{renterPlaceCardNo,typeHandler=com.test.insurdock.encrypt.MyEncryptTypeHandler}
            )
    </insert>
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-05-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档