From 5ed401050c2aed762f025a13d06f97f85b2162f4 Mon Sep 17 00:00:00 2001 From: mjkhan21 Date: Fri, 15 Dec 2023 11:21:42 +0900 Subject: [PATCH] =?UTF-8?q?MyBatis=20=ED=94=8C=EB=9F=AC=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cokr/xit/foundation/data/ARIA.java | 3 + .../data/paging/EncryptionSupport.java | 133 ++++++++++++++++++ .../foundation/data/paging/MapperSupport.java | 4 +- .../foundation/data/paging/MybatisPlugin.java | 30 ++++ 4 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 src/main/java/cokr/xit/foundation/data/paging/EncryptionSupport.java diff --git a/src/main/java/cokr/xit/foundation/data/ARIA.java b/src/main/java/cokr/xit/foundation/data/ARIA.java index 96abdeb..3f642bd 100644 --- a/src/main/java/cokr/xit/foundation/data/ARIA.java +++ b/src/main/java/cokr/xit/foundation/data/ARIA.java @@ -156,6 +156,9 @@ public class ARIA extends AbstractComponent { * @return 복호화한 문자열 */ public String decrypt(String encrypted) { + if (isEmpty(encrypted)) + return ""; + byte[] bytes = Base64.getDecoder().decode(encrypted); return new String(service().decrypt(bytes, key)); } diff --git a/src/main/java/cokr/xit/foundation/data/paging/EncryptionSupport.java b/src/main/java/cokr/xit/foundation/data/paging/EncryptionSupport.java new file mode 100644 index 0000000..fe19f78 --- /dev/null +++ b/src/main/java/cokr/xit/foundation/data/paging/EncryptionSupport.java @@ -0,0 +1,133 @@ +package cokr.xit.foundation.data.paging; + +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.resultset.ResultSetHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Signature; + +import cokr.xit.foundation.data.ARIA; + +@Intercepts({ + @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}), + @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), +}) +public class EncryptionSupport extends MybatisPlugin { + private boolean enabled; + private ARIA aria = new ARIA(); + + private List decrypted = Collections.emptyList(); + private List adaptors = Collections.emptyList(); + + @Override + protected void configure() { + String str = properties.getProperty("enabled", ""); + enabled = !"false".equals(str); + + str = properties.getProperty("enc.key"); + aria.setKey(str); + str = properties.getProperty("enc.algorithm"); + if (!isEmpty(str)) + aria.setAlgorithm(str); + + str = properties.getProperty("dec.fields", ""); + if (!isEmpty(str)) + decrypted = List.of(str.split(",")); + + str = properties.getProperty("enc.adaptor", ""); + if (isEmpty(str)) return; + + adaptors = Stream.of(str.split(",")).map(name -> { + try { + Class klass = Class.forName(name); + Adaptor adaptor = (Adaptor)klass.getConstructor().newInstance(); + adaptor.setARIA(aria); + return adaptor; + } catch (Exception e) { + throw runtimeException(e); + } + }).toList(); + } + + @Override + protected Object handle(ResultSetHandler resultSetHandler, Statement statement) throws SQLException { + Object obj = super.handle(resultSetHandler, statement); + + if (obj instanceof List) { + List list = (List)obj; + list.forEach(this::decrypt); + } else { + decrypt(obj); + } + + return obj; + } + + private void decrypt(Object obj) { + if (!enabled || decrypted.isEmpty()) return; + + if (obj instanceof Map) + decrypt((Map)obj); + } + + protected void decrypt(Map map) { + decrypted.forEach(k -> { + if (!map.containsKey(k)) return; + + Object v = map.get(k); + map.put(k, aria.decrypt((String)v)); + }); + } + + @Override + protected Object update(Executor executor, MappedStatement mappedStatement, Object obj) throws SQLException { + switch (mappedStatement.getSqlCommandType()) { + case INSERT: + case UPDATE: encrypt(obj); break; + default: break; + } + + return super.update(executor, mappedStatement, obj); + } + + private void encrypt(Object obj) { + if (!enabled || obj == null || adaptors.isEmpty()) return; + + process(obj, arg -> adaptors.forEach(adaptor -> encrypt(adaptor, arg))); + } + + private void encrypt(Adaptor adaptor, Object obj) { + if (!adaptor.setTarget(obj)) return; + + adaptor.encrypt(); + adaptor.clear(); + } + + public abstract static class Adaptor { + protected ARIA aria; + protected Object obj; + + public Adaptor setARIA(ARIA aria) { + this.aria = aria; + return this; + } + + public boolean setTarget(Object obj) { + this.obj = obj; + return true; + } + + public abstract void encrypt(); + + public void clear() { + obj = null; + } + } +} \ No newline at end of file diff --git a/src/main/java/cokr/xit/foundation/data/paging/MapperSupport.java b/src/main/java/cokr/xit/foundation/data/paging/MapperSupport.java index ae53929..cfc40fe 100644 --- a/src/main/java/cokr/xit/foundation/data/paging/MapperSupport.java +++ b/src/main/java/cokr/xit/foundation/data/paging/MapperSupport.java @@ -71,11 +71,11 @@ public class MapperSupport extends MybatisPlugin { now = ifEmpty(now, () -> dateFormat.format(new Date())); if (arg instanceof AbstractEntity) { setTimestamp(sqlType, (AbstractEntity)arg, now); - } else if (arg instanceof Map) { + } else if (arg instanceof Map) { Map map = (Map)arg; for (Object o: map.values()) setEntityTimestamp(sqlType, o, now); - } else if (arg instanceof Iterable) { + } else if (arg instanceof Iterable) { Iterable iterable = (Iterable)arg; for (Object o: iterable) setEntityTimestamp(sqlType, o, now); diff --git a/src/main/java/cokr/xit/foundation/data/paging/MybatisPlugin.java b/src/main/java/cokr/xit/foundation/data/paging/MybatisPlugin.java index 7ac0959..0f3db06 100644 --- a/src/main/java/cokr/xit/foundation/data/paging/MybatisPlugin.java +++ b/src/main/java/cokr/xit/foundation/data/paging/MybatisPlugin.java @@ -5,6 +5,9 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; +import java.util.Map; +import java.util.Properties; +import java.util.function.Consumer; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.parameter.ParameterHandler; @@ -38,6 +41,16 @@ import cokr.xit.foundation.AbstractComponent; @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) }) public class MybatisPlugin extends AbstractComponent implements Interceptor { + protected Properties properties; + + @Override + public void setProperties(Properties properties) { + this.properties = properties; + configure(); + } + + protected void configure() {} + @Override public Object intercept(Invocation invocation) throws Throwable { Object target = invocation.getTarget(); @@ -110,4 +123,21 @@ public class MybatisPlugin extends AbstractComponent implements Interceptor { protected Object handle(ResultSetHandler resultSetHandler, Statement statement) throws SQLException { return resultSetHandler.handleResultSets(statement); } + + protected void process(Object obj, Consumer processor) { + if (obj instanceof Map) { + Map map = (Map)obj; + map.values().forEach(processor); + } else if (obj instanceof Iterable) { + Iterable objs = (Iterable)obj; + for (Object o: objs) + processor.accept(o); + } else if (obj.getClass().isArray()) { + Object[] objs = (Object[])obj; + for (Object o: objs) + processor.accept(o); + } else { + processor.accept(obj); + } + } } \ No newline at end of file