Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6abb55a656 | |||
| 8cc6f775db | |||
| c3c10d1479 | |||
| 13b00504ac | |||
| 208b0be097 | |||
| 8c45451230 | |||
| dbdc06cf7e | |||
| 898a6d37aa | |||
| f67970c280 | |||
| 47b99d1858 | |||
| 384a3a5e47 | |||
| 207a2d692a | |||
| ea6ae8c022 | |||
| de262cec7b | |||
| 09013156dc | |||
| 2e77ce373e | |||
| c959549b99 | |||
| a974065fef |
@@ -114,6 +114,8 @@ dependencies {
|
|||||||
api group: 'commons-io', name: 'commons-io', version: '2.11.0'
|
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 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||||
testImplementation 'ch.qos.logback:logback-classic:1.2.10'
|
testImplementation 'ch.qos.logback:logback-classic:1.2.10'
|
||||||
|
|||||||
Binary file not shown.
@@ -4,8 +4,10 @@ package com.eactive.apim.portal.agreements.entity;
|
|||||||
public enum AgreementType {
|
public enum AgreementType {
|
||||||
TERMS_OF_USE("TERMS_OF_USE", "이용약관"),
|
TERMS_OF_USE("TERMS_OF_USE", "이용약관"),
|
||||||
PRIVACY_POLICY("PRIVACY_POLICY", "개인정보처리방침"),
|
PRIVACY_POLICY("PRIVACY_POLICY", "개인정보처리방침"),
|
||||||
|
PRIVACY_COLLECT("PRIVACY_COLLECT", "개인정보수집동의서"),
|
||||||
PRIVACY_COLLECT_IND("PRIVACY_COLLECT_IND", "개인용 개인정보수집동의서"),
|
PRIVACY_COLLECT_IND("PRIVACY_COLLECT_IND", "개인용 개인정보수집동의서"),
|
||||||
PRIVACY_COLLECT_ORG("PRIVACY_COLLECT_ORG", "법인용 개인정보수집동의서");
|
PRIVACY_COLLECT_ORG("PRIVACY_COLLECT_ORG", "법인용 개인정보수집동의서"),
|
||||||
|
NOTIFICATION_CONSENT("NOTIFICATION_CONSENT", "알림 수신 동의서");
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
private final String description;
|
private final String description;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.eactive.apim.portal.agreements.entity.AgreementsId;
|
|||||||
import com.eactive.apim.portal.agreements.entity.AgreementsStatus;
|
import com.eactive.apim.portal.agreements.entity.AgreementsStatus;
|
||||||
import com.eactive.apim.portal.agreements.repository.AgreementsRepository;
|
import com.eactive.apim.portal.agreements.repository.AgreementsRepository;
|
||||||
import com.eactive.apim.portal.common.exception.NotFoundException;
|
import com.eactive.apim.portal.common.exception.NotFoundException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -16,6 +17,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
|
@Slf4j
|
||||||
public class AgreementsService {
|
public class AgreementsService {
|
||||||
|
|
||||||
private static final String NOT_FOUND_MESSAGE = "해당 버전의 약관이 존재하지 않습니다.";
|
private static final String NOT_FOUND_MESSAGE = "해당 버전의 약관이 존재하지 않습니다.";
|
||||||
@@ -111,6 +113,7 @@ public class AgreementsService {
|
|||||||
public List<Agreements> findAllAgreementsSortPublishedOn(AgreementType agreementType) {
|
public List<Agreements> findAllAgreementsSortPublishedOn(AgreementType agreementType) {
|
||||||
List<Agreements> agreements = agreementsRepository.findAllByAgreementsTypeOrderByPublishedOnDesc(agreementType);
|
List<Agreements> agreements = agreementsRepository.findAllByAgreementsTypeOrderByPublishedOnDesc(agreementType);
|
||||||
if(agreements.isEmpty()) {
|
if(agreements.isEmpty()) {
|
||||||
|
log.error("해당 약관 종류의 약관이 존재하지 않습니다. agreementType={}", agreementType);
|
||||||
throw new IllegalStateException(NOT_FOUND_BY_DATE_MESSAGE);
|
throw new IllegalStateException(NOT_FOUND_BY_DATE_MESSAGE);
|
||||||
}
|
}
|
||||||
return agreements;
|
return agreements;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import javax.persistence.JoinTable;
|
|||||||
import javax.persistence.ManyToMany;
|
import javax.persistence.ManyToMany;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import org.hibernate.annotations.Comment;
|
import org.hibernate.annotations.Comment;
|
||||||
import org.hibernate.annotations.LazyCollection;
|
import org.hibernate.annotations.LazyCollection;
|
||||||
@@ -23,6 +24,7 @@ import org.hibernate.annotations.LazyCollectionOption;
|
|||||||
import org.springframework.data.annotation.LastModifiedBy;
|
import org.springframework.data.annotation.LastModifiedBy;
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
@Entity
|
@Entity
|
||||||
@Data
|
@Data
|
||||||
@Table(name = "ptl_credential")
|
@Table(name = "ptl_credential")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.eactive.apim.portal.common.entity;
|
package com.eactive.apim.portal.common.entity;
|
||||||
|
|
||||||
|
import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter;
|
||||||
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
|
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
|
||||||
import kjb.safedb.KjbNotRnnoJpaAttributeConverter;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.springframework.data.annotation.CreatedBy;
|
import org.springframework.data.annotation.CreatedBy;
|
||||||
@@ -34,7 +34,7 @@ public abstract class Auditable {
|
|||||||
* 최초등록자ID
|
* 최초등록자ID
|
||||||
*/
|
*/
|
||||||
@Column(name = "created_by")
|
@Column(name = "created_by")
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
@CreatedBy
|
@CreatedBy
|
||||||
private String createdBy;
|
private String createdBy;
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ public abstract class Auditable {
|
|||||||
* 최종수정자ID
|
* 최종수정자ID
|
||||||
*/
|
*/
|
||||||
@Column(name = "LAST_MODIFIED_BY")
|
@Column(name = "LAST_MODIFIED_BY")
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
@LastModifiedBy
|
@LastModifiedBy
|
||||||
private String lastModifiedBy;
|
private String lastModifiedBy;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.eactive.apim.portal.invitation.entity;
|
package com.eactive.apim.portal.invitation.entity;
|
||||||
|
|
||||||
import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus;
|
import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus;
|
||||||
|
import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter;
|
||||||
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
|
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.hibernate.annotations.Comment;
|
import org.hibernate.annotations.Comment;
|
||||||
@@ -29,10 +30,15 @@ public class UserInvitation {
|
|||||||
@Comment("기관ID")
|
@Comment("기관ID")
|
||||||
private String orgId;
|
private String orgId;
|
||||||
|
|
||||||
@Column(name = "INVITATION_EMAIL", length = 30, nullable = false)
|
@Column(name = "INVITATION_EMAIL", length = 256)
|
||||||
@Comment("초대이메일")
|
@Comment("초대이메일")
|
||||||
private String invitationEmail;
|
private String invitationEmail;
|
||||||
|
|
||||||
|
@Column(name = "INVITATION_MOBILE", length = 256)
|
||||||
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
|
@Comment("초대휴대폰")
|
||||||
|
private String invitationMobile;
|
||||||
|
|
||||||
@Column(name = "INVITATION_DATE", length = 14, nullable = false)
|
@Column(name = "INVITATION_DATE", length = 14, nullable = false)
|
||||||
@Convert(converter = LocalDateTimeToStringConverter14.class)
|
@Convert(converter = LocalDateTimeToStringConverter14.class)
|
||||||
@Comment("초대일시")
|
@Comment("초대일시")
|
||||||
|
|||||||
+7
@@ -22,4 +22,11 @@ public interface UserInvitationRepository extends JpaRepository<UserInvitation,
|
|||||||
Optional<UserInvitation> findFirstByInvitationEmailAndOrgIdAndStatus(String email, String orgId, InvitationStatus status);
|
Optional<UserInvitation> findFirstByInvitationEmailAndOrgIdAndStatus(String email, String orgId, InvitationStatus status);
|
||||||
|
|
||||||
Optional<UserInvitation> findFirstByInvitationEmailAndOrgId(String email, String orgId);
|
Optional<UserInvitation> findFirstByInvitationEmailAndOrgId(String email, String orgId);
|
||||||
|
|
||||||
|
// 휴대폰 번호 기반 초대 조회 (INVITATION_MOBILE 은 결정적 암호화로 저장되어 equality 조회 가능)
|
||||||
|
Optional<UserInvitation> findFirstByInvitationMobileAndStatus(String mobile, InvitationStatus status);
|
||||||
|
|
||||||
|
Optional<UserInvitation> findFirstByInvitationMobileAndOrgIdAndStatus(String mobile, String orgId, InvitationStatus status);
|
||||||
|
|
||||||
|
Optional<UserInvitation> findFirstByInvitationMobileAndOrgId(String mobile, String orgId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
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<String, String> {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 암호화
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
// 복호화 결과(평문) 홀수 글자 마스킹
|
||||||
|
String maskedDbData = dbData;
|
||||||
|
if (StringUtils.isNotEmpty(dbData)) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < dbData.length(); i++) {
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
sb.append(dbData.charAt(i));
|
||||||
|
} else {
|
||||||
|
sb.append("*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maskedDbData = sb.toString();
|
||||||
|
}
|
||||||
|
log.debug("DB 복호화 : {} -> {}", dbDataOriginal, maskedDbData);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,4 +91,8 @@ public class PortalOrg extends Auditable implements Serializable, com.eactive.ea
|
|||||||
@Column(name = "sc_phone_number", length = 20)
|
@Column(name = "sc_phone_number", length = 20)
|
||||||
@Comment("고객센터연락처")
|
@Comment("고객센터연락처")
|
||||||
private String scPhoneNumber;
|
private String scPhoneNumber;
|
||||||
|
|
||||||
|
@Column(name = "reverse_proxy_path", length = 255)
|
||||||
|
@Comment("리버스 프록시 경로")
|
||||||
|
private String reverseProxyPath;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package com.eactive.apim.portal.portaluser.entity;
|
package com.eactive.apim.portal.portaluser.entity;
|
||||||
|
|
||||||
import com.eactive.apim.portal.common.entity.Auditable;
|
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.ApprovalStatus;
|
||||||
import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode;
|
import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode;
|
||||||
import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.UserStatus;
|
import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.UserStatus;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import kjb.safedb.KjbNotRnnoJpaAttributeConverter;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.hibernate.annotations.Comment;
|
import org.hibernate.annotations.Comment;
|
||||||
@@ -22,7 +21,7 @@ import java.time.LocalDateTime;
|
|||||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
|
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "ptl_user")
|
@Table(name = "PTL_USER")
|
||||||
public class PortalUser extends Auditable implements Serializable, com.eactive.eai.data.Data {
|
public class PortalUser extends Auditable implements Serializable, com.eactive.eai.data.Data {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ public class PortalUser extends Auditable implements Serializable, com.eactive.e
|
|||||||
|
|
||||||
@Column(name = "login_id", length = 200, nullable = false)
|
@Column(name = "login_id", length = 200, nullable = false)
|
||||||
@Comment("로그인ID")
|
@Comment("로그인ID")
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
private String loginId;
|
private String loginId;
|
||||||
|
|
||||||
@Column(name = "password_hash", length = 60)
|
@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)
|
@Column(name = "phone_number", length = 50)
|
||||||
@Comment("전화번호")
|
@Comment("전화번호")
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
private String phoneNumber;
|
private String phoneNumber;
|
||||||
|
|
||||||
@Column(name = "mobile_number", length = 50)
|
@Column(name = "mobile_number", length = 50)
|
||||||
@Comment("휴대폰 번호")
|
@Comment("휴대폰 번호")
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
private String mobileNumber;
|
private String mobileNumber;
|
||||||
|
|
||||||
@Column(name = "email_addr", length = 200)
|
@Column(name = "email_addr", length = 200)
|
||||||
@Comment("이메일주소")
|
@Comment("이메일주소")
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
private String emailAddr;
|
private String emailAddr;
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.EAGER)
|
@ManyToOne(fetch = FetchType.EAGER)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.eactive.apim.portal.portaluser.entity;
|
package com.eactive.apim.portal.portaluser.entity;
|
||||||
|
|
||||||
|
import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter;
|
||||||
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
|
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
|
||||||
import kjb.safedb.KjbNotRnnoJpaAttributeConverter;
|
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
@@ -25,9 +25,14 @@ public class TwoFactorAuth {
|
|||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
@Column(name = "recipient", length = 255)
|
@Column(name = "recipient", length = 255)
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
private String recipient;
|
private String recipient;
|
||||||
|
|
||||||
|
// recipient 가 암호화 컬럼이라 값으로 동등 조회/중복정리가 불가능하므로,
|
||||||
|
// recipient 평문의 결정적 해시(HMAC-SHA256, hex)를 별도 조회 키로 둔다. (비암호화)
|
||||||
|
@Column(name = "recipient_key", length = 64)
|
||||||
|
private String recipientKey;
|
||||||
|
|
||||||
@Column(name = "auth_number", length = 255)
|
@Column(name = "auth_number", length = 255)
|
||||||
private String authNumber;
|
private String authNumber;
|
||||||
|
|
||||||
@@ -49,6 +54,14 @@ public class TwoFactorAuth {
|
|||||||
this.recipient = recipient;
|
this.recipient = recipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRecipientKey() {
|
||||||
|
return recipientKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecipientKey(String recipientKey) {
|
||||||
|
this.recipientKey = recipientKey;
|
||||||
|
}
|
||||||
|
|
||||||
public String getAuthNumber() {
|
public String getAuthNumber() {
|
||||||
return authNumber;
|
return authNumber;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ public interface PortalUserRepository extends BaseRepository<PortalUser, String>
|
|||||||
|
|
||||||
boolean existsByLoginId(String loginId);
|
boolean existsByLoginId(String loginId);
|
||||||
|
|
||||||
|
boolean existsByMobileNumber(String mobileNumber);
|
||||||
|
|
||||||
|
// 휴대폰 번호 기반 기존 회원 조회 (mobileNumber 는 유니크 제약이 없어 다건 가능)
|
||||||
|
List<PortalUser> findAllByMobileNumber(String mobileNumber);
|
||||||
|
|
||||||
@Query("SELECT u FROM PortalUser u WHERE u.portalOrg = :org AND u.userStatus != :status")
|
@Query("SELECT u FROM PortalUser u WHERE u.portalOrg = :org AND u.userStatus != :status")
|
||||||
Page<PortalUser> findActiveUsers(@Param("org") PortalOrg org, @Param("status") PortalUserEnums.UserStatus status, Pageable pageable);
|
Page<PortalUser> findActiveUsers(@Param("org") PortalOrg org, @Param("status") PortalUserEnums.UserStatus status, Pageable pageable);
|
||||||
|
|
||||||
@@ -36,5 +41,12 @@ public interface PortalUserRepository extends BaseRepository<PortalUser, String>
|
|||||||
long countByPortalOrg(PortalOrg portalOrg);
|
long countByPortalOrg(PortalOrg portalOrg);
|
||||||
|
|
||||||
Optional<PortalUser> findByUserName(String userName);
|
Optional<PortalUser> findByUserName(String userName);
|
||||||
|
|
||||||
|
// 암복
|
||||||
|
@Query(
|
||||||
|
value = "select * from ${ems.datasource.schema}.ptl_user where email_addr = :emailAddr",
|
||||||
|
nativeQuery = true
|
||||||
|
)
|
||||||
|
Optional<PortalUser> findByEmailAddrRaw(@Param("emailAddr") String emailAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
@@ -6,7 +6,16 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface TwoFactorAuthRepository extends JpaRepository<TwoFactorAuth, String> {
|
public interface TwoFactorAuthRepository extends JpaRepository<TwoFactorAuth, String> {
|
||||||
|
|
||||||
|
// recipientKey(결정적 해시) 기반 조회/정리. recipient 가 암호화 컬럼이라 값 동등 조회가 깨지는 문제를 우회한다.
|
||||||
|
Optional<TwoFactorAuth> findByRecipientKey(String recipientKey);
|
||||||
|
|
||||||
|
void deleteAllByRecipientKey(String recipientKey);
|
||||||
|
|
||||||
|
// 아래 두 메서드는 recipient(암호화 컬럼) 기반이라 신뢰할 수 없음 — 신규 코드에서는 RecipientKey 버전을 사용할 것.
|
||||||
|
@Deprecated
|
||||||
Optional<TwoFactorAuth> findByRecipient(String recipientKey);
|
Optional<TwoFactorAuth> findByRecipient(String recipientKey);
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
void deleteAllByRecipient(String recipient);
|
void deleteAllByRecipient(String recipient);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public class Inquiry extends Auditable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 문의 상태 (예: 대기중, 답변 완료 등)
|
* 문의 상태 (예: 대기중, 답변 완료 등)
|
||||||
|
* @DjbInquiryStatus 참조
|
||||||
*/
|
*/
|
||||||
@Column(name = "INQUIRY_STATUS", length = 32)
|
@Column(name = "INQUIRY_STATUS", length = 32)
|
||||||
private String inquiryStatus;
|
private String inquiryStatus;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public enum MessageCode {
|
|||||||
// 제주은행
|
// 제주은행
|
||||||
API_STATUS_CHANGED("API 상태 변화", "FM_APIM011", Channels.SWING),
|
API_STATUS_CHANGED("API 상태 변화", "FM_APIM011", Channels.SWING),
|
||||||
INFLOW_TOKEN_FAILED("유량제어 토큰 획득 실패", "FM_APIM012", Channels.SWING),
|
INFLOW_TOKEN_FAILED("유량제어 토큰 획득 실패", "FM_APIM012", Channels.SWING),
|
||||||
|
ADMIN_VERIFICATION_MOBILEPHONE("관리자포탈 휴대폰 번호 인증", "FM_APIM013", Channels.KAKAO_ALIMTALK),
|
||||||
|
|
||||||
|
|
||||||
// 미 사용, 미 분류
|
// 미 사용, 미 분류
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.eactive.apim.portal.template.entity;
|
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.apim.portal.portaluser.entity.PortalUser;
|
||||||
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
|
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
|
||||||
import kjb.safedb.KjbNotRnnoJpaAttributeConverter;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
@@ -53,10 +53,10 @@ public class MessageRequest implements Serializable {
|
|||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
private String phone;
|
private String phone;
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.EAGER)
|
@ManyToOne(fetch = FetchType.EAGER)
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import lombok.*;
|
|||||||
@Builder
|
@Builder
|
||||||
public class MessageRecipient {
|
public class MessageRecipient {
|
||||||
private String username;
|
private String username;
|
||||||
private String userId; //email
|
private String userId;
|
||||||
|
private String email;
|
||||||
private String phone;
|
private String phone;
|
||||||
private String messengerId;
|
private String messengerId;
|
||||||
|
|
||||||
@@ -19,7 +20,8 @@ public class MessageRecipient {
|
|||||||
public static MessageRecipient of(PortalUser user) {
|
public static MessageRecipient of(PortalUser user) {
|
||||||
MessageRecipient recipient = new MessageRecipient();
|
MessageRecipient recipient = new MessageRecipient();
|
||||||
recipient.setUsername(user.getUserName());
|
recipient.setUsername(user.getUserName());
|
||||||
recipient.setUserId(user.getEmailAddr());
|
recipient.setUserId(user.getLoginId());
|
||||||
|
recipient.setEmail(user.getEmailAddr());
|
||||||
recipient.setPhone(user.getPhoneNumber());
|
recipient.setPhone(user.getPhoneNumber());
|
||||||
return recipient;
|
return recipient;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public class MessageSendService {
|
|||||||
List<MessageRecipient> recipients = new ArrayList<>();
|
List<MessageRecipient> recipients = new ArrayList<>();
|
||||||
|
|
||||||
for (UserInfo user : template.getAdditionalRecipients()) {
|
for (UserInfo user : template.getAdditionalRecipients()) {
|
||||||
MessageRecipient messageRecipient = new MessageRecipient(user.getUsername(), user.getUserid(), user.getCphnno(), user.getUserid());
|
MessageRecipient messageRecipient = new MessageRecipient(user.getUsername(), user.getUserid(), user.getEmad(), user.getCphnno(), user.getUserid());
|
||||||
recipients.add(messageRecipient);
|
recipients.add(messageRecipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +125,7 @@ public class MessageSendService {
|
|||||||
messengerRequest.setServiceId(portalProperties.get("ums.messenger.tx_id"));
|
messengerRequest.setServiceId(portalProperties.get("ums.messenger.tx_id"));
|
||||||
messengerRequest.setMessageType("MESSENGER");
|
messengerRequest.setMessageType("MESSENGER");
|
||||||
messageRequestRepository.save(messengerRequest);
|
messageRequestRepository.save(messengerRequest);
|
||||||
|
logger.debug(messengerRequest.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
package com.eactive.apim.portal.user.entity;
|
package com.eactive.apim.portal.user.entity;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter;
|
||||||
import java.time.LocalDateTime;
|
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
|
||||||
|
import com.eactive.eai.data.converter.StringTrimConverter;
|
||||||
import javax.persistence.Cacheable;
|
import com.eactive.eai.data.entity.AbstractEntity;
|
||||||
import javax.persistence.Column;
|
import lombok.Data;
|
||||||
import javax.persistence.Convert;
|
import lombok.EqualsAndHashCode;
|
||||||
import javax.persistence.Entity;
|
import lombok.NonNull;
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.Table;
|
|
||||||
|
|
||||||
import org.hibernate.annotations.Cache;
|
import org.hibernate.annotations.Cache;
|
||||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||||
import org.hibernate.annotations.Comment;
|
import org.hibernate.annotations.Comment;
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
|
||||||
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
|
import javax.persistence.*;
|
||||||
import com.eactive.eai.data.converter.StringTrimConverter;
|
import java.io.Serializable;
|
||||||
import com.eactive.eai.data.entity.AbstractEntity;
|
import java.time.LocalDateTime;
|
||||||
import kjb.safedb.KjbNotRnnoJpaAttributeConverter;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.NonNull;
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
|
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
|
||||||
@@ -54,7 +46,7 @@ public class UserInfo extends AbstractEntity<String> implements Serializable {
|
|||||||
|
|
||||||
@Column(length = 40)
|
@Column(length = 40)
|
||||||
@Comment("휴대폰 번호")
|
@Comment("휴대폰 번호")
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
private String cphnno;
|
private String cphnno;
|
||||||
|
|
||||||
@Column(length = 40)
|
@Column(length = 40)
|
||||||
@@ -63,7 +55,7 @@ public class UserInfo extends AbstractEntity<String> implements Serializable {
|
|||||||
|
|
||||||
@Column(length = 100)
|
@Column(length = 100)
|
||||||
@Comment("이메일 주소")
|
@Comment("이메일 주소")
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
private String emad;
|
private String emad;
|
||||||
|
|
||||||
@Column(length = 3)
|
@Column(length = 3)
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
package com.eactive.apim.portal.user.entity;
|
package com.eactive.apim.portal.user.entity;
|
||||||
|
|
||||||
|
import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter;
|
||||||
import com.eactive.eai.data.converter.LocalDateTimeToStringConverter14;
|
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 lombok.Data;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@TableGenerator(
|
@TableGenerator(
|
||||||
name = "USER_LOGIN_LOG_GEN",
|
name = "USER_LOGIN_LOG_GEN",
|
||||||
table = "PTL_ID",
|
table = "PTL_ID",
|
||||||
@@ -32,7 +25,7 @@ public class UserLog {
|
|||||||
private Long logId;
|
private Long logId;
|
||||||
|
|
||||||
@Column(name = "login_id", nullable = false)
|
@Column(name = "login_id", nullable = false)
|
||||||
@Convert(converter = KjbNotRnnoJpaAttributeConverter.class)
|
@Convert(converter = PersonalDataEncryptConverter.class)
|
||||||
private String loginId;
|
private String loginId;
|
||||||
|
|
||||||
@Column(name = "login_time")
|
@Column(name = "login_time")
|
||||||
|
|||||||
@@ -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<String, String> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user