Spring Security 로그인 실패 시 에러메세지
by 볼빵빵오춘기Toy Project를 진행하면서 로그인과 회원가입 기능을 Security로 구현했지만, 로그인 시 아무런 안내 없이 실패하면 사용자는 이유를 알 수 없어 혼란스러울 수 있다.
그래서 로그인에 실패할 경우, 사용자에게 안내 문구를 제공해 왜 로그인이 되지 않는지 명확히 알려주는 것이 필요하다고 생각하게 됐다.
SecurityConfig
@Bean public LoginSuccessHandler loginSuccessHandler(){ return new LoginSuccessHandler(); } @Bean public LoginFailureHandler loginFailureHandler(){ return new LoginFailureHandler(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ http.formLogin((formLogin) -> formLogin.loginPage("/loginForm").loginProcessingUrl("/login").successHandler(loginSuccessHandler()).failureHandler(loginFailureHandler())) .authorizeHttpRequests(authorize -> authorize.requestMatchers("/admin/**").hasAnyRole("ADMIN") // /center/** url => admin 권한이 있는 사용자만 접근 가능 .anyRequest().permitAll() // 권한을 주지않은 url 이 아니면 접근 허용 ).oauth2Login((oauth2) -> oauth2 .loginPage("/loginForm").userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint .userService(principalOauth2UserService)).successHandler(loginSuccessHandler()).failureHandler(loginFailureHandler())) .exceptionHandling(exceptionHandling -> exceptionHandling .authenticationEntryPoint(customAuthenticationEntryPoint()) // 인증되지 않은 사용자가 접근할 때 처리 .accessDeniedHandler(customAccessDeniedHandler())); // 권한이 없는 사용자가 접근할 때 처리 http.csrf(CsrfConfigurer::disable).cors(Customizer.withDefaults()); return http.build(); }
※ 설정 코드 부분(더보기 참고)
더보기
@Bean public LoginSuccessHandler loginSuccessHandler(){ return new LoginSuccessHandler(); } @Bean public LoginFailureHandler loginFailureHandler(){ return new LoginFailureHandler(); }
.userService(principalOauth2UserService)).successHandler(loginSuccessHandler()).failureHandler(loginFailureHandler()))
LoginSuccessHandler
로그인을 성공하면 SecurityContextHolder를 통해 인증된 사용자의 정보를 가져와 Authentication에 정보를 갖고있다.
@Component public class LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { public LoginSuccessHandler() { // 로그인 성공 시 이동할 URL 설정 this.setDefaultTargetUrl("/"); } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // SecurityContextHolder를 통해 현재 인증된 사용자의 정보를 가져옴 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); super.onAuthenticationSuccess(request, response, authentication); } }
LoginFailureHandler
로그인 실패 시 어떤 Exception이 발생하는지 확인 후 그에 맞는 메세지를 출력하도록 한다.
@Configuration public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { String errorMessage = null; if (exception instanceof BadCredentialsException) { errorMessage = "아이디와 비밀번호를 확인해주세요."; } else if (exception instanceof InternalAuthenticationServiceException) { errorMessage = "내부 시스템 문제로 로그인할 수 없습니다. 관리자에게 문의하세요."; } else if (exception instanceof UsernameNotFoundException) { errorMessage = "존재하지 않는 계정입니다."; } else { errorMessage = "알 수없는 오류입니다."; } errorMessage = URLEncoder.encode(errorMessage, "UTF-8"); setDefaultFailureUrl("/loginForm?error=" + errorMessage); super.onAuthenticationFailure(request, response, exception); } }
실행 결과


블로그의 정보
Hello 춘기's world
볼빵빵오춘기