1. 배경 및 목적
패스워드 분실 시 복구절차에 따라 임시 패스워드를 발급하고 임시 패스워드로 로그인하여 패스워드를 변경하도록 합니다.
2. 개발자 가이드
• 환경설정
스프링 시큐리티와 계정관리를 연동하기 위한 AccountInitializer 클래스를 security-context.xml 에 빈으로 등록합니다.
security-context.xml
<beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd" default-lazy-init="true"> <!-- 계정관리 기능을 사용하기 위하여 AccountInitializer 클래스를 빈으로 등록하고 lazy-init 속성을 false 로 지정한다 --> <beans:bean class="smartsuit.app.bp.common.account.AccountInitializer" lazy-init="false"></beans:bean> </beans:beans>
스프링 시큐리티에서 로그인 처리에 필요한 UserDetailsService 인터페이스를 구현한 DefaultUserDetailsService 클래스에서 AccountService 를 이용하여 로그인된 사용자의 패스워드 초기화 상태를 판단하여 User 객체에 초기화여부를 boolean 유형으로 관리합니다.
DefaultUserDetailsService.java
package smartsuit.infra; import java.util.Date; import java.util.Map; import javax.inject.Inject; import javax.transaction.Transactional; import org.apache.ibatis.session.SqlSession; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import lombok.AccessLevel; import lombok.Setter; import lombok.experimental.FieldDefaults; import smartsuit.app.bp.common.account.AccountService; @Transactional @Service("defaultUserDetailsService") @FieldDefaults(level=AccessLevel.PROTECTED) public class DefaultUserDetailsService implements UserDetailsService { @Inject SqlSession sqlSession; @Inject AccountService accountService; @Setter String accountCredentialsNonExpiredName = "pw_mod_dt"; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = sqlSession.selectOne("infra-UserDetails.getUserDetails", username); if (user == null) { throw new UsernameNotFoundException(username + " id does not exist."); } //사용자 정보 조회 Map<String,Object> userInfo = sqlSession.selectOne("infra-UserDetails.getSessionUserInfo", username); user.setUserInfo(userInfo); ...(생략) //비밀번호 초기화 여부(스프링 시큐리티의 UserDetails 인터페이스는 패스워드 초기화 여부에 대한 필드가 존재하지 않기 때문에 별도의 필드를 추가해야 하며(솔루션의 User 클래스에 credentialsNonInitialized 필드가 구현되어 있습니다.) user.setCredentialsNonInitialized("N".equals(userInfo.get(accountCredentialsNonInitializedName))); ...(생략) return new UserDetailsProxy(user); } }
계정관리에서 설정된 패스워드 복잡도 설정을 클라이언트에서 사용하기 위하여 SharedController 클래스의 getSessionUser 메소드를 통하여 계정관리 설정을 내려줍니다.
SharedController.java
@Controller public class SharedController { @Inject AccountService accountService; @RequestMapping (value = "/**/getSessionUser.do") public @ResponseBody Map<String,Object> getSessionUser() { User user = Auth.getCurrentUser(); Map<String,Object> sessionUser = Maps.newHashMap(); sessionUser.put("credentialsNonExpired", user.isCredentialsNonExpired()); //패스워드 초기화 상태 전달 sessionUser.put("credentialsNonInitialized", user.isCredentialsNonInitialized()); sessionUser.put("userInfo", user.getUserInfo()); //계정관리 설정값을 포함하여 클라이언트에 전달 합니다. sessionUser.put("accountSettings", accountService.getAccountSettings()); sessionUser.put("authorities", user.getAuthorities()); return sessionUser; } }
클라이언트의 SCSessionManager 의 isCredentialsNonInitialized 함수를 이용하여 sc-mdi.html 페이지에서 패스워드 초기화 사앹를 판단하고 패스워드 변경 팝업을 활성화 합니다.
sc-mdi.html
/****************************** * 비밀번호 변경 주기 체크 ******************************/ userPopupCheck: function() { var me = this; //패스워드 초기화 if(!SCSessionManager.isCredentialsNonInitialized()) { UT.alert(me.translate("STD.MDI1005"), function() { //"임시 비밀번호로 로그인 되었습니다. 비밀번호를 변경해주시길 바랍니다." me.popupChangePassword(); }, true); } }, popupChangePassword: function() { var me = this; me.importLink("./ui/lib/mdi/sc-mdi-pw-popup.html", function() { if(!me.pwChangePopup) { me.pwChangePopup = UT.popup("sc-mdi-pw-popup", me, 350, 267, { "logout": function() { //패스워드 변경 후 로그아웃시켜 재로그인 하도록 합니다. me.logout(); } }, {title: me.translate("비밀번호변경"), closable:true}); } me.pwChangePopup.show(); }); }