UserDetails (3/4)
이전 편: [Spring Security] 2. AuthenticationSuccessHandler 구현
다음 편: [Spring Security] 4. 세션 기반 인증과 @AuthenticationPrincipal
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. 성공·실패 핸들러 비교
| 성공 핸들러 | 실패 핸들러 | |
|---|---|---|
| 인터페이스 | AuthenticationSuccessHandler | AuthenticationFailureHandler |
| 메서드 | onAuthenticationSuccess | onAuthenticationFailure |
| 핵심 파라미터 | Authentication | AuthenticationException |
| 일반적인 응답 코드 | 200 OK | 401 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