3 minute read

1. 문제 상황

운영 서버를 ECS 기반으로 마이그레이션한 이후, OAuth2 로그인 과정에서 간헐적으로 다음 오류가 발생했다.

authorization_request_not_found

스테이징 환경에서는 재현되지 않았고, OAuth2 관련 설정값 및 환경변수도 모두 정상적으로 구성되어 있었다.

코드 변경 없이 운영에서만 발생하는 문제였기 때문에, 프레임워크 동작 또는 인프라 환경 차이에서 원인을 찾는 방향으로 분석을 진행했다.


2. Spring Security OAuth2 로그인 동작 방식 확인

오류 메시지가 Spring Security 내부에서 발생한 것이었기 때문에,

추가로 로컬 IDE에서 디버깅을 수행하며 요청–응답 과정과 세션 저장 동작을 직접 확인했다.

이 과정을 통해 오류는 AuthorizationRequest가 정상적으로 조회되지 않는 상황에서 발생한다는 점을 명확히 확인할 수 있었다.

✔ Spring Security OAuth2 로그인 흐름 (Mermaid)

sequenceDiagram
    participant User as 사용자
    participant App as 서버(Spring Security)
    participant OAuth as OAuth2 Provider

    User->>App: 1) /oauth2/authorization 요청
    App->>App: AuthorizationRequest 생성 후 세션(HttpSession)에 저장
    App->>OAuth: OAuth2 인증 페이지로 리다이렉트

    OAuth->>User: 로그인 페이지 표시
    User->>OAuth: 로그인 완료

    OAuth->>App: 2) /login/oauth2/code 콜백 요청
    App->>App: 세션에서 AuthorizationRequest 조회
    App->>App: 조회 실패 → authorization_request_not_found 발생
    App->>User: 로그인 처리 또는 오류 반환

핵심 동작

  • OAuth2 로그인 시작 시 AuthorizationRequest가 세션에 저장
  • 콜백 요청 시 동일 세션에서 해당 AuthorizationRequest를 꺼내야 함
  • 조회 실패 시 authorization_request_not_found 발생

3. 운영 환경에서만 문제가 발생한 이유

스테이징과 운영의 인프라 구성을 다시 비교한 결과 문제가 명확해졌다.

환경 ECS 인스턴스 수
스테이징 1대 (사내 비용 문제로 단일 인스턴스 유지)
운영 3대 (로드밸런싱 구성)
  • 스테이징은 단일 서버였기 때문에 모든 요청이 항상 동일 서버에서 처리되며,
  • 세션 기반 OAuth2 흐름이 절대 깨지지 않는다.
  • 반면 운영 환경은 3개의 ECS 인스턴스를 ALB 뒤에서 운용 중이었으며,
  • Target Group의 Sticky Session(stickiness.enabled) 설정이 비활성화 상태였다.

이로 인해 실제 요청 흐름은 다음과 같은 문제가 발생할 수 있었다.

✔ 문제 상황 (Mermaid)

sequenceDiagram
    participant User as 사용자
    participant LB as 로드밸런서(ALB)
    participant AppA as 서버 A
    participant AppB as 서버 B
    participant AppC as 서버 C

    User->>LB: 1) /oauth2/authorization 요청
    LB->>AppA: 라우팅
    AppA->>AppA: 세션에 AuthorizationRequest 저장

    OAuth->>LB: 2) 콜백 요청(/login/oauth2/code)
    LB->>AppC: 라우팅(다른 서버)

    AppC->>AppC: 세션 조회 (AuthorizationRequest 없음)
    AppC->>User: authorization_request_not_found 반환

요약하면:

  • 로그인 시작 요청은 서버 A에서 처리되어 세션 저장
  • 콜백 요청은 서버 B 또는 C로 라우팅될 수 있음
  • 해당 서버는 AuthorizationRequest 세션 값을 가지고 있지 않음
  • 결과적으로 Spring Security가 AuthorizationRequest를 조회하지 못해 오류 발생

