[Trouble Shooting] Spring Security formLogin이 React JSON API와 맞지 않았던 이유

2026. 5. 25. 22:05·Trouble Shooting

로그인에서 formLogin 대신 AuthenticationManager를 사용한 이유

TownTalk 프로젝트에서 로그인 기능을 구현하고 있던 와중 처음에는 Spring Security의 formLogin 설정을 사용하려고 했다. formLogin을 사용하면 Spring Security가 로그인 요청을 직접 처리해주기 때문이다.

예를 들어 loginProcessignUrl을 지정하면 URL에 대한 컨트롤러를 따로 만들지 않아도 Security Filter가 요청을 가로채서 인증을 수행한다.
처음에는 이 구조가 편해 보여서 formLogin을 사용하려고 했다.
하지만 실제로 적용해보니 현재 프로젝트 구조와는 잘 맞지 않았다. 이 프로젝트는 React에서 JSON 형식으로 로그인 요청을 보내고, 백엔드는 그 요청에 대해 JSON 응답을 내려주는 방식으로 구성되어 있기 때문이다.

반면 Spring Security의 기본 formLogin은 HTML form 기반 로그인 흐름에 더 적합하다.그래서 React + JSON API 구조에서는 요청 처리 방식과 응답 방식이 맞지 않아 오히려 흐름이 헷갈렸다.

이번 글에서는 왜 formLogin 대신 AuthenticationManager를 직접 호출하는 방식으로 로그인 흐름을 정리했는지 적어보려고 한다.

헷갈렸던 부분

Spring Security를 제대로 적용해보는 건 이번이 처음이였다. 그래서 처음 formLogin을 설정하면서 loginPage와 loginProcessignUrl이 헷갈렸었다.
.formLogin(form -> form
        .loginPage("/login")
        .loginProcessingUrl("/api/auth/loginProc")
        .usernameParameter("loginId")
        .passwordParameter("password")
)
처음에는 loginPage("/login")을 설정하면 React의 /login 화면과 자연스럽게 연결되는 줄 알았지만, 이는 React 화면을 찾아주는 설정이 아니다.

인증이 필요한 요청이 들어왔을 때 Spring Security가 이동시킬 로그인 페이지 URL을 지정하는 설정이다.
또 loginProcessingUrl("/api/auth/loginProc")은 컨트롤러에 직접 매핑하는 URL이 아니다. 이 URL은 Spring Security Filter가 가로채서 로그인 인증을 처리하는 URL이다.

즉, 기본 formLogin 흐름은 서버가 HTML form 로그인을 처리하는 구조에 더 가깝다. 

문제 발생 이유

현재 프로젝트는 React 프론트와 Spring Boot 백엔드가 분리되어 있다.
React는 fetch로 JSON 요청을 보내고, 백엔드는 /api/.. 형태로 JSON 응답을 내려주고 있다.

하지만 Spring Security의 기본 formLogin은 HTML form 요청을 기준으로 동작같으며, 형태는 다음과 비슷하다.
POST /login
Content-Type: application/x-www-form-urlencoded

username=testuser&password=password123!

프로젝트 - 로그인 요청

POST /api/auth/login
Content-Type: application/json

{
  "loginId": "testuser",
  "password": "password123!"
}
요청 방식 자체가 다르기에, formLogin을 그대로 사용하면 흐름이 계속 헷갈렸다.
특히 로그인 실패 시에도 React 입장에서는 HTML redirect보다 JSON 에러 응답이 필요했다.

해결 방향

현재 구조에서는 로그인 API를 직접 만들고, 그 안에서 Spring Security의 인증 기능을 사용하는 방식이 더 자연스러웠다.
그래서 login 요청을 처리하는 코드를 작성하고, 내부에서 AuthenticationManager를 호출했다.
authenticationManager.authenticate(
        new UsernamePasswordAuthenticationToken(
                request.loginId(),
                request.password()
        )
);
이 코드는 직접 비밀번호를 비교하는 코드가 아니라, AuthenticationManager에게 인증 요청을 하는 코드이다.
실제 사용자 조회는 UserDetailsService가 하고, 비밀번호 검증은 PasswordEncoder가 담당한다.

변경 후 흐름

React LoginPage
  -> POST /api/auth/login
  -> AuthController
  -> AuthenticationManager
  -> UserDetailsService
  -> PasswordEncoder
  -> 인증 성공 또는 실패
React는 JOSN으로 로그인 요청을 보내고, 백엔드는 API 컨트롤러에서 요청을 받아 Spring Security 인증 로직에 위임한다.

React는 화면과 요청을 담당하고, Spring Security는 인증 검증을 담당하게 되어 역할이 더 명확해졌다.

정리

서버 렌더링 기반의 HTML form 로그인이라면 Spring Security의 formLogin이 자연스럽다.
하지만 현재 프로젝트처럼 React와 Spring Boot API 서버가 분리된 구조에서는 formLign보다 JSON 로그인 API를 직접 만들고, AuthenticationManager에 인증을 위임하는 방식이 적합했다.

이러한 문제를 통해, Spring Security의 formLogin은 단순히 로그인 기능을 켜는 설정이 아니라, 
HTML form 기반 인증 흐름을 전제로 한 구조라는 점을 이해하게 됐다.

그리고 이번 점을 계기로 Spring Security Filter Chain를 더 깊이있게 공부해야 한다고 느꼈고, 바로 내부 구조와 흐름에 대해서 공부할 생각이다.

 

'Trouble Shooting' 카테고리의 다른 글

[Trouble Shooting] RefreshTokenService가 필요한 이유  (0) 2026.05.28
[Trouble Shooting] Access Token과 Refresh Token은 왜 나눠서 보관할까?  (0) 2026.05.27
[Trouble Shooting] JWT 필터에서 토큰이 없는데 바로 401을 던지지 않는 이유  (0) 2026.05.26
'Trouble Shooting' 카테고리의 다른 글
  • [Trouble Shooting] RefreshTokenService가 필요한 이유
  • [Trouble Shooting] Access Token과 Refresh Token은 왜 나눠서 보관할까?
  • [Trouble Shooting] JWT 필터에서 토큰이 없는데 바로 401을 던지지 않는 이유
mins0on
mins0on
비전공자의 백엔드 개발자 공부 기록 일지입니다.
  • mins0on
    꾸준함의 가치
    mins0on
  • 전체
    오늘
    어제
    • 분류 전체보기 (65) N
      • Java (7)
      • Spring (9)
      • DataBase (1)
      • Algorithm (1)
      • Network (6)
      • 운영체제 (2)
      • 코드 분석 (26)
      • Trouble Shooting (4) N
      • Project (1)
      • Migration (3)
      • 기타 (1)
      • 개념 정리 (3)
      • Coding Test (1)
        • Baekjoon (1)
  • hELLO· Designed By정상우.v4.10.6
mins0on
[Trouble Shooting] Spring Security formLogin이 React JSON API와 맞지 않았던 이유
상단으로

티스토리툴바