diff --git a/WebContent/jsp/common/screen/emergency.jsp b/WebContent/jsp/common/screen/emergency.jsp index c3f0826..1163a97 100644 --- a/WebContent/jsp/common/screen/emergency.jsp +++ b/WebContent/jsp/common/screen/emergency.jsp @@ -293,6 +293,12 @@ fncSsoLogin(); }); + // SMS 인증번호 입력 필드에서 엔터키 누르면 확인 + $("#smsAuthCode").keydown(function(event){ + if ( event.which == 13 ) { + verifySmsAuthCode(); + } + }); }); function fncSsoLogin() { diff --git a/kjb-docs/sms-auth-guide.md b/kjb-docs/sms-auth-guide.md new file mode 100644 index 0000000..08949f8 --- /dev/null +++ b/kjb-docs/sms-auth-guide.md @@ -0,0 +1,209 @@ +# SMS 2차 인증 운영 가이드 + +## 개요 + +eLink Monitoring Admin 로그인 시 ID/PW 인증 후 SMS 인증번호를 통한 2차 인증을 수행합니다. +SSO 로그인에는 적용되지 않습니다. + +--- + +## 로그인 흐름 + +### 일반 로그인 (SMS 인증 활성화 시) + +``` +1. 로그인 페이지 접속 +2. ID/PW 입력 → [Login] 버튼 클릭 +3. SMS 인증 모달 표시 (등록된 휴대폰으로 인증번호 발송) +4. 인증번호 6자리 입력 → [확인] 버튼 클릭 +5. 로그인 완료 +``` + +### SSO 로그인 + +``` +1. 로그인 페이지 접속 +2. [SSO Login] 버튼 클릭 +3. SSO 인증 진행 +4. 로그인 완료 (SMS 인증 없음) +``` + +--- + +## 설정 관리 + +### 설정 위치 +**모니터링 프로퍼티** (DB 테이블: `TSEAIRM24`, 그룹: `Monitoring`) + +### 설정 항목 + +| 프로퍼티 키 | 기본값 | 설명 | +|------------|--------|------| +| `kjb.sms_auth.enabled` | `true` | SMS 인증 사용 여부 | +| `kjb.sms_auth.mode` | `real` | 인증번호 생성 모드 | +| `kjb.sms_auth.fixed_value` | `654321` | fixed 모드 시 사용할 고정 인증번호 | + +### 인증번호 생성 모드 (`kjb.sms_auth.mode`) + +| 모드 | 설명 | 용도 | +|------|------|------| +| `real` | 6자리 랜덤 숫자 생성 | **운영 환경** | +| `hhmm00` | 현재시각(HHmm) + "00" (예: 14:35 → `143500`) | 개발/테스트 | +| `fixed` | `kjb.sms_auth.fixed_value` 값 사용 | 개발/테스트 | + + + +--- + +## 비상 로그인 + +SSO 장애 또는 SMS 발송 장애 시 ID/PW만으로 로그인할 수 있는 비상 모드입니다. + +### 사용 방법 + +``` +https://{서버주소}/monitoring/loginForm.do?emergency +``` + +### 동작 +- SSO 로그인 버튼 숨김 +- SMS 2차 인증 우회 +- ID/PW만으로 로그인 완료 + +### 주의사항 +- 비상 상황에서만 사용 +- 사용 시 서버 로그에 기록됨: `비상 로그인 모드 활성화 - SSO/SMS 인증 우회` + +--- + +## SMS 인증 화면 안내 + +### 인증 모달 + +| 항목 | 설명 | +|------|------| +| 마스킹된 전화번호 | `010****1234` 형식으로 표시 | +| 남은 시간 | 인증번호 유효시간 카운트다운 (60초) | +| 재발송 버튼 | 20초 후 활성화, 남은 시간 표시 (예: `재발송 (15초)`) | + +### 인증 규칙 +- 인증번호: 6자리 숫자 +- 유효시간: **1분** +- 재발송 가능: 발송 후 **20초** 경과 시 + +--- + +## 오류 상황 및 대응 + +### 1. "휴대폰번호가 등록되어 있지 않아 로그인할 수 없습니다" + +**원인**: 사용자 정보에 휴대폰번호가 없음 + +**대응**: +- 관리자가 해당 사용자의 휴대폰번호 등록 +- 또는 비상 로그인 사용 (`/loginForm.do?emergency`) + +### 2. "인증번호 발송에 실패했습니다" + +**원인**: UMS 서버 연동 오류 + +**대응**: +- UMS 서버 상태 확인 +- 비상 로그인 사용 (`/loginForm.do?emergency`) +- 또는 SMS 인증 비활성화 (`kjb.sms_auth.enabled = false`) + +### 3. "인증번호가 만료되었습니다" + +**원인**: 1분 이내에 인증번호 입력하지 않음 + +**대응**: +- 모달 닫고 다시 로그인 시도 +- 새 인증번호 발송됨 + +### 4. "인증번호가 일치하지 않습니다" + +**원인**: 잘못된 인증번호 입력 + +**대응**: +- 정확한 6자리 숫자 입력 +- 재발송 버튼으로 새 인증번호 요청 가능 + +--- + +## 로그 확인 + +### 로그 위치 +서버 로그 파일 (Logback 설정에 따름) + +### 주요 로그 메시지 + +| 로그 | 의미 | +|------|------| +| `SMS 인증번호 발송 완료 - guid: xxx, phone: 010****1234` | 인증번호 발송 성공 | +| `SMS 인증번호 검증 성공` | 인증 성공 | +| `SMS 인증번호 불일치` | 인증 실패 (잘못된 번호) | +| `SMS 인증번호 만료됨` | 인증 실패 (시간 초과) | +| `비상 로그인 모드 활성화 - SSO/SMS 인증 우회` | 비상 모드 접속 | + +### 디버그 로그 (개발/테스트용) + +`-DLOGBACK_LOG_LEVEL=DEBUG` 설정 시 인증번호가 로그에 출력됩니다: +``` +SMS 인증번호 생성 (real 모드): 123456 +SMS 인증번호 발송 - authCode: 123456 +``` + +> **주의**: 운영 환경에서는 DEBUG 로그 비활성화 권장 + +--- + +## 초기 설정 (신규 구축 시) + +### DB 데이터 입력 + +```sql +-- SMS 인증 설정 초기화 +INSERT INTO EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) +VALUES ('Monitoring', 'kjb.sms_auth.enabled', 'true'); + +INSERT INTO EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) +VALUES ('Monitoring', 'kjb.sms_auth.mode', 'real'); + +INSERT INTO EMSADM.TSEAIRM24 (PRPTYGROUPNAME, PRPTYNAME, PRPTY2VAL) +VALUES ('Monitoring', 'kjb.sms_auth.fixed_value', '654321'); +``` + +### UMS 연동 설정 확인 + +SMS 발송을 위해 UMS 관련 프로퍼티가 설정되어 있어야 합니다: +- `kjb.ums.host_url` +- `kjb.ums.api_key` +- 기타 UMS 연동 설정 + +--- + +## FAQ + +### Q: SMS 인증을 일시적으로 비활성화하려면? + +```sql +UPDATE EMSADM.TSEAIRM24 +SET PRPTY2VAL = 'false' +WHERE PRPTYGROUPNAME = 'Monitoring' AND PRPTYNAME = 'kjb.sms_auth.enabled'; +``` + +### Q: 특정 사용자만 SMS 인증을 우회하려면? + +현재는 사용자별 설정이 없습니다. +- 해당 사용자에게 비상 로그인 URL 안내: `/loginForm.do?emergency` +- 또는 전체 SMS 인증 비활성화 + +### Q: 인증번호를 미리 알 수 있나요? (테스트용) + +`kjb.sms_auth.mode` 설정 변경: +- `hhmm00`: 현재시각 기반 (예: 14:35 → `143500`) +- `fixed`: 고정값 사용 (`kjb.sms_auth.fixed_value`) + +### Q: SSO 로그인에도 SMS 인증이 적용되나요? + +아니요. SMS 2차 인증은 ID/PW 로그인에만 적용됩니다. diff --git a/src/main/java/com/eactive/eai/rms/common/context/MonitoringContextImpl.java b/src/main/java/com/eactive/eai/rms/common/context/MonitoringContextImpl.java index 811fca8..a4a6e68 100644 --- a/src/main/java/com/eactive/eai/rms/common/context/MonitoringContextImpl.java +++ b/src/main/java/com/eactive/eai/rms/common/context/MonitoringContextImpl.java @@ -21,6 +21,8 @@ import com.eactive.eai.rms.common.util.ContainerUtil; import com.eactive.eai.rms.data.entity.man.monitoringProperty.MonitoringPropertyId; import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyGroupService; import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService; +import com.eactive.ext.kjb.common.KjbPropertyHolder; +import com.eactive.ext.kjb.util.KjbPropertyInjector; @Service("monitoringContext") @Transactional(transactionManager = "transactionManagerForEMS") @@ -43,6 +45,7 @@ class MonitoringContextImpl implements MonitoringContext { public void refresh() { init(); + KjbPropertyHolder.reload(); } public void refresh(String propName) { @@ -82,6 +85,10 @@ class MonitoringContextImpl implements MonitoringContext { setPortMapper(monitoringContextDAO.getPortMap()); + // KjbPropertyHolder 초기화 + KjbPropertyInjector injector = new KjbPropertyInjector(monitoringPropertyService, "Monitoring"); + KjbPropertyHolder.initialize(injector::inject); + logger.info("SSO AgentId : " + System.getProperty(EAI_AGENTID)); } catch (Exception e) { diff --git a/src/main/java/com/eactive/eai/rms/common/login/MainController.java b/src/main/java/com/eactive/eai/rms/common/login/MainController.java index 1c8abe3..7fe78fb 100644 --- a/src/main/java/com/eactive/eai/rms/common/login/MainController.java +++ b/src/main/java/com/eactive/eai/rms/common/login/MainController.java @@ -33,8 +33,6 @@ import org.springframework.web.servlet.View; import com.eactive.eai.common.seed.Seed; import com.eactive.eai.rms.ext.kjb.smsauth.SmsAuthService; -import com.eactive.ext.kjb.common.KjbProperty; -import com.eactive.ext.kjb.util.KjbPropertyInjector; import com.eactive.eai.data.converter.LocalDateTimeFormatters; import com.eactive.eai.rms.common.context.MonitoringContext; import com.eactive.eai.rms.common.datasource.DataSourceType; @@ -266,13 +264,11 @@ public class MainController implements InterceptorSkipController { } /** - * SmsAuthService 인스턴스 조회 (lazy initialization) + * SmsAuthService 인스턴스 조회 (singleton) */ private SmsAuthService getSmsAuthService() { try { - KjbPropertyInjector injector = new KjbPropertyInjector(monitoringPropertyService, "Monitoring"); - KjbProperty kjbProperty = injector.inject(new KjbProperty()); - return new SmsAuthService(kjbProperty); + return SmsAuthService.getInstance(); } catch (Exception e) { logger.warn("SmsAuthService 초기화 실패 - SMS 인증 비활성화 처리", e); return null; diff --git a/src/main/java/com/eactive/eai/rms/ext/kjb/smsauth/SmsAuthController.java b/src/main/java/com/eactive/eai/rms/ext/kjb/smsauth/SmsAuthController.java index 8909faf..50e48ca 100644 --- a/src/main/java/com/eactive/eai/rms/ext/kjb/smsauth/SmsAuthController.java +++ b/src/main/java/com/eactive/eai/rms/ext/kjb/smsauth/SmsAuthController.java @@ -4,10 +4,6 @@ import com.eactive.apim.portal.user.entity.UserInfo; import com.eactive.eai.rms.common.interceptor.InterceptorSkipController; import com.eactive.eai.rms.common.login.LoginVo; import com.eactive.eai.rms.common.login.MainController; -import com.eactive.eai.rms.common.util.CommonConstants; -import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService; -import com.eactive.ext.kjb.common.KjbProperty; -import com.eactive.ext.kjb.util.KjbPropertyInjector; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -23,27 +19,14 @@ import javax.servlet.http.HttpSession; @RequestMapping("/sms-auth") public class SmsAuthController implements InterceptorSkipController { - public static final String PROP_GROUP_ID = "Monitoring"; - private final MainController mainController; - private final KjbPropertyInjector kjbPropertyInjector; - private SmsAuthService smsAuthService; - private KjbProperty kjbProperty; - - public SmsAuthController(MonitoringPropertyService monitoringPropertyService, - MainController mainController) { + public SmsAuthController(MainController mainController) { this.mainController = mainController; - this.kjbPropertyInjector = new KjbPropertyInjector(monitoringPropertyService, PROP_GROUP_ID); - reloadProperty(); } - /** - * 프로퍼티 리로드 (DB 변경 대응) - */ - private void reloadProperty() { - this.kjbProperty = kjbPropertyInjector.inject(new KjbProperty()); - this.smsAuthService = new SmsAuthService(kjbProperty); + private SmsAuthService getSmsAuthService() { + return SmsAuthService.getInstance(); } /** @@ -52,11 +35,8 @@ public class SmsAuthController implements InterceptorSkipController { @RequestMapping(value = "/send.json", method = RequestMethod.POST) @ResponseBody public SmsAuthDto sendAuthCode(HttpServletRequest request) { - // 프로퍼티 리로드 (DB 변경 대응) - reloadProperty(); - HttpSession session = request.getSession(); - UserInfo userInfo = smsAuthService.getUserInfoFromSession(session); + UserInfo userInfo = getSmsAuthService().getUserInfoFromSession(session); if (userInfo == null) { log.warn("SMS 인증 세션 정보 없음 - send 요청"); @@ -66,7 +46,7 @@ public class SmsAuthController implements InterceptorSkipController { .build(); } - if (!smsAuthService.hasValidPhoneNumber(userInfo)) { + if (!getSmsAuthService().hasValidPhoneNumber(userInfo)) { log.warn("유효한 휴대폰번호 없음 - userId: {}", userInfo.getUserid()); return SmsAuthDto.builder() .sent(false) @@ -75,19 +55,19 @@ public class SmsAuthController implements InterceptorSkipController { } // 인증번호 생성 및 발송 - String authCode = smsAuthService.generateAuthCode(); - boolean sent = smsAuthService.sendAuthCode(userInfo.getCphnno(), authCode); + String authCode = getSmsAuthService().generateAuthCode(); + boolean sent = getSmsAuthService().sendAuthCode(userInfo.getCphnno(), authCode); if (sent) { // 세션에 인증번호 저장 (기존 UserInfo, LoginVo 유지) - LoginVo loginVo = smsAuthService.getLoginVoFromSession(session); - smsAuthService.setupAuthSession(session, userInfo, loginVo, authCode); + LoginVo loginVo = getSmsAuthService().getLoginVoFromSession(session); + getSmsAuthService().setupAuthSession(session, userInfo, loginVo, authCode); return SmsAuthDto.builder() .sent(true) - .maskedPhone(smsAuthService.maskPhoneNumber(userInfo.getCphnno())) - .expiresAt(smsAuthService.getExpiresAt(session)) - .resendableAt(smsAuthService.getResendableAt(session)) + .maskedPhone(getSmsAuthService().maskPhoneNumber(userInfo.getCphnno())) + .expiresAt(getSmsAuthService().getExpiresAt(session)) + .resendableAt(getSmsAuthService().getResendableAt(session)) .build(); } else { return SmsAuthDto.builder() @@ -103,11 +83,8 @@ public class SmsAuthController implements InterceptorSkipController { @RequestMapping(value = "/resend.json", method = RequestMethod.POST) @ResponseBody public SmsAuthDto resendAuthCode(HttpServletRequest request) { - // 프로퍼티 리로드 (DB 변경 대응) - reloadProperty(); - HttpSession session = request.getSession(); - UserInfo userInfo = smsAuthService.getUserInfoFromSession(session); + UserInfo userInfo = getSmsAuthService().getUserInfoFromSession(session); if (userInfo == null) { log.warn("SMS 인증 세션 정보 없음 - resend 요청"); @@ -117,29 +94,29 @@ public class SmsAuthController implements InterceptorSkipController { .build(); } - if (!smsAuthService.canResend(session)) { - long waitTime = smsAuthService.getResendWaitTime(session); + if (!getSmsAuthService().canResend(session)) { + long waitTime = getSmsAuthService().getResendWaitTime(session); log.debug("재발송 대기 중 - 남은 시간: {}ms", waitTime); return SmsAuthDto.builder() .sent(false) .errorMessage(String.format("%.0f초 후에 재발송이 가능합니다.", Math.ceil(waitTime / 1000.0))) - .resendableAt(smsAuthService.getResendableAt(session)) + .resendableAt(getSmsAuthService().getResendableAt(session)) .build(); } // 새 인증번호 생성 및 발송 - String authCode = smsAuthService.generateAuthCode(); - boolean sent = smsAuthService.sendAuthCode(userInfo.getCphnno(), authCode); + String authCode = getSmsAuthService().generateAuthCode(); + boolean sent = getSmsAuthService().sendAuthCode(userInfo.getCphnno(), authCode); if (sent) { - LoginVo loginVo = smsAuthService.getLoginVoFromSession(session); - smsAuthService.setupAuthSession(session, userInfo, loginVo, authCode); + LoginVo loginVo = getSmsAuthService().getLoginVoFromSession(session); + getSmsAuthService().setupAuthSession(session, userInfo, loginVo, authCode); return SmsAuthDto.builder() .sent(true) - .maskedPhone(smsAuthService.maskPhoneNumber(userInfo.getCphnno())) - .expiresAt(smsAuthService.getExpiresAt(session)) - .resendableAt(smsAuthService.getResendableAt(session)) + .maskedPhone(getSmsAuthService().maskPhoneNumber(userInfo.getCphnno())) + .expiresAt(getSmsAuthService().getExpiresAt(session)) + .resendableAt(getSmsAuthService().getResendableAt(session)) .build(); } else { return SmsAuthDto.builder() @@ -157,8 +134,8 @@ public class SmsAuthController implements InterceptorSkipController { public SmsAuthDto verifyAuthCode(HttpServletRequest request, @RequestParam("authCode") String authCode) { HttpSession session = request.getSession(); - UserInfo userInfo = smsAuthService.getUserInfoFromSession(session); - LoginVo loginVo = smsAuthService.getLoginVoFromSession(session); + UserInfo userInfo = getSmsAuthService().getUserInfoFromSession(session); + LoginVo loginVo = getSmsAuthService().getLoginVoFromSession(session); if (userInfo == null || loginVo == null) { log.warn("SMS 인증 세션 정보 없음 - verify 요청"); @@ -168,16 +145,16 @@ public class SmsAuthController implements InterceptorSkipController { .build(); } - if (smsAuthService.isExpired(session)) { + if (getSmsAuthService().isExpired(session)) { log.warn("SMS 인증번호 만료됨 - userId: {}", userInfo.getUserid()); - smsAuthService.clearAuthSession(session); + getSmsAuthService().clearAuthSession(session); return SmsAuthDto.builder() .success(false) .errorMessage("인증번호가 만료되었습니다. 다시 시도해주세요.") .build(); } - if (!smsAuthService.validateAuthCode(session, authCode)) { + if (!getSmsAuthService().validateAuthCode(session, authCode)) { log.warn("SMS 인증번호 불일치 - userId: {}", userInfo.getUserid()); return SmsAuthDto.builder() .success(false) @@ -187,7 +164,7 @@ public class SmsAuthController implements InterceptorSkipController { // 인증 성공 - 로그인 완료 처리 try { - smsAuthService.clearAuthSession(session); + getSmsAuthService().clearAuthSession(session); // MainController의 loadUserInfoAndSetupSession 호출 String result = mainController.loadUserInfoAndSetupSession(request, session, loginVo, userInfo); @@ -236,7 +213,7 @@ public class SmsAuthController implements InterceptorSkipController { @ResponseBody public SmsAuthDto getStatus(HttpServletRequest request) { HttpSession session = request.getSession(); - UserInfo userInfo = smsAuthService.getUserInfoFromSession(session); + UserInfo userInfo = getSmsAuthService().getUserInfoFromSession(session); if (userInfo == null) { return SmsAuthDto.builder() @@ -246,10 +223,10 @@ public class SmsAuthController implements InterceptorSkipController { } return SmsAuthDto.builder() - .enabled(smsAuthService.isEnabled()) - .maskedPhone(smsAuthService.maskPhoneNumber(userInfo.getCphnno())) - .expiresAt(smsAuthService.getExpiresAt(session)) - .resendableAt(smsAuthService.getResendableAt(session)) + .enabled(getSmsAuthService().isEnabled()) + .maskedPhone(getSmsAuthService().maskPhoneNumber(userInfo.getCphnno())) + .expiresAt(getSmsAuthService().getExpiresAt(session)) + .resendableAt(getSmsAuthService().getResendableAt(session)) .build(); } } diff --git a/src/main/java/com/eactive/eai/rms/ext/kjb/smsauth/SmsAuthService.java b/src/main/java/com/eactive/eai/rms/ext/kjb/smsauth/SmsAuthService.java index 702713d..17deb65 100644 --- a/src/main/java/com/eactive/eai/rms/ext/kjb/smsauth/SmsAuthService.java +++ b/src/main/java/com/eactive/eai/rms/ext/kjb/smsauth/SmsAuthService.java @@ -3,6 +3,7 @@ package com.eactive.eai.rms.ext.kjb.smsauth; import com.eactive.apim.portal.user.entity.UserInfo; import com.eactive.eai.rms.common.login.LoginVo; import com.eactive.ext.kjb.common.KjbProperty; +import com.eactive.ext.kjb.common.KjbPropertyHolder; import com.eactive.ext.kjb.common.KjbUmsException; import com.eactive.ext.kjb.ums.KjbUmsService; import com.eactive.ext.kjb.ums.UmsBizWorkCode; @@ -17,6 +18,7 @@ import java.time.format.DateTimeFormatter; @Slf4j public class SmsAuthService { + private static volatile SmsAuthService instance; // 세션 키 public static final String SESSION_KEY_AUTH_CODE = "SMS_AUTH_CODE"; @@ -33,20 +35,36 @@ public class SmsAuthService { public static final String MODE_HHMM00 = "hhmm00"; public static final String MODE_FIXED = "fixed"; - private final KjbProperty kjbProperty; - private final KjbUmsService kjbUmsService; private final SecureRandom random = new SecureRandom(); - public SmsAuthService(KjbProperty kjbProperty) { - this.kjbProperty = kjbProperty; - this.kjbUmsService = new KjbUmsService(kjbProperty); + private SmsAuthService() { + // 싱글톤 + } + + public static SmsAuthService getInstance() { + if (instance == null) { + synchronized (SmsAuthService.class) { + if (instance == null) { + instance = new SmsAuthService(); + } + } + } + return instance; + } + + private static KjbProperty getProperty() { + return KjbPropertyHolder.get(); + } + + private KjbUmsService getUmsService() { + return KjbUmsService.getInstance(); } /** * SMS 인증이 필요한지 확인 */ public boolean isRequired(UserInfo userInfo) { - if (!kjbProperty.isSmsAuthEnabled()) { + if (!getProperty().isSmsAuthEnabled()) { log.debug("SMS 인증 비활성화 상태"); return false; } @@ -74,10 +92,11 @@ public class SmsAuthService { * 인증번호 생성 */ public String generateAuthCode() { - String mode = kjbProperty.getSmsAuthMode(); + KjbProperty prop = getProperty(); + String mode = prop.getSmsAuthMode(); if (MODE_FIXED.equalsIgnoreCase(mode)) { - String fixedValue = kjbProperty.getSmsAuthFixedValue(); + String fixedValue = prop.getSmsAuthFixedValue(); log.debug("SMS 인증번호 생성 (fixed 모드): {}", fixedValue); return fixedValue; } else if (MODE_HHMM00.equalsIgnoreCase(mode)) { @@ -105,7 +124,7 @@ public class SmsAuthService { .authNumber(authCode) .build(); - kjbUmsService.sendKakaoAlimTalk(guid, UmsBizWorkCode.VERIFY_PHONE, cleanedPhone, content); + getUmsService().sendKakaoAlimTalk(guid, UmsBizWorkCode.VERIFY_PHONE, cleanedPhone, content); log.info("SMS 인증번호 발송 완료 - guid: {}, phone: {}****{}", guid, cleanedPhone.substring(0, 3), cleanedPhone.substring(cleanedPhone.length() - 4)); log.debug("SMS 인증번호 발송 - authCode: {}", authCode); @@ -265,6 +284,13 @@ public class SmsAuthService { * SMS 인증 활성화 여부 */ public boolean isEnabled() { - return kjbProperty.isSmsAuthEnabled(); + return getProperty().isSmsAuthEnabled(); + } + + /** + * 테스트용 인스턴스 리셋 + */ + public static void resetForTest() { + instance = null; } } diff --git a/src/main/java/com/eactive/eai/rms/ext/kjb/sso/SsoController.java b/src/main/java/com/eactive/eai/rms/ext/kjb/sso/SsoController.java index 63572ce..c949135 100644 --- a/src/main/java/com/eactive/eai/rms/ext/kjb/sso/SsoController.java +++ b/src/main/java/com/eactive/eai/rms/ext/kjb/sso/SsoController.java @@ -8,7 +8,6 @@ import com.eactive.eai.rms.common.datasource.DataSourceTypeManager; import com.eactive.eai.rms.common.interceptor.InterceptorSkipController; import com.eactive.eai.rms.common.login.MainController; import com.eactive.eai.rms.common.util.CommonConstants; -import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService; import com.eactive.eai.rms.data.entity.man.user.UserInfoService; import com.eactive.eai.rms.data.entity.man.user.UserRole; import com.eactive.eai.rms.data.entity.man.user.UserRoleId; @@ -16,8 +15,8 @@ import com.eactive.eai.rms.data.entity.man.user.UserRoleService; import com.eactive.eai.rms.data.entity.man.user.UserServiceTypeService; import com.eactive.eai.rms.onl.apim.masking.MaskingUtils; import com.eactive.ext.kjb.common.KjbProperty; +import com.eactive.ext.kjb.common.KjbPropertyHolder; import com.eactive.ext.kjb.sso.KjbSsoModule; -import com.eactive.ext.kjb.util.KjbPropertyInjector; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Controller; @@ -34,25 +33,18 @@ import java.util.Properties; @Slf4j @Controller public class SsoController implements InterceptorSkipController { - public static final String PROP_GORUP_ID = "Monitoring"; - private final KjbSsoModule ssoModule; - private final KjbProperty prop; private final UserInfoService userInfoService; private final UserRoleService userRoleService; private final UserServiceTypeService userServiceTypeService; private final MonitoringContext monitoringContext; private final MainController mainController; - public SsoController(MonitoringPropertyService monitoringPropertyService, - UserInfoService userInfoService, + public SsoController(UserInfoService userInfoService, UserRoleService userRoleService, UserServiceTypeService userServiceTypeService, MonitoringContext monitoringContext, MainController mainController) { - KjbPropertyInjector kjbPropertyInjector = new KjbPropertyInjector(monitoringPropertyService, PROP_GORUP_ID); - prop = kjbPropertyInjector.inject(new KjbProperty()); - this.ssoModule = KjbSsoModule.getInstance(prop); this.userInfoService = userInfoService; this.userRoleService = userRoleService; this.userServiceTypeService = userServiceTypeService; @@ -60,11 +52,19 @@ public class SsoController implements InterceptorSkipController { this.mainController = mainController; } + private KjbSsoModule getSsoModule() { + return KjbSsoModule.getInstance(); + } + + private KjbProperty getProperty() { + return KjbPropertyHolder.get(); + } + @RequestMapping(value = "/sso/login.do") public String login(HttpServletRequest req, HttpServletResponse res, @RequestParam(name = "UURL", required = false) String uurl) throws Exception { HttpSession session = req.getSession(); - String ssoId = ssoModule.getSsoId(req); + String ssoId = getSsoModule().getSsoId(req); log.debug("ssoId : {}", ssoId); log.debug("uurl : {}", uurl); @@ -83,19 +83,19 @@ public class SsoController implements InterceptorSkipController { } if (StringUtils.isEmpty(uurl)) { - uurl = ssoModule.getAscpUrl(); + uurl = getSsoModule().getAscpUrl(); } if (ssoId == null) { - ssoModule.goLoginPage(res, uurl); + getSsoModule().goLoginPage(res, uurl); return null; } else { // EAM 세션 검증 - String retCode = ssoModule.getEamSessionCheck(req, res); + String retCode = getSsoModule().getEamSessionCheck(req, res); log.debug("retCode : {}", retCode); - log.debug("Property - sso.ignore_validation = {}", prop.isSsoIgnoreValidation()); - if (!prop.isSsoIgnoreValidation() && !"0".equalsIgnoreCase(retCode)) { + log.debug("Property - sso.ignore_validation = {}", getProperty().isSsoIgnoreValidation()); + if (!getProperty().isSsoIgnoreValidation() && !"0".equalsIgnoreCase(retCode)) { // SSO 검증 실패 시 에러 메시지 설정 및 로그인 페이지로 이동 session.setAttribute("resultMsg", "SSO 로그인을 실패 하였습니다. 다른 로그인 방식을 시도 하세요"); return "redirect:/loginForm.do"; @@ -106,11 +106,11 @@ public class SsoController implements InterceptorSkipController { return "redirect:/loginForm.do"; } - if (prop.isSsoSyncInfo()) { + if (getProperty().isSsoSyncInfo()) { // 기본 정보 - Properties info = ssoModule.getUserInfos(ssoId); + Properties info = getSsoModule().getUserInfos(ssoId); // 확장 정보 - Properties extendedInfo = ssoModule.getUserExFields(ssoId); + Properties extendedInfo = getSsoModule().getUserExFields(ssoId); // 필수 필드 검증 try { diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/ObpPartnerCodeService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/ObpPartnerCodeService.java index 38d186c..a31bce6 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/ObpPartnerCodeService.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalorg/ObpPartnerCodeService.java @@ -1,12 +1,8 @@ package com.eactive.eai.rms.onl.apim.portalorg; -import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService; import com.eactive.ext.kjb.common.KjbObpException; -import com.eactive.ext.kjb.common.KjbProperty; import com.eactive.ext.kjb.obp.KjbObpModule; -import com.eactive.ext.kjb.util.KjbPropertyInjector; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; @@ -18,13 +14,9 @@ import java.util.Map; @Slf4j @Service public class ObpPartnerCodeService { - private static final String PROP_GROUP_ID = "Monitoring"; - private final KjbPropertyInjector kjbPropertyInjector; - - @Autowired - public ObpPartnerCodeService(MonitoringPropertyService monitoringPropertyService) { - this.kjbPropertyInjector = new KjbPropertyInjector(monitoringPropertyService, PROP_GROUP_ID); + private KjbObpModule getObpModule() { + return KjbObpModule.getInstance(); } /** @@ -36,10 +28,7 @@ public class ObpPartnerCodeService { Map result = new HashMap<>(); try { - KjbProperty prop = kjbPropertyInjector.inject(new KjbProperty()); - KjbObpModule obpModule = KjbObpModule.getInstance(prop); - - String partnerCode = obpModule.queryPartnerCode(compRegNo); + String partnerCode = getObpModule().queryPartnerCode(compRegNo); result.put("success", true); result.put("partnerCode", partnerCode); diff --git a/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java b/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java index 4a42c9c..365b04d 100644 --- a/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java +++ b/src/main/java/com/eactive/eai/rms/onl/common/service/ums/UmsDispatchService.java @@ -2,11 +2,8 @@ package com.eactive.eai.rms.onl.common.service.ums; import com.eactive.apim.portal.template.entity.MessageRequest; import com.eactive.apim.portal.template.repository.MessageRequestRepository; -import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService; -import com.eactive.ext.kjb.common.KjbProperty; import com.eactive.ext.kjb.ums.KjbEmailSendModule; import com.eactive.ext.kjb.ums.KjbUmsService; -import com.eactive.ext.kjb.util.KjbPropertyInjector; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -30,7 +27,6 @@ import javax.annotation.PostConstruct; @Service @Transactional(transactionManager = "transactionManagerForEMS") public class UmsDispatchService { - public static final String PROP_GORUP_ID = "Monitoring"; private final ThreadPoolExecutor executorService; private final TransactionTemplate transactionTemplate; @@ -44,19 +40,12 @@ public class UmsDispatchService { private EntityManager entityManager; private final MessageRequestRepository messageRequestRepository; - private KjbUmsService kjbUmsService; - private KjbEmailSendModule kjbEmailSendModule; - private final KjbPropertyInjector kjbPropertyInjector; - private KjbProperty kjbProperty; - @Autowired public UmsDispatchService( @Qualifier("transactionManagerForEMS") PlatformTransactionManager transactionManager, - MonitoringPropertyService monitoringPropertyService, MessageRequestRepository messageRequestRepository ) { - this.kjbPropertyInjector = new KjbPropertyInjector(monitoringPropertyService, PROP_GORUP_ID); this.messageRequestRepository = messageRequestRepository; this.executorService = new ThreadPoolExecutor( @@ -74,36 +63,17 @@ public class UmsDispatchService { @PostConstruct public void init() { - this.kjbProperty = kjbPropertyInjector.inject(new KjbProperty()); - - try { - this.kjbUmsService = new KjbUmsService(kjbProperty); - this.kjbEmailSendModule = KjbEmailSendModule.getInstance(kjbProperty, messageRequestRepository); - log.info("KjbUmsService and KjbEmailSendModule initialized successfully"); - } catch (Exception e) { - log.error("Failed to initialize KjbUmsService or KjbEmailSendModule: {}", e.getMessage(), e); - } + // KjbEmailSendModule에 repository 설정 + KjbEmailSendModule.getInstance().setRepository(messageRequestRepository); + log.info("UmsDispatchService initialized - KjbUmsService and KjbEmailSendModule ready via singleton"); } - /** - * URL 설정값이 비어있을 경우 DB에서 재로딩 후 서비스 재초기화 - */ - private void reloadPropertyIfNeeded() { - if (!kjbPropertyInjector.isUrlConfigured(kjbProperty)) { - log.info("KjbProperty URL 값이 비어있어 재로딩 시도"); - KjbProperty reloaded = kjbPropertyInjector.reloadIfUrlEmpty(kjbProperty); + private KjbUmsService getUmsService() { + return KjbUmsService.getInstance(); + } - if (reloaded != kjbProperty && kjbPropertyInjector.isUrlConfigured(reloaded)) { - this.kjbProperty = reloaded; - try { - this.kjbUmsService = new KjbUmsService(kjbProperty); - this.kjbEmailSendModule = KjbEmailSendModule.getInstance(kjbProperty, messageRequestRepository); - log.info("KjbProperty 재로딩 후 서비스 재초기화 완료"); - } catch (Exception e) { - log.error("KjbProperty 재로딩 후 서비스 재초기화 실패: {}", e.getMessage(), e); - } - } - } + private KjbEmailSendModule getEmailModule() { + return KjbEmailSendModule.getInstance(); } @SuppressWarnings("unchecked") @@ -111,9 +81,6 @@ public class UmsDispatchService { public void processMessages() { log.info("Starting to process pending messages"); - // URL 설정값이 비어있을 경우 DB에서 재로딩 시도 - reloadPropertyIfNeeded(); - // 상태가 PENDING인 메시지들을 조회하고 바로 PROCESSING으로 변경 List pendingMessages = entityManager.createQuery( "SELECT m FROM MessageRequest m WHERE m.requestStatus = :status " + @@ -168,11 +135,11 @@ public class UmsDispatchService { // processMessage(message); if (KjbUmsService.isAllowChannel(message)) { - kjbUmsService.send(message); + getUmsService().send(message); } if (KjbEmailSendModule.isAllowChannel(message)) { - kjbEmailSendModule.sendEmail(message); + getEmailModule().sendEmail(message); } return null;