日常開發(fā)過程中需要對(duì)數(shù)據(jù)庫中敏感字段加解密侣滩,如手機(jī)號(hào)证逻、密碼等數(shù)據(jù),直接在業(yè)務(wù)代碼中進(jìn)行加解密有點(diǎn)冗余找筝,本文采用自定義注解+mybatis攔截器實(shí)現(xiàn);
主要步驟:
1:自定義注解慷吊;
2:實(shí)現(xiàn)mybatis的Interceptor接口袖裕;
3:加解密算法;
MyBatis 允許你在映射語句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用溉瓶。默認(rèn)情況下急鳄,MyBatis 允許使用插件來攔截的方法調(diào)用包括:(官網(wǎng)鏈接https://mybatis.org/mybatis-3/zh/configuration.html#plugins)
// 執(zhí)行
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
// 請(qǐng)求參數(shù)處理
ParameterHandler (getParameterObject, setParameters)
// 返回結(jié)果集處理
ResultSetHandler (handleResultSets, handleOutputParameters)
// SQL語句構(gòu)建
StatementHandler (prepare, parameterize, batch, update, query)
話不多說直接上代碼,整體項(xiàng)目結(jié)構(gòu)如圖所示:
自定義注解堰酿,該注解作用范圍在類上面
package com.mybatis.interceptor.transaction;
import java.lang.annotation.*;
@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {
}
自定義加密注解疾宏,該注解作用范圍在字段上,如密碼等字段
package com.mybatis.interceptor.transaction;
import java.lang.annotation.*;
/**
* 加密注解
*/
@Documented
@Inherited
@Target({ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptTransaction {
}
自定義解密注解触创,該注解作用范圍也在字段上
package com.mybatis.interceptor.transaction;
import java.lang.annotation.*;
/**
* 解密注解
*/
@Documented
@Inherited
@Target({ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptTransaction {
}
加密接口及其實(shí)現(xiàn)類
package com.mybatis.interceptor.handler;
import java.lang.reflect.Field;
public interface EncryptUtil {
/**
* 加密
* @param declaredFields
* @param paramsObject
* @param <T>
* @return
* @throws IllegalAccessException
*/
<T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException;
}
加密實(shí)現(xiàn)類
package com.mybatis.interceptor.handler;
import com.mybatis.interceptor.transaction.EncryptTransaction;
import com.mybatis.interceptor.util.AESUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Objects;
@Component
public class EncryptUtilImpl implements EncryptUtil{
@Autowired
private AESUtil aesUtil;
@Override
public <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
for (Field field : declaredFields) {
//取出所有被EncryptTransaction注解的字段
EncryptTransaction encryptTransaction = field.getAnnotation(EncryptTransaction.class);
if (!Objects.isNull(encryptTransaction)) {
field.setAccessible(true);
Object object = field.get(paramsObject);
//暫時(shí)只實(shí)現(xiàn)String類型的加密
if (object instanceof String) {
String value = (String) object;
//加密
try {
field.set(paramsObject, aesUtil.encrypt(value));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return paramsObject;
}
}
解密接口及其實(shí)現(xiàn)類
package com.mybatis.interceptor.handler;
public interface DecryptUtil {
/**
* 解密
*
* @param result resultType的實(shí)例
* @return T
* @throws IllegalAccessException 字段不可訪問異常
*/
<T> T decrypt(T result) throws IllegalAccessException;
}
解密實(shí)現(xiàn)類
package com.mybatis.interceptor.handler;
import com.mybatis.interceptor.transaction.DecryptTransaction;
import com.mybatis.interceptor.util.AESUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Objects;
@Component
public class DecryptImpl implements DecryptUtil{
@Autowired
private AESUtil aesUtil;
/**
* 解密
* @param result resultType的實(shí)例
* @param <T>
* @return
* @throws IllegalAccessException
*/
@Override
public <T> T decrypt(T result) throws IllegalAccessException {
//取出resultType的類
Class<?> resultClass = result.getClass();
Field[] declaredFields = resultClass.getDeclaredFields();
for (Field field : declaredFields) {
//取出所有被DecryptTransaction注解的字段
DecryptTransaction decryptTransaction = field.getAnnotation(DecryptTransaction.class);
if (!Objects.isNull(decryptTransaction)) {
field.setAccessible(true);
Object object = field.get(result);
//String的解密
if (object instanceof String) {
String value = (String) object;
//對(duì)注解的字段進(jìn)行逐一解密
try {
field.set(result, aesUtil.decrypt(value));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return result;
}
}
具體加解密算法
package com.mybatis.interceptor.util;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
@Component
public class AESUtil {
private static final String defaultV = "6859505890402435";
private static final String key = "1061697007556132";
private static SecretKeySpec getKey(String strKey) throws Exception {
byte[] arrBTmp = strKey.getBytes();
byte[] arrB = new byte[16]; // 創(chuàng)建一個(gè)空的16位字節(jié)數(shù)組(默認(rèn)值為0)
for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) {
arrB[i] = arrBTmp[i];
}
SecretKeySpec skeySpec = new SecretKeySpec(arrB, "AES");
return skeySpec;
}
/**
* 加密
* @param content
* @return
* @throws Exception
*/
public static String encrypt(String content) throws Exception {
final Base64.Encoder encoder = Base64.getEncoder();
SecretKeySpec skeySpec = getKey(key);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(defaultV.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(content.getBytes());
String encodedText = encoder.encodeToString(encrypted);
return encodedText;
}
/**
* 解密
* @param content
* @return
* @throws Exception
*/
public static String decrypt(String content) throws Exception {
final Base64.Decoder decoder = Base64.getDecoder();
SecretKeySpec skeySpec = getKey(key);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(defaultV.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] base64 = decoder.decode(content);
byte[] original = cipher.doFinal(base64);
String originalString = new String(original);
return originalString;
}
}
入?yún)r截器
參考官網(wǎng)坎藐,通過 @Intercepts 和 @Signature 的聯(lián)合使用,@Intercepts 注解開啟攔截器哼绑,@Signature 注解定義攔截器的實(shí)際類型岩馍。指定 ParameterHandler.class 類型。
package com.mybatis.interceptor.interceptor;
import com.mybatis.interceptor.handler.EncryptUtil;
import com.mybatis.interceptor.transaction.SensitiveData;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Objects;
import java.util.Properties;
@Slf4j
@Component
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class ParameterInterceptor implements Interceptor {
@Autowired
private EncryptUtil encryptUtil;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//@Signature 指定了 type= parameterHandler 后抖韩,這里的 invocation.getTarget() 便是parameterHandler
//若指定ResultSetHandler 蛀恩,這里則能強(qiáng)轉(zhuǎn)為ResultSetHandler
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 獲取參數(shù)對(duì)像,即 mapper 中 paramsType 的實(shí)例
Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
//取出實(shí)例
Object parameterObject = parameterField.get(parameterHandler);
if (parameterObject != null) {
Class<?> parameterObjectClass = parameterObject.getClass();
//校驗(yàn)該實(shí)例的類是否被@SensitiveData所注解
SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);
if (Objects.nonNull(sensitiveData)) {
//取出當(dāng)前當(dāng)前類所有字段帽蝶,傳入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
encryptUtil.encrypt(declaredFields, parameterObject);
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
結(jié)果攔截器
package com.mybatis.interceptor.interceptor;
import com.mybatis.interceptor.handler.DecryptUtil;
import com.mybatis.interceptor.transaction.DecryptTransaction;
import com.mybatis.interceptor.transaction.SensitiveData;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;
@Slf4j
@Component
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class ResultSetInterceptor implements Interceptor {
@Autowired
private DecryptUtil decryptUtil;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//取出查詢的結(jié)果
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))) {
for (Object result : resultList) {
//逐一解密
decryptUtil.decrypt(result);
}
}
//基于selectOne
} else {
if (needToDecrypt(resultObject)) {
decryptUtil.decrypt(resultObject);
}
}
return resultObject;
}
private boolean needToDecrypt(Object object) {
Class<?> objectClass = object.getClass();
SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);
return Objects.nonNull(sensitiveData);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
實(shí)體類編寫赦肋,帶上自定義注解
package com.mybatis.interceptor.entity;
import com.mybatis.interceptor.transaction.DecryptTransaction;
import com.mybatis.interceptor.transaction.EncryptTransaction;
import com.mybatis.interceptor.transaction.SensitiveData;
import java.io.Serializable;
@SensitiveData
public class User implements Serializable {
private static final long serialVersionUID = -1205226416664488559L;
private Long id;
private String city;
@DecryptTransaction
@EncryptTransaction
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", city='" + city + '\'' +
", name='" + name + '\'' +
'}';
}
}
mapper接口
package com.mybatis.interceptor.mapper;
import com.mybatis.interceptor.entity.User;
import java.util.List;
public interface UserMapper {
Long addUser(User user);
User findById(Long id);
List<User> list();
User findByName(String name);
}
service及其實(shí)現(xiàn)類
package com.mybatis.interceptor.service;
import com.mybatis.interceptor.entity.User;
import java.util.List;
public interface UserService {
List<User> list();
Long add(User user);
User findById(Long id);
User findByName(String name);
}
service實(shí)現(xiàn)類
package com.mybatis.interceptor.service;
import com.mybatis.interceptor.mapper.UserMapper;
import com.mybatis.interceptor.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public List<User> list() {
return userMapper.list();
}
@Override
public Long add(User user) {
return userMapper.addUser(user);
}
@Override
public User findById(Long id) {
return userMapper.findById(id);
}
@Override
public User findByName(String name) {
return userMapper.findByName(name);
}
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.interceptor.mapper.UserMapper">
<resultMap id="baseResultMap" type="com.mybatis.interceptor.entity.User">
<result column="id" property="id" jdbcType="INTEGER" />
<result column="city" property="city" jdbcType="VARCHAR" />
<result column="name" property="name" jdbcType="VARCHAR" />
</resultMap>
<insert id="addUser">
INSERT INTO user (
id,city, name
)
VALUES (
#{id,jdbcType=INTEGER},
#{city,jdbcType=VARCHAR},
#{name,jdbcType=VARCHAR}
)
</insert>
<select id="list" resultMap="baseResultMap">
SELECT u.* FROM user u
</select>
<select id="findById" resultMap="baseResultMap">
SELECT u.* FROM user u WHERE u.id=#{id,jdbcType=INTEGER}
</select>
<select id="findByName" resultMap="baseResultMap">
SELECT u.* FROM user u WHERE u.name=#{name,jdbcType=VARCHAR}
</select>
</mapper>
Spring 啟動(dòng)類
package com.mybatis.interceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication
@MapperScan("com.mybatis.interceptor.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
properties配置
server.port=9999
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
#MySql配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url= jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
測(cè)試類
package com.mybatis.interceptor;
import com.mybatis.interceptor.entity.User;
import com.mybatis.interceptor.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class ApplicationTests {
@Test
public void contextLoads() {
}
@Autowired
private UserService userService;
@Test
public void addTest(){
User user = new User();
user.setCity("shanghai");
user.setName("nima");
user.setId(Long.valueOf(10));
userService.add(user);
}
@Test
public void findTest(){
System.out.println(userService.findById(Long.valueOf(10)));
}
}
加解密測(cè)試通過,如下圖所示: