Published on

Keycloak OAuth 로그인 무한 리다이렉트 8가지 원인

Authors

Keycloak로 OAuth/OIDC 로그인을 붙이면, 가장 흔하게 마주치는 장애가 로그인 성공처럼 보이는데 앱으로 돌아오지 못하고 무한 리다이렉트(loop) 되는 현상입니다. 브라우저 주소창은 .../protocol/openid-connect/auth.../login-actions/authenticate ↔ 애플리케이션의 callback(또는 signin-oidc)을 계속 왕복하고, 네트워크 탭에는 302가 줄줄이 쌓입니다.

이 문제는 대체로 “인증 자체는 되었는데, 세션/쿠키/리다이렉트/상태(state) 검증이 어딘가에서 깨져서 다시 인증을 시작하는” 패턴입니다. 아래 8가지는 현장에서 가장 자주 만나는 원인이고, 각각의 증상 → 확인 포인트 → 해결 방법 순으로 정리했습니다.

참고로, 인프라(특히 ALB Ingress) 레벨에서 502/리셋이 섞여 보인다면 먼저 네트워크 안정성도 같이 점검해야 합니다. 예: EKS ALB Ingress 502 Target reset 원인과 해결

0) 리다이렉트 루프를 “재현 가능하게” 만드는 최소 진단법

무한 리다이렉트는 브라우저 캐시/쿠키/확장프로그램 영향도 받습니다. 아래처럼 최소 조건으로 재현하면서 원인을 좁히는 것이 빠릅니다.

  1. 시크릿 창에서 재현
  2. DevTools → Network에서 Preserve log 켜고, 302 체인을 끝까지 확인
  3. 어떤 지점에서 쿠키가 세팅/전송되지 않는지 확인
  4. Keycloak 서버 로그에서 이벤트/에러 확인

Keycloak 컨테이너/Pod 기준으로는 다음을 먼저 켜면 도움이 됩니다.

# Keycloak (Quarkus) 로그 레벨 예시
KC_LOG_LEVEL=info
KC_LOG_CONSOLE_COLOR=false

# 필요 시 디버그
KC_LOG_LEVEL=debug

또한 OIDC 클라이언트(앱) 측에서도 OIDC 미들웨어 로그를 올려 state, nonce, code_verifier 관련 오류가 없는지 봅니다.


1) redirect_uri 불일치(정확히는 “미세한 불일치”)

전형적인 증상

  • Keycloak 로그인 후 앱 callback으로 갔다가 다시 Keycloak로 튕김
  • Keycloak 이벤트 로그에 invalid_redirect_uri가 보이거나, 앱이 callback에서 400/401을 내고 다시 /auth로 보냄

확인 포인트

  • Keycloak Client 설정의 Valid Redirect URIs
  • 실제 요청의 redirect_uri 파라미터(대소문자, 슬래시, 포트, 쿼리 포함 여부)

특히 아래 케이스가 잦습니다.

  • https://app.example.com/callback vs https://app.example.com/callback/ (슬래시)
  • httphttps 혼재(프록시 뒤에서 발생)
  • 포트 포함 여부(예: :443)

해결

  • 가능한 한 정확히 허용하거나, 환경별로 패턴을 명확히 등록
Valid Redirect URIs 예시
- https://app.example.com/oauth/callback
- https://app.example.com/*   (가능하면 최소 범위로)

프레임워크가 내부적으로 callback 경로를 바꾸는 경우도 있으니(예: /signin-oidc) 라이브 트래픽의 redirect_uri를 그대로 캡처해서 맞추는 것이 안전합니다.


2) 프록시/Ingress 뒤에서 “외부 URL”을 잘못 인식(Hostname/HTTPS 문제)

전형적인 증상

  • 외부는 HTTPS인데 Keycloak가 중간에 HTTP로 리다이렉트 생성
  • Location: http://...가 섞이면서 브라우저가 쿠키를 안 보내거나, HSTS/혼합 콘텐츠로 꼬임
  • 루프가 특정 환경(EKS/ALB/Nginx)에서만 발생

확인 포인트

  • Keycloak가 생성하는 리다이렉트의 스킴/호스트
  • Ingress/ALB가 전달하는 X-Forwarded-Proto, X-Forwarded-Host 헤더

해결