스테이징에서는 서버가 1대라 절대 발생하지 않는 문제였다.


4. 해결 방법 검토

이 문제를 해결하는 방법은 크게 두 가지가 있었다.

Sticky Session 활성화

ALB Target Group에서 Sticky Session을 활성화하면, 동일 사용자의 요청이 일정 시간 동안 같은 인스턴스로 전달된다. 설정 한 줄이면 적용할 수 있고, 애플리케이션 코드를 수정할 필요가 없다.

다만 Sticky Session이 켜지면 ALB의 로드밸런싱 알고리즘은 첫 요청에만 적용되고, 이후 요청은 쿠키 기반으로 같은 인스턴스에 고정된다. 트래픽이 많아지면 인스턴스 간 부하 불균형이 생기거나, Scale-Out으로 인스턴스를 추가해도 기존 세션이 재분배되지 않는 문제가 발생할 수 있다.

세션 외부화 (Redis)

Spring Security의 AuthorizationRequestRepository를 커스터마이징하면, AuthorizationRequest를 HttpSession이 아닌 Redis 같은 외부 저장소에 보관할 수 있다. OAuth2의 state 파라미터를 키로 사용하기 때문에, 어떤 인스턴스가 콜백을 받더라도 조회가 가능하다. Sticky Session 없이 ALB 알고리즘이 정상 작동하므로, 부하 분산이나 Scale-Out에도 영향이 없다.

대신 Redis 인프라를 추가로 구성해야 하고, 커스텀 Repository 코드도 작성해야 한다.

선택: Sticky Session

현재 서비스는 트래픽 규모가 크지 않고, 운영 인스턴스도 3대로 구성되어 있다. OAuth2 로그인 플로우는 수 초 내에 완료되기 때문에 Sticky Session의 duration을 짧게 설정하면 실질적인 부하 불균형은 거의 발생하지 않는다.

Redis 인프라를 추가하는 비용 대비 얻는 이점이 현 시점에서는 크지 않다고 판단하여, Sticky Session을 활성화하는 방식으로 해결했다.

Sticky Session 활성화 후:

  • OAuth2 로그인 시작 요청과 콜백이 동일 서버에서 처리되었고
  • AuthorizationRequest 조회가 정상적으로 이루어졌으며
  • 동일 오류는 더 이상 발생하지 않았다.

향후 트래픽이 증가하거나 인스턴스 수를 늘려야 하는 시점이 오면, 세션 외부화 방식으로 전환할 계획이다.


정리

이번 이슈의 본질은 분산 환경 자체가 아니라, Spring Security OAuth2 내부 구현체에 대한 이해 부족이었고, 그 이해 부족이 스테이징/운영 환경 차이에 의해 늦게 드러난 문제였다.

Spring Security는 OAuth2 로그인 시작 시 AuthorizationRequestRepository를 통해 AuthorizationRequest를 HttpSession에 저장하고, 콜백 요청에서 같은 세션으로 조회하는 구조를 갖는다. 이 전제를 명확히 이해하지 못해, 운영에서만 증상이 드러난 것이다. 스테이징은 단일 인스턴스라 세션 불일치가 발생하지 않아 문제가 가려졌고, 운영의 다중 인스턴스에서만 표면화됐다.

현재 상태에서의 수정 방향은 다음과 같다.

1) OAuth2 내부 구현 전제 명확화

  • AuthorizationRequest 저장/조회 흐름을 문서화
  • “세션 연속성 필요”를 설계 전제로 명시

2) 세션 연속성 확보 전략 정리

  • Sticky Session을 유지할지, Redis 기반 세션 외부화를 도입할지 기준 수립
  • 선택한 전략이 내부 구현 전제를 만족하는지 검증

결론적으로, Sticky Session은 증상을 막는 “대응책”일 뿐이고, 문제의 근본 원인은 Spring Security OAuth2 내부 구현을 충분히 이해하지 못한 것과 그 전제를 검증할 수 없었던 스테이징/운영 환경 차이였다.

참고 자료

Updated:

Comments