이메일 인증, 패스워드 초기화, 패스워드 변경 알림, 계정 차단 알림, 법인 사용자 초대 취소 완료

This commit is contained in:
Rinjae
2025-11-13 21:58:10 +09:00
parent aabb9d38a9
commit 0ea44c88f3
8 changed files with 95 additions and 29 deletions
+1
View File
@@ -101,3 +101,4 @@ TODO.txt
*.lck
*.log
.claude
@@ -10,6 +10,7 @@ import com.eactive.apim.portal.common.util.SecurityUtil;
import com.eactive.apim.portal.common.util.StringMaskingUtil;
import com.eactive.apim.portal.invitation.entity.UserInvitation;
import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus;
import com.eactive.apim.portal.invitation.event.UserInvitationCancelEvent;
import com.eactive.apim.portal.invitation.event.UserInvitationEvent;
import com.eactive.apim.portal.portaluser.event.UserManagerAssignedEvent;
import com.eactive.apim.portal.invitation.repository.UserInvitationRepository;
@@ -183,7 +184,29 @@ public class UserManFacade {
throw new IllegalStateException("Invitation is not in a cancelable state");
}
// invitationId 로 사용자 이름 조회, 실패시 id 를 이름으로 대체
String name = portalUserRepository.findById(invitation.getInvitationEmail())
.map(PortalUser::getUserName)
.orElse(invitation.getInvitationEmail());
// name 이 이메일 일 경우 @ 앞부분 만 사용 (안하면 암호화 해야함...)
if (name.contains("@")) {
name = name.substring(0, name.indexOf("@"));
}
invitation.setStatus(InvitationStatus.CANCELED);
messageHandlerService.publishEvent(UserInvitationCancelEvent.KEY,
MessageRecipient.builder()
.username(name)
.userId(invitationId)
.build(),
new HashMap<String, Object>() {{
put("corpName", user.getPortalOrg().getOrgName());
put("managerName", user.getUserName());
}}
);
userInvitationRepository.save(invitation);
}
@@ -146,7 +146,8 @@ public class UserRegisterFacadeImpl implements UserRegisterFacade {
}
agreementsFacade.saveUserAgreements(newUser.getId(), AgreementType.PRIVACY_COLLECT_IND);
sendEmailActivation(newUser);
// 11.13 - 회원 가입단계가 아닌 로그인 단계로 이메일 인증 이동
// sendEmailActivation(newUser);
return new ValidationResponse(true,"회원가입이 완료되었습니다.");
}
@@ -96,9 +96,10 @@ public class PortalUserAuthService implements UserDetailsService {
public void initializePassword(String loginId, String userName, String mobileNumber) {
PortalUser portalUser = portalUserRepository.findByLoginIdAndUserNameAndMobileNumber(loginId, userName, mobileNumber).orElseThrow(() -> new UserNotFoundException("일치하는 사용자 정보를 찾을 수 없습니다."));
PortalUser portalUser = portalUserRepository.findByLoginIdAndUserNameAndMobileNumber(loginId, userName, mobileNumber)
.orElseThrow(() -> new UserNotFoundException("일치하는 사용자 정보를 찾을 수 없습니다."));
String tempPassword = EncryptionUtil.generateNewPassword();
String tempPassword = EncryptionUtil.generateNewPasswordForKjbank(loginId);
portalUser.setPasswordHash(passwordEncoder.encode(tempPassword));
portalUserRepository.save(portalUser);
@@ -30,7 +30,30 @@ public class EncryptionUtil {
return newpassword.toString();
}
@Value("${encryption.key:kbank_portal_application_1357902}")
/**
* 광주은행용 임시패스워드 생성
* - @ + 이메일 주소 앞 5자리 + 랜덤숫자 3자리 + !
* @return
*/
public static String generateNewPasswordForKjbank(String loginId) {
StringBuilder newpassword = new StringBuilder();
newpassword.append("@");
if (loginId.length() <= 5) {
newpassword.append(loginId);
} else {
newpassword.append(loginId.substring(0, 5));
}
for (int i = 1; i <= 3; i++) {
newpassword.append(RandomUtil.getRandomNum(0, 9));
}
newpassword.append("!");
return newpassword.toString();
}
@Value("${encryption.key:kjbank_portal_application_1357902}")
private String secretKey; // Should be 16, 24, or 32 bytes long for AES-128, AES-192, or AES-256
private static final String ALGORITHM = "AES";
@@ -5,11 +5,10 @@ import com.eactive.apim.portal.apps.user.service.PortalUserLogService;
import com.eactive.apim.portal.common.exception.UserNotFoundException;
import com.eactive.apim.portal.portaluser.entity.PortalUser;
import com.eactive.apim.portal.portaluser.repository.PortalUserRepository;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.apim.portal.template.service.MessageHandlerService;
import com.eactive.apim.portal.template.service.MessageRecipient;
import org.apache.groovy.util.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
@@ -20,6 +19,11 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* Created by Sungpil Hyun
*/
@@ -30,10 +34,15 @@ public class PortalAuthenticationFailureHandler implements AuthenticationFailure
private static final Logger logger = LoggerFactory.getLogger(PortalAuthenticationFailureHandler.class);
private final PortalUserRepository portalUserRepository;
private final PortalUserLogService userLogService;
private final MessageHandlerService messageHandlerService;
public PortalAuthenticationFailureHandler(PortalUserRepository portalUserRepository, PortalUserLogService userLogService) {
public PortalAuthenticationFailureHandler(PortalUserRepository portalUserRepository,
PortalUserLogService userLogService,
MessageHandlerService messageHandlerService) {
this.portalUserRepository = portalUserRepository;
this.userLogService = userLogService;
this.messageHandlerService = messageHandlerService;
}
@Override
@@ -53,8 +62,17 @@ public class PortalAuthenticationFailureHandler implements AuthenticationFailure
user.setLoginFailureCount(user.getLoginFailureCount() + 1);
if (user.getLoginFailureCount() >= 5) {
user.setAccountLockYn("Y");
// 계정 잠금 알림
messageHandlerService.publishEvent(
MessageCode.USER_ACCOUNT_LOCKED,
MessageRecipient.of(user),
Maps.of("reason", "5회 이상 로그인 실패로 인한 계정 잠금")) ;;
}
portalUserRepository.save(user);
} catch (UserNotFoundException e) {
logger.error("{} login try {}", username, e.getMessage());
}
@@ -5,8 +5,12 @@ import com.eactive.apim.portal.apps.user.service.PortalUserAuthService;
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums;
import com.eactive.apim.portal.portaluser.entity.PortalUserEnums;
import com.eactive.apim.portal.template.entity.MessageCode;
import com.eactive.apim.portal.template.service.MessageHandlerService;
import com.eactive.apim.portal.template.service.MessageRecipient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.groovy.util.Maps;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -25,6 +29,7 @@ public class PortalAuthenticationManager implements AuthenticationManager {
private final PortalUserAuthService portalUserAuthService;
private final PasswordEncoder passwordEncoder;
private final MessageHandlerService messageHandlerService;
private String decodePassword(String encodedPassword) {
try {
@@ -46,6 +51,16 @@ public class PortalAuthenticationManager implements AuthenticationManager {
PortalAuthenticatedUser user = (PortalAuthenticatedUser) portalUserAuthService.loadUserByUsername(username);
if (!user.isAccountNonLocked()) {
if (user.getLoginFailureCount() >= 5) {
throw new LockedException("계정이 잠겼습니다. 비밀번호 초기화 또는 관리자에게 문의하세요.");
}
throw new LockedException("로그인 할 수 없습니다. 비밀번호 초기화 또는 관리자에게 문의하세요.");
}
// 비밀번호 검증
if (!passwordEncoder.matches(decodedPassword, user.getPassword())) {
// 암호화가 되어 있지 않을 수도 있어 그대로 비교 검증
@@ -83,9 +98,7 @@ public class PortalAuthenticationManager implements AuthenticationManager {
}
}
if (!user.isAccountNonLocked()) {
throw new LockedException("로그인 할 수 없습니다. 비밀번호 초기화 또는 관리자에게 문의하세요.");
}
UsernamePasswordAuthenticationToken authorityToken =
new UsernamePasswordAuthenticationToken(user, decodedPassword, user.getAuthorities());
@@ -61,26 +61,12 @@
<!-- 제출 버튼 -->
<div class="btn_application m_top">
<a type="button" class="common_btn_type_1 gray btn_cancel"><span>취소</span></a>
<div class="btn_gap"></div>
<button type="button" class="common_btn_type_1 btn_complete" disabled><span>인증 완료</span></button>
<!-- <div class="btn_gap"></div>-->
<!-- <button type="button" class="common_btn_type_1 btn_complete" disabled><span>인증 완료</span></button>-->
</div>
</form>
</div>
<!-- &lt;!&ndash; 탭 메뉴 &ndash;&gt;-->
<!-- <div class="tab-wrap">-->
<!-- <div class="tabs">-->
<!-- <ul class="tab_nav tabm">-->
<!-- <li class="active"><a href="#tab1">이메일 인증</a></li>-->
<!-- </ul>-->
<!-- &lt;!&ndash; 이메일 인증 폼 &ndash;&gt;-->
<!-- <div class="tab active" id="tab1">-->
<!-- -->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
</section>