SSO 로그인 대응
This commit is contained in:
+2
-1
@@ -244,4 +244,5 @@ gradle-gf63.sh
|
||||
eapim-admin-kjb/
|
||||
.claude
|
||||
*.report.md
|
||||
*.claude.md
|
||||
*.claude.md
|
||||
tmpclaude-*-cwd
|
||||
|
||||
@@ -205,6 +205,11 @@
|
||||
<div style="text-align: right !important;" class="form-group">
|
||||
<a style="font-size: 12px !important;" href="#pwdChgModal" data-toggle="modal" >Password Change</a>
|
||||
</div>
|
||||
<!-- SSO 로그인 버튼 -->
|
||||
<div class="form-group">
|
||||
<input type="button" id="btn_sso_login" class="form-control btn btn-success rounded submit px-3"
|
||||
value="SSO Login" onclick="location.href='<c:url value="/sso/login.do"/>'">
|
||||
</div>
|
||||
<p style="color:red;"><%=resultMsg %></p>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -118,5 +118,20 @@ public class UserInfoService extends
|
||||
return repository.findAll(predicate, pageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 대소문자 구분 없이 사용자 ID로 검색
|
||||
* @param userId 사용자 ID
|
||||
* @return 사용자 정보 (Optional)
|
||||
*/
|
||||
public Optional<UserInfo> findByUserIdIgnoreCase(String userId) {
|
||||
QUserInfo qUserInfo = QUserInfo.userInfo;
|
||||
|
||||
UserInfo result = getJPAQueryFactory()
|
||||
.selectFrom(qUserInfo)
|
||||
.where(qUserInfo.userid.equalsIgnoreCase(userId))
|
||||
.fetchOne();
|
||||
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<UserInfo> 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<UserRole> userRoles = userRoleService.findByUserId(userInfo.getUserid());
|
||||
List<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user