시리즈 안내

이 문서는 AuthenticationSuccessHandler 구현 이후의 내용입니다.

로그인 성공·실패 핸들러의 역할과 차이를 설명합니다.


1. 핸들러가 왜 필요한가?

Spring Security의 기본 로그인 동작:

  • 성공 → 특정 URL로 리다이렉트
  • 실패 → 로그인 페이지로 리다이렉트

하지만 REST API에서는 리다이렉트가 아니라 JSON 응답을 내려줘야 합니다. 핸들러를 직접 구현하면 로그인 결과에 따라 원하는 응답을 자유롭게 만들 수 있습니다.


2. 성공 핸들러 (AuthenticationSuccessHandler)

로그인이 성공했을 때 실행됩니다.

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(
            HttpServletRequest request,
            HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {

        // authentication: 인증된 사용자 정보
        String username = authentication.getName();

        response.setStatus(HttpStatus.OK.value());
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write("{\"username\":\"" + username + "\"}");
    }
}
  • Authentication authentication : 인증 성공한 사용자 정보가 담겨 있습니다
  • authentication.getName() : username 반환
  • authentication.getAuthorities() : 권한 목록 반환
  • 응답은 response 객체에 직접 씁니다

3. 실패 핸들러 (AuthenticationFailureHandler)

로그인이 실패했을 때 실행됩니다. 비밀번호 틀림, 존재하지 않는 계정 등이 해당됩니다.

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(
            HttpServletRequest request,
            HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {

        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write("{\"message\":\"" + exception.getMessage() + "\"}");
    }
}
  • AuthenticationException exception : 실패 원인이 담긴 예외 객체
  • exception.getMessage() : 실패 사유 문자열 반환
  • 보통 401 Unauthorized를 응답 코드로 사용합니다
[실수] exception.getMessage()를 그대로 노출하지 말 것

실패 원인을 그대로 내려주면 "해당 username이 존재하지 않음" 같은 정보가 노출되어 보안에 취약합니다. 실무에서는 구체적인 원인 대신 "아이디 또는 비밀번호가 올바르지 않습니다" 같은 일반적인 메시지를 사용합니다.


4. 성공·실패 핸들러 비교

성공 핸들러실패 핸들러
인터페이스AuthenticationSuccessHandlerAuthenticationFailureHandler
메서드onAuthenticationSuccessonAuthenticationFailure
핵심 파라미터AuthenticationAuthenticationException
일반적인 응답 코드200 OK401 Unauthorized

5. SecurityConfig에 등록

http
    .formLogin(form -> form
        .successHandler(loginSuccessHandler)
        .failureHandler(loginFailureHandler)
    );
  • 두 핸들러 모두 @Component로 등록 후 주입받아 사용합니다

6. 전체 흐름

sequenceDiagram participant Client participant Filter as Security Filter participant UDS as UserDetailsService participant SH as SuccessHandler participant FH as FailureHandler Client->>Filter: POST /login (username, password) Filter->>UDS: loadUserByUsername(username) UDS-->>Filter: UserDetails 반환 alt 비밀번호 일치 Filter->>SH: onAuthenticationSuccess() SH-->>Client: 200 OK + JSON else 비밀번호 불일치 또는 계정 없음 Filter->>FH: onAuthenticationFailure() FH-->>Client: 401 Unauthorized + JSON end