
기존에 로그인 폼에 추가 입력값을 받도록 수정
기존에는 아이디와 비밀번호만 받아서 로그인 처리를 하였으나 요구사항은 일반사용자(b2c)와 회사사용자(b2b)를 구분하여
회사사용자의 경우에는 회사코드를 받아서 처리하는 로직이 필요하다.
회사코드는 선택에 따라 value값을 compcode라는 name으로
사용자 구분코드는 hidden되어있는 input에 division이라는 이름으로 로그인 폼 전송시에 포함시켜서 전송하였다.


CustomUsernamePasswordAuthenticationFilter
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final String SPRING_SECURITY_FORM_COMPCODE_KEY = "compcode";
private static final String SPRING_SECURITY_FORM_DIVISION_KEY = "division";
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
String division = obtainDivision(request);
String compcode = obtainCompcode(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
CustomUsernamePasswordAuthenticationToken authRequest = new CustomUsernamePasswordAuthenticationToken(username, password, compcode, division);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private String obtainCompcode(HttpServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_COMPCODE_KEY);
}
private String obtainDivision(HttpServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_DIVISION_KEY);
}
}
CustomUsernamePasswordAuthenticationToken
public class CustomUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
private final String compcode;
private final String division;
public CustomUsernamePasswordAuthenticationToken(Object principal, Object credentials, String compcode, String division) {
super(principal, credentials);
this.compcode = compcode;
this.division = division;
}
public CustomUsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, String compcode, String division) {
super(principal, credentials, authorities);
this.compcode = compcode;
this.division = division;
}
public String getCompcode() {
return compcode;
}
public String getDivision() {
return division;
}
}
CustomAuthenticationProvider
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private MyUserDetailsServiceImpl userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
String compcode = ((CustomUsernamePasswordAuthenticationToken)authentication).getCompcode();
String division = ((CustomUsernamePasswordAuthenticationToken)authentication).getDivision();
User user = (User) userDetailsService.loadUserByUsername(username, compcode, division);
checkUserStatus(user);
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException(username);
}
return new CustomUsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities(), compcode, division);
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(CustomUsernamePasswordAuthenticationToken.class);
}
private void checkUserStatus(User user) throws AuthenticationException {
if (!user.isEnabled()) {
if (!user.isAccountNonLocked()) {
throw new LockedException(user.getUsername());
} else if (!user.isAccountNonExpired()) {
throw new AccountExpiredException(user.getUsername());
} else {
throw new DisabledException(user.getUsername());
}
} else if (!user.isAccountNonLocked()) {
throw new LockedException(user.getUsername());
} else if (!user.isAccountNonExpired()) {
throw new AccountExpiredException(user.getUsername());
} else if (!user.isCredentialsNonExpired() && ((MySecurityUser)user).getFmdpUser().getPasswordRespite() <= 0) {
throw new CredentialsExpiredException(user.getUsername());
} else if (((MySecurityUser)user).getFmdpUser().getUserOrgList().isEmpty()) {
throw new DisabledException(user.getUsername());
}
}
}
CustomUserDetailsServiceImpl
@Service
public class CustomUserDetailsServiceImpl implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsServiceImpl.class);
private static final String COMMON = "COMMON";
@Autowired
LoginService loginService;
@Value("${default.passwordperiod}")
private int passwordPeriod;
@Value("${default.passwordrespite}")
private int passwordRespite;
@Value("${default.css}")
private String defaultCss;
@Override
public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {
if (logger.isDebugEnabled()) {
logger.debug(MyConstants.LOG_PARAM, this.getClass().getName(), "loadUserByUsername", loginId);
}
Map<String, Object> params = new HashMap<>();
params.put("loginId", loginId);
params.put("passwordPeriod", passwordPeriod);
params.put("passwordRespite", passwordPeriod + passwordRespite);
params.put("defaultCss", defaultCss);
UserVO userVo = loginService.findByUsername(params);
if (userVo == null) {
throw new UsernameNotFoundException(loginId);
}
// set common role
setCommonRole(userVo);
return new FmdpSecurityUser(userVo);
}
public UserDetails loadUserByUsername(String loginId, String compcode, String division) throws UsernameNotFoundException {
if (logger.isDebugEnabled()) {
logger.debug(MyConstants.LOG_PARAM, this.getClass().getName(), "loadUserByUsername", loginId);
}
Map<String, Object> params = new HashMap<>();
params.put("loginId", loginId);
params.put("passwordPeriod", passwordPeriod);
params.put("passwordRespite", passwordPeriod + passwordRespite);
params.put("defaultCss", defaultCss);
params.put("compCode", compcode);
UserVO userVo = loginService.findByUsername(params);
if (userVo == null) {
throw new UsernameNotFoundException(loginId);
}
// set common role
setCommonRole(userVo);
return new MySecurityUser(userVo);
}
/**
* <pre>
* add common role
* </pre>
*
* @param userVo
*/
private void setCommonRole(UserVO userVo) {
List<UserRoleVO> roles = userVo.getRoles();
if (roles == null || roles.isEmpty()) {
roles = new ArrayList<>();
roles.add(createCommonRole());
userVo.setRoles(roles);
}
}
/**
* <pre>
* common role 생성
* </pre>
*
* @return
*/
private UserRoleVO createCommonRole() {
UserRoleVO userRoleVo = new UserRoleVO();
userRoleVo.setRoleId(COMMON);
userRoleVo.setRoleCode(COMMON);
userRoleVo.setRoleName(COMMON);
return userRoleVo;
}
}
public class CustomSuccessHandler implements AuthenticationSuccessHandler {
...
중략
...
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
clearAuthenticationAttributes(request);
HttpSession session = request.getSession(false);
UserVO userVo = ((FmdpSecurityUser) authentication.getPrincipal()).getFmdpUser();
String compcode = "";
String division = "";
if (authentication instanceof CustomUsernamePasswordAuthenticationToken) {
CustomUsernamePasswordAuthenticationToken token = (CustomUsernamePasswordAuthenticationToken) authentication;
compcode = token.getCompcode();
division = token.getDivision();
}
setDefaultValue(userVo);
session.setAttribute("fmdpUser", userVo);
session.setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, new Locale(userVo.getLanguage()));
// get user access list
UserVO vo = MySessionUtil.getMyUserFromSession();
List<String> menulist = loginService.selectAccessList(vo.getUserId());
// get admin access list
if (MySessionUtil.isManager() || MySessionUtil.isAdmin()) {
menulist.addAll(loginService.selectAdminAccessList());
}
// TODO: 조직제거
if (vo.getUserOrgList().isEmpty()) {
menulist.removeAll(loginService.selectExceptMenuList("B2C_USER_EXCEPT_MENU"));
if (vo.getUserCommunityOrgList().isEmpty()) {
menulist.removeAll(loginService.selectExceptMenuList("B2C_USER_COMMUNITY_EXCEPT_MENU"));
}
}
userVo.setAccessList(menulist);
MenuVO menuVO = new MenuVO();
menuVO.setTimezoneId(userVo.getTimezoneId());
menuVO.setDateFormat(userVo.getDateFormat());
List<MenuVO> totalMenuList = menuService.selectTotalMenuList(menuVO);
for (MenuVO menuVo : totalMenuList) {
setMenuAuth(menuVo, userVo.getAccessList());
}
userVo.setTotalMenuList(totalMenuList);
// save log and clear user password fail count
saveLoginLog(userVo);
sendRedirect(request, response, vo);
}
/**
* <pre>
* 메뉴 권한 설정
* </pre>
*
* @param menuVo
* @param accessList
*/
private void setMenuAuth(MenuVO menuVo, List<String> accessList) {
menuVo.setAccessYn(menuVo.getUseYn().equals("Y") && accessList.contains(menuVo.getMenuId()) ? "Y" : "N");
// menu group
if ("1".equals(menuVo.getMenuType())) {
if (menuVo.getChildren() != null && !menuVo.getChildren().isEmpty()) {
// has usable children menu
for (MenuVO childMenuVo : menuVo.getChildren()) {
setMenuAuth(childMenuVo, accessList);
}
menuVo.setAccessYn("Y".equals(menuVo.getAccessYn()) && getAccessMenuCount(menuVo) > 0 ? "Y" : "N");
} else {
// has children menu
menuVo.setAccessYn("N");
}
}
}
/**
* <pre>
* 하위 메뉴 권한 확인
* 하위 메뉴에 권한이 하나라도 없으면 해당 메뉴의 권한도 삭제
* </pre>
*
* @param menuVo
* @return
*/
private int getAccessMenuCount(MenuVO menuVo) {
int accessMenuCount = 0;
for (MenuVO childMenuVo : menuVo.getChildren()) {
if (childMenuVo.getAccessYn().equals("Y") && childMenuVo.getUseYn().equals("Y")) {
accessMenuCount ++;
}
}
return accessMenuCount;
}
private void setDefaultValue(UserVO userVo) {
setIpAddress(userVo);
setDefaultPerPage(userVo);
}
private void setIpAddress(UserVO userVo) {
userVo.setIpAddress(FmdpHttpRequestUtil.getClientIP());
}
private void setDefaultPerPage(UserVO userVo) {
if (0 == userVo.getPerPage()) {
userVo.setPerPage(defaultPerPage);
}
}
private void saveLoginLog(UserVO userVo) {
if (logger.isDebugEnabled()) {
logger.debug(MyConstants.LOG_PARAM, this.getClass().getName(), "saveLoginLog", userVo);
}
loginService.clearPwdFailCount(userVo);
loginService.insertLoginLog(userVo);
}
private void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}
private void sendRedirect(HttpServletRequest request, HttpServletResponse response, UserVO vo) throws IOException {
String targetUrl = main;
if (vo.getUserOrgList().isEmpty()) targetUrl = b2cMain;
logger.info("targetUrl: {}", targetUrl);
HttpSession session = request.getSession(false);
if (vo.isPasswordExpired()) {
// targetUrl = "/member/password";
session.setAttribute("passwordExpired", true);
} else {
session.setAttribute("passwordExpired", false);
}
if (session != null) {
String prevPage = (String) session.getAttribute("referer");
targetUrl = StringUtils.hasLength(prevPage) ? prevPage : targetUrl;
}
(new DefaultRedirectStrategy()).sendRedirect(request, response, targetUrl);
}
private void sendRedirectToLogin(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
clearAuthenticationAttributes(request);
String targetUrl = "/login";
request.setAttribute("error", true);
request.setAttribute("errorCode", MyErrorCode.WORK_PLACE_NOT_FOUND_EXCEPTION.getCode());
request.setAttribute("errorMessage", MyMessageUtil.getMessage("message.error." + MyErrorCode.WORK_PLACE_NOT_FOUND_EXCEPTION.getCode()));
request.getRequestDispatcher("/login").forward(request, response);
}
}
SecurityConfig
@Configuration
public class SecurityConfig {
@Autowired
private PrincipalOauth2UserService principalOauth2UserService;
@Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
return new FmdpAuthenticationProvider();
}
@Bean
public AuthenticationSuccessHandler successHandler() {
return new CustomSuccessHandler();
}
@Bean
public AuthenticationFailureHandler failureHandler() {
return new CustomFailureHandler();
}
@Bean
public AuthenticationSuccessHandler oauth2SuccessHandler() {
return new CustomOAuth2SuccessHandler();
}
@Bean
public AuthenticationFailureHandler oauth2FailureHandler() {
return new CustomOAuth2FailureHandler();
}
@Bean
public WebSecurityCustomizer configure() {
return web -> web.ignoring().mvcMatchers(
"/css/**",
"/js/**",
"/images/**",
"/fonts/**",
"/plugins/**",
"/favicon.ico");
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity, AuthenticationManager authenticationManager) throws Exception {
CustomUsernamePasswordAuthenticationFilter customFilter = new CustomUsernamePasswordAuthenticationFilter();
customFilter.setAuthenticationManager(authenticationManager);
customFilter.setAuthenticationSuccessHandler(successHandler());
customFilter.setAuthenticationFailureHandler(failureHandler());
httpSecurity
.cors().disable()
.csrf().disable()
.headers()
.frameOptions().disable()
.and()
.authorizeRequests().antMatchers("/", "/intro", "/login", "/loginB2c", "/sample/**", "/guest/**", "/member/**", "/api/**", "/common/log/**", "/auth/**", "/join/**", "/terms/**", "/fmdpError/**", "/extdb/familium/**").permitAll()
.and()
.authorizeRequests().antMatchers("/manage/**").hasAnyRole("ADMIN", "MANAGER")
.and()
.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.successHandler(successHandler())
.failureHandler(failureHandler())
.and()
.exceptionHandling().accessDeniedPage("/accessDenied")
.and()
.logout().logoutUrl("/logout").invalidateHttpSession(true)
.and()
.rememberMe().key("fmdp").userDetailsService(new FmdpUserDetailsServiceImpl())
.and()
.oauth2Login()
.successHandler(oauth2SuccessHandler())
.failureHandler(oauth2FailureHandler())
.loginPage("/login")
.userInfoEndpoint()
.userService(principalOauth2UserService);
httpSecurity.addFilterAt(customFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
}