Spring Security의 formLogin() 동작 방식 파헤치기

심현민·2025년 9월 22일
0

Spring

목록 보기
18/18
post-thumbnail

웹 애플리케이션에서 로그인 폼은 사용자가 가장 먼저 맞이하는 관문이다.
Spring Security는 .formLogin()만으로 이 로그인 과정을 손쉽게 구현할 수 있도록 도와준다.

이 뒤에는 아주 체계적인 인증 흐름과 여러 필터들이 내부적으로 동작한다.
오늘은 사용자가 아이디와 비밀번호를 입력하고 '로그인' 버튼을 누르는 순간부터 어떤 일들이 일어나는지, formLogin()의 내부를 공부해서 정리해보겠다.

formLogin()

formLogin()은 이름 그대로 HTTP 폼(Form) 기반의 인증을 활성화하는 API이다.
SecurityConfig 설정에서 이 한 줄을 추가하는 것만으로 Spring Security는 다음과 같은 일을 처리한다.

  • 기본적인 로그인 페이지를 제공. (물론 대부분 커스텀 페이지를 사용하긴 한다.)
  • 사용자가 폼에 입력한 아이디와 비밀번호를 HttpServletRequest에서 읽어 인증을 시도
  • 인증을 처리할 핵심 필터인 UsernamePasswordAuthenticationFilter를 활성화

로그인 페이지 만나기 이전

만약 인증되지 않은 사용자가 로그인이 필요한 페이지(예: /user)에 접근한다면?
-> 사용자는 여러 보안 필터들을 거쳐 로그인 페이지로 이동한다.

  1. AuthorizationFilter

    • 사용자의 요청은 SecurityFilterChain을 통과하다가 AuthorizationFilter를 만난다.
    • 이 필터는 "사용자가 /user 페이지에 접근할 권한이 있는가?"를 검사한다.
    • 당연히 아직 인증 정보가 없으므로 AccessDeniedException 예외를 발생시킨다.
  2. ExceptionTranslationFilter

    • 발생한 예외는 ExceptionTranslationFilter에 의해 포착된다.
    • 이 필터는 예외의 종류를 분석하여 '권한 부족(Authorization)' 문제인지 아니면 '인증 부재(Authentication)' 문제인지를 판단
  3. AuthenticationEntryPoint

    • ExceptionTranslationFilter는 '인증'이 필요해서 발생했다고 판단
    • AuthenticationEntryPoint를 통해 인증 절차를 시작
  4. 로그인 페이지로 리다이렉트

    • AuthenticationEntryPoint를 통해 인증을 시작하기 위해 사용자를 로그인 페이지로 리다이렉트
    • 사용자는 로그인 화면에 도착

formLogin() 커스터 마이징

formLogin()에서 인증 성공/실패 시의 동작을 유연하게 설정 가능

로그인 성공 처리

  • .defaultSuccessUrl("/", true): 로그인 성공 시 무조건 지정된 "/" 경로로 이동시킨다.
  • .defaultSuccessUrl("/", false): (기본값) 사용자가 원래 가려던 페이지가 있었다면 그곳으로, 없다면 "/" 경로로 이동
  • .successHandler(...): 인증 성공 후 복잡한 로직을 수행하고 싶을 때 사용
    • successHandler를 설정하면 defaultSuccessUrl은 무시됩니다.

로그인 실패 처리

  • .failureHandler(...):
    - 로그인 실패 시의 동작을 정의
    - 실패 원인을 로깅하거나, 특정 에러 메시지를 담아 로그인 페이지로 다시 보내는 등의 처리가 가능
// HttpSecurity 설정 내에서...
.formLogin(form -> form
    .successHandler((request, response, authentication) -> {
        System.out.println("authentication: " + authentication.getName());
        response.sendRedirect("/home");
    })
    .failureHandler((request, response, exception) -> {
        System.out.println("exception: " + exception.getMessage());
        response.sendRedirect("/login?error=true");
    })
)

UsernamePasswordAuthenticationFilter

우리가 .formLogin()을 호출할 때 내부에서는 다음과 같은 필터가 생성된다.

// FormLoginConfigurer.java (간략화)
public FormLoginConfigurer() {
    // 폼 인증을 처리할 핵심 필터를 생성합니다.
    super(new UsernamePasswordAuthenticationFilter(), null);
    usernameParameter("username");
    passwordParameter("password");
}
  • 위 코드를 통해 UsernamePasswordAuthenticationFilter가 생성된다. 이 필터가 바로 폼 인증의 실질적인 역할을 수행한다.

    • 해당 필터는 기본적으로 /login 경로의 POST 요청을 감지
    • 해당 요청이 들어오면 요청 본문에서 아이디와 비밀번호를 추출하여 인증 토큰(UsernamePasswordAuthenticationToken)을 만든다.
    • 만들어진 토큰을 AuthenticationManager에게 전달하여 실제 인증 절차를 위임

결국, .formLogin()은 이 UsernamePasswordAuthenticationFilter를 손쉽게 설정하고 필터 체인에 추가하기 위한 편리한 API임을 알 수 있다.

profile
혼자 성장하는 것보다 함께 성장하는 것을 선호합니다.

0개의 댓글