diff --git a/build.gradle b/build.gradle index a91f172..f3aa0ad 100644 --- a/build.gradle +++ b/build.gradle @@ -114,6 +114,8 @@ dependencies { api group: 'commons-io', name: 'commons-io', version: '2.11.0' + compileOnly files('libs/damo-manager.jar') + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testImplementation 'ch.qos.logback:logback-classic:1.2.10' diff --git a/libs/damo-manager.jar b/libs/damo-manager.jar new file mode 100644 index 0000000..e80c2ad Binary files /dev/null and b/libs/damo-manager.jar differ diff --git a/src/main/java/com/eactive/apim/portal/common/entity/Auditable.java b/src/main/java/com/eactive/apim/portal/common/entity/Auditable.java index 2cd8370..adb9353 100644 --- a/src/main/java/com/eactive/apim/portal/common/entity/Auditable.java +++ b/src/main/java/com/eactive/apim/portal/common/entity/Auditable.java @@ -1,7 +1,7 @@ package com.eactive.apim.portal.common.entity; +import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter; import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14; -import kjb.safedb.KjbNotRnnoJpaAttributeConverter; import lombok.Data; import lombok.EqualsAndHashCode; import org.springframework.data.annotation.CreatedBy; @@ -34,7 +34,7 @@ public abstract class Auditable { * 최초등록자ID */ @Column(name = "created_by") - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) @CreatedBy private String createdBy; @@ -50,7 +50,7 @@ public abstract class Auditable { * 최종수정자ID */ @Column(name = "LAST_MODIFIED_BY") - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) @LastModifiedBy private String lastModifiedBy; diff --git a/src/main/java/com/eactive/apim/portal/jpa/PersonalDataEncryptConverter.java b/src/main/java/com/eactive/apim/portal/jpa/PersonalDataEncryptConverter.java new file mode 100644 index 0000000..7561545 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/jpa/PersonalDataEncryptConverter.java @@ -0,0 +1,115 @@ +package com.eactive.apim.portal.jpa; + +import com.eactive.ext.djb.DamoManager; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +@Slf4j +@Converter +public class PersonalDataEncryptConverter implements AttributeConverter { + + + /** + * 암호화 + * @param attribute the entity attribute value to be converted + * @return + */ + @Override + public String convertToDatabaseColumn(String attribute) { + String originalAttribute = attribute; + if (StringUtils.isNotEmpty(attribute)) { + attribute = DamoManager.getInstance().encrypt(attribute); + + if (log.isDebugEnabled()) { + // originalAttribute 홀수 글자 마스킹 + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < originalAttribute.length(); i++) { + if (i % 2 == 0) { + sb.append(originalAttribute.charAt(i)); + } else { + sb.append("*"); + } + } + + originalAttribute = sb.toString(); + log.debug("DB 암호화 : {} -> {}", originalAttribute, attribute); + } + } + + return attribute; + + } + + /** + * 복호화 + * @param dbData the data from the database column to be + * converted + * @return + */ + @Override + public String convertToEntityAttribute(String dbData) { + + String dbDataOriginal = dbData; + if (StringUtils.isNotEmpty(dbData)) { + if (this.isEncrypted(dbData)) { + try { + dbData = DamoManager.getInstance().decrypt(dbData); + } catch (Exception e) { + // 복호화 실패시 원본 반환 + String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "..."; + log.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length()); + return dbData; + } + } else { + log.debug("DB 복호화 경고: Base64 형식이 아님. 복호화하지 않고 원본 반환. dbData={}", dbData); + } + } + + log.debug("DB 복호화 : {} -> {}", dbDataOriginal, dbData); + + return dbData; + } + + /** + * 현재 damo-manager 가 bypass 모드(무변환)인지 여부. + * bypass 면 {@code encrypt}/{@code decrypt} 가 원문을 그대로 반환하므로 암호화/정규화가 no-op 이 된다. + * 레거시 평문 일괄 암호화 같은 마이그레이션 작업은 이 경우 의미가 없으므로 호출 측에서 차단해야 한다. + */ + public boolean isBypassMode() { + return DamoManager.getInstance().isBypassMode(); + } + + /** + * 암호화 여부 확인(base64 인코딩 여부로 판단, 또는 0-9, - 로 구성된 경우 암호화 되지 않은 걸로 판단(연락처)) + * @param data + * @return + */ + private boolean isEncrypted(String data) { + if (StringUtils.isEmpty(data)) { + return false; + } + + // 숫자, - 로만 구성된 경우 암호화 되지 않은 걸로 판단 (ex: 연락처) + if (data.matches("^[0-9-]+$")) { + return false; + } + + data = data.trim(); + + // Base64 형식 체크 + if (!data.matches("^[A-Za-z0-9+/]+={0,2}$")) { + return false; + } + + // Base64 길이 체크 (4의 배수) + if (data.length() % 4 != 0) { + return false; + } + + + return true; + } +} diff --git a/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java b/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java index ecd69e5..ad7bf25 100644 --- a/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java +++ b/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java @@ -1,13 +1,12 @@ package com.eactive.apim.portal.portaluser.entity; import com.eactive.apim.portal.common.entity.Auditable; -import com.eactive.apim.portal.portalorg.entity.PortalOrg; +import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter;import com.eactive.apim.portal.portalorg.entity.PortalOrg; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.ApprovalStatus; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.UserStatus; import java.io.Serializable; -import kjb.safedb.KjbNotRnnoJpaAttributeConverter; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Comment; @@ -34,7 +33,7 @@ public class PortalUser extends Auditable implements Serializable, com.eactive.e @Column(name = "login_id", length = 200, nullable = false) @Comment("로그인ID") - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) private String loginId; @Column(name = "password_hash", length = 60) @@ -47,17 +46,17 @@ public class PortalUser extends Auditable implements Serializable, com.eactive.e @Column(name = "phone_number", length = 50) @Comment("전화번호") - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) private String phoneNumber; @Column(name = "mobile_number", length = 50) @Comment("휴대폰 번호") - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) private String mobileNumber; @Column(name = "email_addr", length = 200) @Comment("이메일주소") - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) private String emailAddr; @ManyToOne(fetch = FetchType.EAGER) diff --git a/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java b/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java index 98a5a24..0b0e714 100644 --- a/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java +++ b/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java @@ -1,7 +1,7 @@ package com.eactive.apim.portal.portaluser.entity; +import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter; import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14; -import kjb.safedb.KjbNotRnnoJpaAttributeConverter; import org.hibernate.annotations.GenericGenerator; import javax.persistence.*; @@ -25,7 +25,7 @@ public class TwoFactorAuth { private String id; @Column(name = "recipient", length = 255) - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) private String recipient; @Column(name = "auth_number", length = 255) diff --git a/src/main/java/com/eactive/apim/portal/portaluser/repository/PortalUserRepository.java b/src/main/java/com/eactive/apim/portal/portaluser/repository/PortalUserRepository.java index fbe2813..d00f156 100644 --- a/src/main/java/com/eactive/apim/portal/portaluser/repository/PortalUserRepository.java +++ b/src/main/java/com/eactive/apim/portal/portaluser/repository/PortalUserRepository.java @@ -36,5 +36,12 @@ public interface PortalUserRepository extends BaseRepository long countByPortalOrg(PortalOrg portalOrg); Optional findByUserName(String userName); + + // 암복 + @Query( + value = "select * from ${ems.datasource.schema}.ptl_user where email_addr = :emailAddr", + nativeQuery = true + ) + Optional findByEmailAddrRaw(@Param("emailAddr") String emailAddr); } diff --git a/src/main/java/com/eactive/apim/portal/template/entity/MessageRequest.java b/src/main/java/com/eactive/apim/portal/template/entity/MessageRequest.java index 1b721e5..17a238e 100644 --- a/src/main/java/com/eactive/apim/portal/template/entity/MessageRequest.java +++ b/src/main/java/com/eactive/apim/portal/template/entity/MessageRequest.java @@ -1,9 +1,9 @@ package com.eactive.apim.portal.template.entity; +import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter; import com.eactive.apim.portal.portaluser.entity.PortalUser; import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14; -import kjb.safedb.KjbNotRnnoJpaAttributeConverter; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -53,10 +53,10 @@ public class MessageRequest implements Serializable { private String username; - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) private String email; - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) private String phone; @ManyToOne(fetch = FetchType.EAGER) diff --git a/src/main/java/com/eactive/apim/portal/user/entity/UserInfo.java b/src/main/java/com/eactive/apim/portal/user/entity/UserInfo.java index fdd9058..a4a6740 100644 --- a/src/main/java/com/eactive/apim/portal/user/entity/UserInfo.java +++ b/src/main/java/com/eactive/apim/portal/user/entity/UserInfo.java @@ -1,28 +1,20 @@ package com.eactive.apim.portal.user.entity; -import java.io.Serializable; -import java.time.LocalDateTime; - -import javax.persistence.Cacheable; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; - +import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter; +import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14; +import com.eactive.eai.data.converter.StringTrimConverter; +import com.eactive.eai.data.entity.AbstractEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NonNull; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.Comment; import org.springframework.data.annotation.LastModifiedDate; -import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14; -import com.eactive.eai.data.converter.StringTrimConverter; -import com.eactive.eai.data.entity.AbstractEntity; -import kjb.safedb.KjbNotRnnoJpaAttributeConverter; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NonNull; +import javax.persistence.*; +import java.io.Serializable; +import java.time.LocalDateTime; @Data @EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true) @@ -54,7 +46,7 @@ public class UserInfo extends AbstractEntity implements Serializable { @Column(length = 40) @Comment("휴대폰 번호") - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) private String cphnno; @Column(length = 40) @@ -63,7 +55,7 @@ public class UserInfo extends AbstractEntity implements Serializable { @Column(length = 100) @Comment("이메일 주소") - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) private String emad; @Column(length = 3) diff --git a/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java b/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java index 120b747..c19a33d 100644 --- a/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java +++ b/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java @@ -1,19 +1,12 @@ package com.eactive.apim.portal.user.entity; +import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter; import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14; -import java.time.LocalDateTime; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.TableGenerator; - -import kjb.safedb.KjbNotRnnoJpaAttributeConverter; import lombok.Data; +import javax.persistence.*; +import java.time.LocalDateTime; + @TableGenerator( name = "USER_LOGIN_LOG_GEN", table = "PTL_ID", @@ -32,7 +25,7 @@ public class UserLog { private Long logId; @Column(name = "login_id", nullable = false) - @Convert(converter = KjbNotRnnoJpaAttributeConverter.class) + @Convert(converter = PersonalDataEncryptConverter.class) private String loginId; @Column(name = "login_time") diff --git a/src/main/java/kjb/safedb/KjbNotRnnoJpaAttributeConverter.java b/src/main/java/kjb/safedb/KjbNotRnnoJpaAttributeConverter.java deleted file mode 100644 index 20a0ec3..0000000 --- a/src/main/java/kjb/safedb/KjbNotRnnoJpaAttributeConverter.java +++ /dev/null @@ -1,116 +0,0 @@ -package kjb.safedb; - -//import com.eactive.ext.kjb.safedb.KjbSafedbWrapper; -//import com.eactive.ext.kjb.safedb.SafeDBColumns; -//import com.eactive.ext.kjb.safedb.Utils; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.persistence.AttributeConverter; -import javax.persistence.Converter; - -@Converter -@Slf4j -public class KjbNotRnnoJpaAttributeConverter implements AttributeConverter { - private static final Logger needFixLogger = LoggerFactory.getLogger("eapim.portal.needfix"); - - /** - * 암호화 - * @param attribute the entity attribute value to be converted - * @return - */ - @Override - public String convertToDatabaseColumn(String attribute) { -// log.debug("DB 암호화 #1 - {} / {}", attribute, SafeDBColumns.NOT_RNNO); - -// String originalAttribute = attribute; -// if (StringUtils.isNotEmpty(attribute)) { -// KjbSafedbWrapper wrapper = KjbSafedbWrapper.getInstance(); -// attribute = wrapper.encryptNotRnnoString(attribute); -// -// if (log.isDebugEnabled()) { -// // originalAttribute 홀수 글자 마스킹 -// StringBuilder sb = new StringBuilder(); -// for (int i = 0; i < originalAttribute.length(); i++) { -// if (i % 2 == 0) { -// sb.append(originalAttribute.charAt(i)); -// } else { -// sb.append("*"); -// } -// } -// -// originalAttribute = sb.toString(); -// log.debug("DB 암호화 #2 : {} -> {}", originalAttribute, attribute); -// } -// } - - return attribute; - } - - /** - * 복호화 - * @param dbData the data from the database column to be - * converted - * @return - */ - @Override - public String convertToEntityAttribute(String dbData) { -// log.debug("DB 복호화 #1 - {} / {}", dbData, SafeDBColumns.NOT_RNNO); - -// String dbDataOriginal = dbData; -// if (StringUtils.isNotEmpty(dbData)) { -// if (this.isEncrypted(dbData)) { -// KjbSafedbWrapper safeDBWrapper = KjbSafedbWrapper.getInstance(); -// try { -// dbData = safeDBWrapper.decryptNotRnno(dbData); -// } catch (Exception e) { -// // 복호화 실패시 원본 반환 -// String logData = dbData.length() <= 3 ? dbData : dbData.substring(0, 3) + "..."; -// log.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length()); -// needFixLogger.warn("DB 복호화 실패: 복호화하지 않고 원본 반환. dbData={} (Length : {})", logData, dbData.length(), e); -// return dbData; -// } -// } else { -// log.debug("DB 복호화 경고: Base64 형식이 아님. 복호화하지 않고 원본 반환. dbData={}", dbData); -// } -// } - -// log.debug("DB 복호화 #2 : {} -> {}", dbDataOriginal, dbData); - - return dbData; - } - - /** - * 암호화 여부 확인(base64 인코딩 여부로 판단, 또는 0-9, - 로 구성된 경우 암호화 되지 않은 걸로 판단(연락처)) - * @param data - * @return - */ - private boolean isEncrypted(String data) { -// if (StringUtils.isEmpty(data)) { -// return false; -// } -// -// // 숫자, - 로만 구성된 경우 암호화 되지 않은 걸로 판단 (ex: 연락처) -// if (data.matches("^[0-9-]+$")) { -// return false; -// } -// -// data = data.trim(); -// -// // Base64 형식 체크 -// if (!Utils.isBase64(data)) { -// return false; -// } -// -// // Base64 길이 체크 (4의 배수) -// if (data.length() % 4 != 0) { -// return false; -// } -// -// -// return true; - return false; - } -}