diff --git a/.gitignore b/.gitignore
index 2adda4b..41a9225 100644
--- a/.gitignore
+++ b/.gitignore
@@ -244,4 +244,5 @@ gradle-gf63.sh
eapim-admin-kjb/
.claude
*.report.md
-*.claude.md
\ No newline at end of file
+*.claude.md
+tmpclaude-*-cwd
diff --git a/WebContent/jsp/common/screen/emergency.jsp b/WebContent/jsp/common/screen/emergency.jsp
index 5ac9ffa..4d747d2 100644
--- a/WebContent/jsp/common/screen/emergency.jsp
+++ b/WebContent/jsp/common/screen/emergency.jsp
@@ -205,6 +205,11 @@
+
+
+ '">
+
<%=resultMsg %>
diff --git a/src/main/java/com/eactive/eai/rms/data/entity/man/user/UserInfoService.java b/src/main/java/com/eactive/eai/rms/data/entity/man/user/UserInfoService.java
index 3610129..6254ea8 100644
--- a/src/main/java/com/eactive/eai/rms/data/entity/man/user/UserInfoService.java
+++ b/src/main/java/com/eactive/eai/rms/data/entity/man/user/UserInfoService.java
@@ -118,5 +118,20 @@ public class UserInfoService extends
return repository.findAll(predicate, pageable);
}
+ /**
+ * 대소문자 구분 없이 사용자 ID로 검색
+ * @param userId 사용자 ID
+ * @return 사용자 정보 (Optional)
+ */
+ public Optional findByUserIdIgnoreCase(String userId) {
+ QUserInfo qUserInfo = QUserInfo.userInfo;
+
+ UserInfo result = getJPAQueryFactory()
+ .selectFrom(qUserInfo)
+ .where(qUserInfo.userid.equalsIgnoreCase(userId))
+ .fetchOne();
+
+ return Optional.ofNullable(result);
+ }
}
\ No newline at end of file
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 3a8ca01..594d1bb 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
@@ -1,7 +1,22 @@
package com.eactive.eai.rms.ext.kjb.sso;
+import com.eactive.apim.portal.user.entity.UserInfo;
+import com.eactive.eai.common.seed.Seed;
+import com.eactive.eai.rms.common.context.MonitoringContext;
+import com.eactive.eai.rms.common.datasource.DataSourceType;
+import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.common.interceptor.InterceptorSkipController;
+import com.eactive.eai.rms.common.login.LoginVo;
+import com.eactive.eai.rms.common.login.SessionManager;
+import com.eactive.eai.rms.common.util.CommonConstants;
+import com.eactive.eai.rms.common.util.IpUtil;
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;
+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.sso.KjbSsoModule;
import com.eactive.ext.kjb.util.KjbPropertyInjector;
@@ -14,7 +29,12 @@ import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
import java.util.Properties;
+import java.util.stream.Collectors;
@Slf4j
@Controller
@@ -23,36 +43,46 @@ public class SsoController implements InterceptorSkipController {
private final KjbSsoModule ssoModule;
private final KjbProperty prop;
+ private final UserInfoService userInfoService;
+ private final UserRoleService userRoleService;
+ private final UserServiceTypeService userServiceTypeService;
+ private final MonitoringContext monitoringContext;
-
- public SsoController(MonitoringPropertyService monitoringPropertyService) {
+ public SsoController(MonitoringPropertyService monitoringPropertyService,
+ UserInfoService userInfoService,
+ UserRoleService userRoleService,
+ UserServiceTypeService userServiceTypeService,
+ MonitoringContext monitoringContext) {
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;
+ this.monitoringContext = monitoringContext;
}
@RequestMapping(value = "/sso/login.do")
public String login(HttpServletRequest req, HttpServletResponse res,
- @RequestParam(name = "UURL", required = false) String uurl) throws Exception {
+ @RequestParam(name = "UURL", required = false) String uurl) throws Exception {
+ HttpSession session = req.getSession();
String ssoId = ssoModule.getSsoId(req);
log.debug("ssoId : {}", ssoId);
log.debug("uurl : {}", uurl);
- log.debug("KjbProperty : \n{}", prop.toJsonString(true));
-
-
// 디버깅용 쿠키 목록 출력
- Cookie[] cookies = req.getCookies();
- if (cookies == null || cookies.length == 0) {
- log.debug("No cookies");
- } else {
- for (Cookie c: cookies) {
- log.debug("Cookie[{}] : {}, Domain={}, Path={}, Max-Age={}, Secure={}, Version={}",
- c.getName(), c.getValue(), c.getDomain(), c.getPath(), c.getMaxAge(), c.getSecure(), c.getVersion());
+ if (log.isDebugEnabled()) {
+ Cookie[] cookies = req.getCookies();
+ if (cookies == null || cookies.length == 0) {
+ log.debug("No cookies");
+ } else {
+ for (Cookie c : cookies) {
+ log.debug("Cookie[{}] : Domain={}, Path={}",
+ c.getName(), c.getDomain(), c.getPath());
+ }
}
}
-
if (StringUtils.isEmpty(uurl)) {
uurl = ssoModule.getAscpUrl();
@@ -62,45 +92,270 @@ public class SsoController implements InterceptorSkipController {
ssoModule.goLoginPage(res, uurl);
return null;
} else {
-
+ // EAM 세션 검증
String retCode = ssoModule.getEamSessionCheck(req, res);
log.debug("retCode : {}", retCode);
log.debug("Property - sso.ignore_validation = {}", prop.isSsoIgnoreValidation());
if (!prop.isSsoIgnoreValidation() && !"0".equalsIgnoreCase(retCode)) {
-
- ssoModule.goErrorPage(res, Integer.parseInt(retCode));
- return null;
- }
-
- if (StringUtils.isEmpty(ssoId)) {
- throw new IllegalStateException("KJB-SSO - ssoId 획득 실패");
- }
-
-
- // 기본 정보
- Properties info = ssoModule.getUserInfos(ssoId);
- if (info != null) {
- log.debug("info - {}", info.keySet());
- info.keySet().forEach( k -> {
- log.debug("{} : {}", k, info.get(k));
- });
- } else {
- log.debug("User info is null");
- }
-
- // 확장 정보
- Properties extendedInfo = ssoModule.getUserExFields(ssoId);
- if (extendedInfo != null) {
- log.debug("extendedInfo - {}", info.keySet());
- extendedInfo.keySet().forEach( k -> {
- log.debug("{} : {}", k, extendedInfo.get(k));
- });
- } else {
- log.debug("User Extended info is null");
+ // SSO 검증 실패 시 에러 메시지 설정 및 로그인 페이지로 이동
+ session.setAttribute("resultMsg", "SSO 로그인을 실패 하였습니다. 다른 로그인 방식을 시도 하세요");
+ return "redirect:/loginForm.do";
}
+ if (StringUtils.isEmpty(ssoId)) {
+ session.setAttribute("resultMsg", "SSO 인증 정보를 획득하지 못했습니다.");
+ return "redirect:/loginForm.do";
+ }
+
+ // 기본 정보
+ Properties info = ssoModule.getUserInfos(ssoId);
+ // 확장 정보
+ Properties extendedInfo = ssoModule.getUserExFields(ssoId);
+
+ // 필수 필드 검증
+ try {
+ validateRequiredFields(info, extendedInfo);
+ } catch (IllegalStateException e) {
+ log.warn("SSO 필수 필드 검증 실패: {}", e.getMessage());
+ session.setAttribute("resultMsg", e.getMessage());
+ return "redirect:/loginForm.do";
+ }
+
+ // 사용자 정보 로깅 (개인정보 마스킹 처리)
+ String userId = info.getProperty("USERID");
+ String name = info.getProperty("NAME");
+ String email = info.getProperty("EMAIL");
+ String cellPhoneNo = extendedInfo != null ? extendedInfo.getProperty("CELLPHONENO") : null;
+
+ log.info("SSO 로그인 시도 - userId: {}", userId);
+ log.debug("SSO 사용자 정보 - userId: {}, name: {}, phone: {}, email: {}",
+ userId,
+ MaskingUtils.maskName(name),
+ maskPhoneNumber(cellPhoneNo),
+ maskEmail(email));
+
+ // 사용자 생성 또는 업데이트
+ UserInfo userInfo = findOrCreateUser(info, extendedInfo);
+
+ // 로그인 세션 설정
+ setupLoginSession(req, userInfo);
+
+ log.info("SSO 로그인 성공 - userId: {}", userId);
return "redirect:/main.do";
}
}
+
+ /**
+ * 필수 필드 검증
+ */
+ private void validateRequiredFields(Properties info, Properties extendedInfo) throws IllegalStateException {
+ validateField(info, "USERID", "사용자ID");
+ validateField(info, "NAME", "사용자명");
+ validateField(extendedInfo, "CELLPHONENO", "휴대폰번호");
+ }
+
+ private void validateField(Properties props, String key, String fieldName) {
+ String value = props != null ? props.getProperty(key) : null;
+ if (StringUtils.isEmpty(value)) {
+ throw new IllegalStateException(fieldName + " - SSO 서버로 부터 전달 받지 못했습니다.");
+ }
+ }
+
+ /**
+ * 사용자 조회 또는 생성
+ */
+ private UserInfo findOrCreateUser(Properties info, Properties extendedInfo) throws Exception {
+ String userId = info.getProperty("USERID");
+
+ // 대소문자 구분 없이 검색
+ Optional existingUser = userInfoService.findByUserIdIgnoreCase(userId);
+
+ if (existingUser.isPresent()) {
+ return updateUserIfNeeded(existingUser.get(), info, extendedInfo);
+ } else {
+ return createNewUser(info, extendedInfo);
+ }
+ }
+
+ /**
+ * 신규 사용자 생성
+ */
+ private UserInfo createNewUser(Properties info, Properties extendedInfo) throws Exception {
+ String userId = info.getProperty("USERID");
+ String name = info.getProperty("NAME");
+ String email = info.getProperty("EMAIL");
+ String cellPhoneNo = extendedInfo != null ? extendedInfo.getProperty("CELLPHONENO") : null;
+ String positionName = extendedInfo != null ? extendedInfo.getProperty("LEGACYPOSITIONNAME") : null;
+ String deptName = extendedInfo != null ? extendedInfo.getProperty("LEGACYDEPTNAME") : null;
+
+ UserInfo userInfo = new UserInfo();
+ userInfo.setUserid(userId);
+ userInfo.setUsername(name);
+ userInfo.setPassword(Seed.encrypt("!" + userId));
+ userInfo.setCphnno(cleanPhoneNumber(cellPhoneNo));
+ userInfo.setRoleidnfiname(CommonConstants.DEPT_DEVELOPER);
+
+ // 부가 필드 설정 (값이 있을 경우만)
+ if (StringUtils.isNotEmpty(email)) {
+ userInfo.setEmad(email);
+ }
+ if (StringUtils.isNotEmpty(positionName)) {
+ userInfo.setJobtlname(positionName);
+ }
+ if (StringUtils.isNotEmpty(deptName)) {
+ userInfo.setDvsnname(deptName);
+ }
+
+ userInfoService.save(userInfo);
+
+ // 기본 역할 할당
+ String roleId = monitoringContext.getStringProperty(
+ MonitoringContext.USER_DEFAULT_RULE, "newbie");
+ UserRole userRole = new UserRole();
+ userRole.setId(new UserRoleId(userId, roleId));
+ userRoleService.save(userRole);
+
+ // 서비스 타입 할당
+ DataSourceTypeManager.getDynamicDataSourceTypes().stream()
+ .map(DataSourceType::getName)
+ .forEach(type -> userServiceTypeService.insertUserServiceType(userId, type));
+
+ log.info("SSO 신규 사용자 생성 완료 - userId: {}", userId);
+ return userInfo;
+ }
+
+ /**
+ * 기존 사용자 정보 업데이트 (변경된 필드만)
+ */
+ private UserInfo updateUserIfNeeded(UserInfo userInfo, Properties info, Properties extendedInfo) {
+ boolean updated = false;
+ String userId = userInfo.getUserid();
+
+ String name = info.getProperty("NAME");
+ String email = info.getProperty("EMAIL");
+ String cellPhoneNo = extendedInfo != null ? extendedInfo.getProperty("CELLPHONENO") : null;
+ String positionName = extendedInfo != null ? extendedInfo.getProperty("LEGACYPOSITIONNAME") : null;
+ String deptName = extendedInfo != null ? extendedInfo.getProperty("LEGACYDEPTNAME") : null;
+
+ // 사용자명 업데이트
+ if (StringUtils.isNotEmpty(name) && !name.equals(userInfo.getUsername())) {
+ userInfo.setUsername(name);
+ updated = true;
+ }
+
+ // 휴대폰번호 업데이트
+ String cleanedPhone = cleanPhoneNumber(cellPhoneNo);
+ if (StringUtils.isNotEmpty(cleanedPhone) && !cleanedPhone.equals(userInfo.getCphnno())) {
+ userInfo.setCphnno(cleanedPhone);
+ updated = true;
+ }
+
+ // 이메일 업데이트
+ if (StringUtils.isNotEmpty(email) && !email.equals(userInfo.getEmad())) {
+ userInfo.setEmad(email);
+ updated = true;
+ }
+
+ // 직위명 업데이트
+ if (StringUtils.isNotEmpty(positionName) && !positionName.equals(userInfo.getJobtlname())) {
+ userInfo.setJobtlname(positionName);
+ updated = true;
+ }
+
+ // 부서명 업데이트
+ if (StringUtils.isNotEmpty(deptName) && !deptName.equals(userInfo.getDvsnname())) {
+ userInfo.setDvsnname(deptName);
+ updated = true;
+ }
+
+ if (updated) {
+ userInfoService.save(userInfo);
+ log.info("SSO 사용자 정보 업데이트 완료 - userId: {}", userId);
+ }
+
+ return userInfo;
+ }
+
+ /**
+ * 로그인 세션 설정
+ */
+ private void setupLoginSession(HttpServletRequest request, UserInfo userInfo) {
+ HttpSession session = request.getSession(true);
+
+ LoginVo dto = new LoginVo();
+ dto.setUserId(userInfo.getUserid());
+ dto.setUserName(userInfo.getUsername());
+ dto.setDepartment(userInfo.getDvsnname());
+
+ // 역할 설정
+ List userRoles = userRoleService.findByUserId(userInfo.getUserid());
+ List roles = userRoles.stream()
+ .map(r -> r.getId().getRoleId())
+ .collect(Collectors.toList());
+ dto.setRoleList(roles);
+
+ // 서비스타입 설정 (검증 없이 전체 허용)
+ dto.setUseServiceTypes(CommonConstants.USER_SERVICETYPE_NO_CHECK);
+
+ // 세션 등록
+ SessionManager.forceRegisterUserSession(request, dto);
+ session.setAttribute(CommonConstants.LOGIN, dto);
+ session.setAttribute("userId", userInfo.getUserid());
+ session.setAttribute("dualLogin", "N");
+
+ // 대시보드 타이틀 설정
+ String newTitle = monitoringContext.getStringProperty(
+ MonitoringContext.NEW_DASHBOARD_TITLE, "topLogo.gif");
+ session.setAttribute(MonitoringContext.NEW_DASHBOARD_TITLE, newTitle);
+
+ // 마지막 로그인 정보 업데이트
+ userInfo.setLastloginyms(LocalDateTime.now());
+ userInfo.setLastloginip(IpUtil.getClientIp(request));
+ userInfoService.save(userInfo);
+
+ // 세션에 마지막 로그인 정보 저장
+ session.setAttribute("LastLoginYms", userInfo.getLastloginyms()
+ .format(com.eactive.eai.data.converter.LocalDateTimeFormatters.FORMATTER_YYYYMMDDHHMMSS_14));
+ session.setAttribute("LastLoginIp", userInfo.getLastloginip());
+ }
+
+ /**
+ * 휴대폰번호에서 숫자만 추출
+ */
+ private String cleanPhoneNumber(String phone) {
+ if (StringUtils.isEmpty(phone)) {
+ return null;
+ }
+ return phone.replaceAll("[^0-9]", "");
+ }
+
+ /**
+ * 휴대폰번호 마스킹 (로그용)
+ */
+ private String maskPhoneNumber(String phone) {
+ if (StringUtils.isEmpty(phone) || phone.length() < 4) {
+ return "****";
+ }
+ // 010-1234-5678 → 010-****-5678
+ String cleaned = phone.replaceAll("[^0-9]", "");
+ if (cleaned.length() < 7) {
+ return "****";
+ }
+ return cleaned.substring(0, 3) + "****" + cleaned.substring(cleaned.length() - 4);
+ }
+
+ /**
+ * 이메일 마스킹 (로그용)
+ */
+ private String maskEmail(String email) {
+ if (StringUtils.isEmpty(email) || !email.contains("@")) {
+ return "****";
+ }
+ int atIndex = email.indexOf("@");
+ if (atIndex <= 2) {
+ return "**" + email.substring(atIndex);
+ }
+ return email.substring(0, 2) + "**" + email.substring(atIndex);
+ }
}