SPRING-SECURITY에서 로그인시 추가 입력값 처리

leekyungryul·2024년 6월 11일

spring-boot-project

목록 보기
5/5
post-thumbnail

로그인 폼 수정

기존에 로그인 폼에 추가 입력값을 받도록 수정

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

  • 기존의 로그인 폼
  • 수정된 로그인 폼

UsernamePasswordAuthenticationFilter 상속 클래스 작성

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);
    }

}

UsernamePasswordAuthenticationToken 상속 클래스 작성

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;
    }

}

AuthenticationProvider 구현 클래스 수정

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());
        }
        
    }

}

UserDetailsService 구현 클래스 수정

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;
        
    }   
}

AuthenticationSuccessHandler 구현 클래스 수정

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 클래스 수정

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();
    }
}
profile
끊임없이 노력하는 개발자

0개의 댓글