Keycloak(Quarkus 배포)의 대표 설정은 아래 조합을 많이 씁니다.

# 외부에서 접근하는 공개 URL을 명시
KC_HOSTNAME=keycloak.example.com
KC_HOSTNAME_STRICT=true

# 프록시 뒤에 있을 때
KC_PROXY_HEADERS=xforwarded
# (환경에 따라) edge/reencrypt/passthrough
# KC_PROXY=edge

Ingress(Nginx)라면 다음 헤더가 Keycloak로 제대로 전달되는지 확인합니다.

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

ALB Ingress를 쓴다면 TLS 종료 지점과 Keycloak의 인식이 일치해야 합니다. TLS 핸드셰이크/종단 설정이 애매하면 502/504로도 보일 수 있으니 필요 시 EKS ALB Ingress 502/504 - TLS 핸드셰이크 실패 진단도 함께 확인하세요.


3) SameSite / Secure 쿠키 정책으로 세션 쿠키가 “저장/전송”되지 않음

전형적인 증상

  • 로그인 폼 제출은 성공하는데, 다음 요청에서 다시 로그인 화면으로 돌아감
  • DevTools → Application → Cookies에서 Keycloak 세션 쿠키가 없거나, 요청에 쿠키가 안 실림

확인 포인트

  • 브라우저 콘솔에 SameSite 관련 경고
  • Keycloak 도메인과 앱 도메인이 다른지(교차 사이트)

Keycloak는 인증 과정에서 여러 쿠키를 사용합니다. 교차 사이트 흐름에서 SameSite=LaxSameSite=Strict면 쿠키가 안 붙어 세션이 유지되지 않아 루프가 납니다.

해결

  • Keycloak를 앱과 같은 eTLD+1(예: 둘 다 example.com)로 두거나
  • 필요 시 쿠키 정책을 교차 사이트에 맞게 조정

Keycloak 버전/배포 방식에 따라 쿠키 설정 옵션이 달라질 수 있어, 원칙은 다음입니다.

  • 교차 사이트가 필요하면 **SameSite=None; Secure**가 필요
  • HTTPS가 아니면 Secure 쿠키를 못 쓰므로 개발환경에서만 별도 전략 필요

또한 ALB/Nginx에서 Set-Cookie 헤더가 변조/누락되지 않는지도 확인합니다.


4) OIDC state/nonce 검증 실패(앱이 callback을 거부)

전형적인 증상

  • Keycloak에서 앱 callback으로 돌아오긴 하는데, 앱이 곧바로 다시 /login으로 리다이렉트
  • 앱 로그에 state mismatch, nonce mismatch, Correlation failed 같은 메시지

확인 포인트

  • 앱이 state를 저장하는 방식(세션/쿠키/서버 메모리)
  • 로드밸런서 뒤에서 인스턴스가 바뀌는지(sticky session 여부)

해결

  • 앱이 state를 서버 세션에 저장한다면 세션 공유(예: Redis) 또는 sticky session 필요
  • 가능하면 “서버 메모리 기반 세션”을 피하고 분산 세션을 사용

Spring Security(예시)에서 세션/쿠키 기반 state가 깨질 때는 SameSite와 함께 점검해야 합니다.

# 예시: 세션을 Redis로 공유하는 방향(개념 예시)
spring:
  session:
    store-type: redis

5) PKCE(code_verifier) 저장 실패 또는 잘못된 클라이언트 타입

전형적인 증상

  • callback에서 토큰 교환 단계(/token)가 실패하고 다시 인증으로 돌아감
  • 네트워크 탭에서 /token이 400

확인 포인트

  • Public client(SPA)인데 secret을 요구하거나, 반대로 Confidential client인데 PKCE/secret 구성이 꼬임
  • 앱이 code_verifier를 어디에 저장하는지(브라우저 스토리지, 세션, 쿠키)

해결

  • SPA면 대개 Public client + PKCE 필수
  • 서버 사이드 웹앱이면 Confidential client + client secret 또는 PKCE + secret(정책에 따라)

Keycloak 클라이언트 설정에서

  • Client authentication(on/off)
  • Standard flow(Authorization Code)
  • Proof Key for Code Exchange(PKCE)

이 조합이 앱 라이브러리의 기대와 일치해야 합니다.


6) 잘못된 issuer/realm URL(Discovery 메타데이터 불일치)

전형적인 증상

  • 토큰 검증 단계에서 issuer mismatch로 실패
  • 앱이 매 요청마다 인증이 안 된 것으로 판단하고 다시 로그인으로 보냄

확인 포인트

  • 앱의 issuer 설정과 Keycloak의 discovery 문서가 일치하는지
curl -s https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration | jq .issuer
  • issuerhttp://keycloak:8080/...처럼 내부 주소로 나오면 프록시/hostname 설정 문제(2번)와 연결됩니다.

해결

  • 앱의 issuer를 discovery 기반으로 맞추고
  • Keycloak의 hostname/proxy 설정을 바로잡아 외부 issuer가 올바르게 나오게 합니다.

7) 시간 불일치(NTP)로 토큰이 “즉시 만료/아직 유효하지 않음”

전형적인 증상

  • 로그인 직후 토큰 검증에서 token is not active, exp, iat 관련 오류
  • 특정 노드/파드에서만 재현(클러스터 시간 드리프트)

확인 포인트

  • Keycloak 노드와 앱 노드의 시스템 시간
  • 컨테이너 런타임/노드의 NTP 동기화 상태

해결

  • 모든 노드에서 NTP/chrony 동기화
  • JWT 검증 라이브러리의 clock skew 허용치(필요 최소) 조정
// (개념 예시) JWT 검증에서 clock skew 허용
JwtValidators.createDefaultWithIssuer(issuer);
// 라이브러리별로 setClockSkewSeconds 같은 옵션을 제공

8) 앱의 인증 엔드포인트/리다이렉트 로직이 “보호 경로”를 잘못 지정

전형적인 증상

  • callback URL 자체가 인증 보호 대상으로 묶여 있어 callback 접근 시 다시 로그인 시작
  • /oauth/callback 또는 /signin-oidc가 security filter에 의해 차단/리다이렉트

확인 포인트

  • 라우팅/필터 체인에서 callback 경로가 permitAll인지
  • reverse proxy에서 callback 경로가 다른 서비스로 라우팅되는지

해결

callback은 익명 접근 허용이 기본입니다.

Spring Security 예시

@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {
  http
    .authorizeHttpRequests(auth -> auth
      .requestMatchers("/oauth/callback", "/login/**").permitAll()
      .anyRequest().authenticated()
    )
    .oauth2Login(oauth2 -> {});

  return http.build();
}

Nginx 라우팅 예시

location /oauth/callback {
  proxy_pass http://app;
}

location / {
  proxy_pass http://app;
}

ALB Ingress에서도 path rule 우선순위 때문에 callback이 의도치 않은 타겟 그룹으로 가는 경우가 있어, 규칙을 명시적으로 분리하는 것이 안전합니다.


실전 체크리스트(10분 컷)

  1. Network 302 체인에서 http로 떨어지는 순간이 있는가? → (2)
  2. redirect_uri가 Keycloak 설정과 1바이트라도 다른가? → (1)
  3. 쿠키가 저장/전송되는가(SameSite, Secure)? → (3)
  4. 앱 로그에 state/nonce/correlation 실패가 있는가? → (4)
  5. /token 호출이 400/401인가(PKCE/secret)? → (5)
  6. discovery의 issuer가 외부 URL과 일치하는가? → (6)
  7. 특정 노드에서만 재현되는가(시간/NTP)? → (7)
  8. callback 경로가 인증 보호로 묶였는가? → (8)

인증 루프는 “원인 하나”로 끝나기도 하지만, 실제로는 (2) 프록시/HTTPS 인식 문제 + (3) 쿠키 정책 문제가 함께 얽혀 발생하는 경우가 많습니다. 먼저 외부 URL/헤더/쿠키가 정상인지 잡고, 그 다음 state/PKCE/issuer 순으로 내려가면 대부분 빠르게 해결됩니다.

부가적으로, EKS 환경에서 특정 경로만 외부 HTTPS가 실패하거나(인증 서버/토큰 엔드포인트만 실패) 네트워크 계층 문제가 보이면 EKS에서 Pod DNS는 되는데 외부 HTTPS만 실패할 때처럼 L7이 아닌 L3/L4 원인도 같이 의심해보는 것이 좋습니다.