Published on

Keycloak OAuth 로그인 무한 302 리다이렉트 해결

Authors

서론

Keycloak을 OAuth 2.0 / OpenID Connect(OIDC) IdP로 붙인 뒤, 브라우저에서 로그인 버튼을 누르면 /auth/realms/.../protocol/openid-connect/auth → 애플리케이션 콜백 → 다시 Keycloak로… 같은 흐름이 끝없이 반복되는 경우가 있습니다. 네트워크 탭에는 302가 계속 쌓이고, 화면은 로그인 페이지로 되돌아오거나 흰 화면에서 멈춥니다.

이 현상은 대개 “로그인은 성공했는데 세션/쿠키가 유지되지 않거나, Keycloak이 자신이 어떤 URL(스킴/호스트)로 서비스되는지 잘못 인식하는” 문제로 귀결됩니다. 특히 Ingress/ALB/Nginx 같은 리버스 프록시 뒤에서 Keycloak을 운영할 때 빈번합니다.

이 글에서는 무한 302 리다이렉트의 핵심 원인을 유형별로 분해하고, Keycloak 설정(Hostname, Proxy, Cookies), 앱 설정(redirect_uri), 인프라(Forwarded headers, TLS termination)까지 한 번에 정리합니다. 인증이 꼬이면 종종 401로도 나타나므로, 연관 이슈는 Kubernetes 401 Unauthorized 원인별 해결 가이드도 함께 참고하면 좋습니다.


1) 증상 패턴으로 원인 빠르게 좁히기

1.1 브라우저 네트워크에서 보이는 전형적 루프

다음 중 하나라면 거의 확정적으로 “세션 쿠키 미설정/미전달” 또는 “redirect_uri/hostname 불일치”입니다.

  • Keycloak 로그인 폼 제출 후 302 Location: <app-callback>
  • 콜백에서 code 교환 시도 후 다시 302 Location: <keycloak-auth>
  • 혹은 콜백 자체가 다시 Keycloak로 리다이렉트(앱이 “로그인 안 됨”으로 판단)

1.2 Keycloak 로그에서 체크할 키워드

Keycloak 컨테이너 로그(또는 서버 로그)에서 아래를 찾습니다.

  • Invalid parameter: redirect_uri
  • Cookies are not enabled 또는 cookie 관련 경고
  • Failed to verify token / Invalid state(state 값이 매번 바뀌거나 검증 실패)

2) 가장 흔한 원인 Top 6와 해결책

2.1 (1순위) Keycloak이 외부 URL을 잘못 인식(프록시/Forwarded 헤더)

원인

Ingress/ALB/Nginx 뒤에서 TLS를 종료(HTTPS → HTTP)하면, Keycloak은 내부에서 자신을 http://keycloak:8080 같은 주소로 인식할 수 있습니다. 이때 Keycloak이 생성하는 redirect URL, issuer, 쿠키 속성(Secure 등)이 외부 브라우저 기준과 어긋나면서 루프가 발생합니다.

해결: Keycloak에 Proxy/Hostname을 명시

Keycloak 17+ (Quarkus 배포) 기준으로는 아래 조합이 가장 안전합니다.

# 예시: 외부에서 https://sso.example.com 으로 접근
KC_PROXY=edge
KC_HOSTNAME=sso.example.com
KC_HOSTNAME_STRICT=true
KC_HTTP_ENABLED=true
KC_HTTP_PORT=8080
KC_HOSTNAME_URL=https://sso.example.com

Docker compose 예시:

services:
  keycloak:
    image: quay.io/keycloak/keycloak:25.0
    command: ["start"]
    environment:
      KC_PROXY: "edge"
      KC_HOSTNAME: "sso.example.com"
      KC_HOSTNAME_STRICT: "true"
      KC_HOSTNAME_URL: "https://sso.example.com"
      KC_HTTP_ENABLED: "true"
      KEYCLOAK_ADMIN: "admin"
      KEYCLOAK_ADMIN_PASSWORD: "admin"
    ports:
      - "8080:8080"

Ingress/Nginx가 X-Forwarded-Proto, X-Forwarded-Host를 올바르게 전달하는지도 중요합니다.

Nginx reverse proxy 예시:

location / {
  proxy_pass http://keycloak:8080;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-Host $host;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header X-Forwarded-Port $server_port;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

> 포인트: Keycloak은 “내가 https로 서비스되는지”를 알아야 Secure 쿠키/redirect를 올바르게 만듭니다.


2.2 (2순위) SameSite/Secure 쿠키 정책으로 세션 쿠키가 브라우저에 저장되지 않음

원인

최근 브라우저는 교차 사이트(특히 iframe, 서드파티 컨텍스트)에서 쿠키를 강하게 제한합니다.

  • Keycloak 도메인: sso.example.com
  • 앱 도메인: app.example.com (서브도메인이 다르면 “사이트”로는 같을 수 있지만, 흐름/구성에 따라 제약이 생길 수 있음)
  • 혹은 완전히 다른 도메인: app.comsso.com

이때 OIDC 로그인 과정에서 Keycloak의 세션 쿠키가 저장되지 않으면, 사용자는 로그인해도 Keycloak이 “세션 없음”으로 판단해 다시 로그인으로 리다이렉트합니다.

해결 체크리스트

  • 반드시 HTTPS 사용 (SameSite=None 쿠키는 Secure 필수)
  • 프록시 뒤에서 HTTPS임을 Keycloak이 인식하도록 KC_PROXY=edge + hostname 설정
  • 브라우저 개발자 도구 > Application > Cookies에서 KEYCLOAK_SESSION, KEYCLOAK_IDENTITY 등이 생성되는지 확인

추가로, SPA에서 silent check-sso(iframe 기반) 같은 기능을 쓰면 쿠키 정책에 더 민감합니다. 이 경우는 “iframe에서 쿠키 차단”이 루프처럼 보일 수 있으니, 우선 iframe 기반 silent SSO를 끄고 정상 플로우부터 확인하세요.


2.3 (3순위) redirect_uri 불일치(정확히 일치해야 함)

원인

Keycloak의 Client 설정에서 Valid Redirect URIs가 실제 콜백 URL과 조금이라도 다르면, 인증 코드가 돌아오기 전에 Keycloak이 에러를 내거나(보통 400), 앱이 다시 인증을 시도하면서 루프처럼 보이기도 합니다.

특히 아래가 자주 틀립니다.

  • http vs https
  • 포트 포함 여부(:443)
  • path trailing slash(/callback vs /callback/)
  • 쿼리 파라미터 포함 여부

해결

Keycloak Admin Console → Clients → (해당 client) → Settings:

  • Valid Redirect URIs: 예) https://app.example.com/oauth/callback
  • Web Origins: SPA면 https://app.example.com 또는 +(권장하진 않음)

테스트 환경에서만 와일드카드를 쓰더라도 최소화하세요.

예시:

  • Valid Redirect URIs
    • https://app.example.com/*
  • Valid Post Logout Redirect URIs
    • https://app.example.com/*

2.4 (4순위) Keycloak issuer/realm URL과 애플리케이션 설정 불일치

원인

앱(또는 OIDC 라이브러리)이 issuerhttps://sso.example.com/realms/myrealm로 기대하는데, Keycloak이 내부 주소 기반 issuer를 노출하면 토큰 검증/디스커버리 단계에서 꼬입니다. 그 결과 앱은 “로그인 실패 → 다시 로그인 시도”를 반복합니다.

해결

  • Keycloak의 hostname/proxy 설정으로 issuer가 외부 URL로 나오게 만들기
  • 앱의 OIDC 설정(issuer, authorization_endpoint, token_endpoint)을 Keycloak discovery 문서와 일치시키기

디스커버리 문서 확인:

curl -s https://sso.example.com/realms/myrealm/.well-known/openid-configuration | jq .issuer

issuer가 기대값과 다르면 hostname/proxy 설정을 다시 봐야 합니다.


2.5 (5순위) Ingress/ALB가 Location 헤더를 재작성하거나, 경로(path) 기반 라우팅이 어긋남

원인

Keycloak을 /auth 같은 서브패스로 서비스하거나, ALB/Ingress가 Location을 이상하게 바꾸면 redirect가 꼬여 루프가 납니다.

  • 외부: https://example.com/auth → 내부: http://keycloak:8080/
  • 이때 Keycloak이 생성하는 URL과 프록시 rewrite 규칙이 충돌

해결

가능하면 Keycloak은 전용 서브도메인(sso.example.com)으로 운영하는 것을 권장합니다.

부득이하게 path 기반이면:

  • Keycloak의 relative path 설정(버전별 옵션 상이)
  • Ingress rewrite 규칙과 Keycloak base URL이 정확히 일치

이 구간은 설정 조합이 많아, “전용 도메인 + edge proxy”로 단순화하는 것이 가장 빠른 해결책입니다.


2.6 (6순위) 클러스터/프록시에서 헤더 또는 쿠키 크기 제한으로 세션이 깨짐

원인

일부 환경에서는 헤더 크기 제한(특히 쿠키)이 작아 Keycloak 쿠키/토큰 교환 과정에서 잘리거나 누락될 수 있습니다. 이 경우도 로그인 실패 후 재시도 루프로 보일 수 있습니다.

  • Nginx large_client_header_buffers
  • Envoy/ALB 헤더 제한

해결

  • 프록시의 헤더/쿠키 크기 제한 상향
  • 불필요한 쿠키가 과도하게 늘어나는지 점검

Kubernetes에서 프록시/인증 문제를 다룰 때는 401/403/302가 섞여 나타나므로, 트러블슈팅 관점은 Kubernetes 401 Unauthorized 원인별 해결 가이드와 유사합니다.


3) 재현 가능한 진단 절차(10분 컷)

3.1 curl로 302 체인 확인

브라우저 대신 curl로 리다이렉트 체인을 보면, 어디에서 다시 Keycloak로 되돌아가는지 선명해집니다.

curl -I -L -k https://app.example.com/login
  • Locationhttp://로 떨어지면 proxy/hostname 문제 가능성이 큼
  • Keycloak → 앱 콜백 → 다시 Keycloak이면 쿠키/세션/issuer 쪽을 의심

3.2 쿠키가 실제로 저장되는지 확인

Chrome DevTools:

  • Network에서 Keycloak 응답 헤더의 Set-Cookie 확인
  • Application → Cookies에서 해당 쿠키가 저장되었는지 확인

저장되지 않았다면:

  • Secure 누락(HTTPS 아니거나 Keycloak이 HTTP로 인식)
  • SameSite 정책 충돌
  • 도메인/경로 불일치

3.3 Keycloak 디스커버리 문서로 외부 URL 정합성 확인

curl -s https://sso.example.com/realms/myrealm/.well-known/openid-configuration | jq '{issuer, authorization_endpoint, token_endpoint}'

여기 URL이 내부 주소로 나오면, 앱은 정상적으로 토큰 검증을 못 하고 재로그인을 반복할 수 있습니다.


4) 대표 스택별 “정답 설정” 예시

4.1 Kubernetes + Nginx Ingress + Keycloak (TLS는 Ingress에서 종료)

Keycloak Deployment 환경변수 핵심:

env:
  - name: KC_PROXY
    value: "edge"
  - name: KC_HOSTNAME
    value: "sso.example.com"
  - name: KC_HOSTNAME_STRICT
    value: "true"
  - name: KC_HOSTNAME_URL
    value: "https://sso.example.com"
  - name: KC_HTTP_ENABLED
    value: "true"

Ingress는 X-Forwarded-Protohttps로 전달되도록 기본 동작/설정을 확인합니다.

4.2 Spring Security(OAuth2 Login)에서 redirect_uri 점검

Spring Boot에서 redirect-uri는 기본이 {baseUrl}/login/oauth2/code/{registrationId} 입니다. 프록시 뒤에서 baseUrl이 http로 잡히면 루프가 날 수 있습니다.

spring:
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: myclient
            client-secret: xxx
            scope: openid,profile,email
        provider:
          keycloak:
            issuer-uri: https://sso.example.com/realms/myrealm
server:
  forward-headers-strategy: framework

forward-headers-strategy는 프록시 헤더를 신뢰해 baseUrl 산정에 반영하게 도와줍니다(환경에 따라 native가 필요할 수 있음).


5) 운영에서 루프를 예방하는 체크리스트

  • Keycloak은 가능하면 전용 도메인(서브도메인) 으로 운영
  • TLS 종료 지점이 어디든, Keycloak이 외부 스킴/호스트를 정확히 알도록 KC_PROXY=edge + hostname 설정
  • Client의 Valid Redirect URIs를 실제 콜백과 1:1로 맞추기
  • .well-known/openid-configuration의 issuer가 외부 URL로 나오는지 확인
  • 브라우저에서 쿠키가 저장되는지(특히 Secure/SameSite) 확인
  • 프록시/Ingress의 헤더 크기 제한과 Location rewrite 정책 점검

인증 문제는 종종 “서버가 중간에 연결을 끊는” 것처럼 보이기도 합니다. 네트워크 레벨에서 이상 징후가 함께 보인다면 Python httpx RemoteProtocolError 서버 끊김 원인과 해결처럼 연결/프록시 계층도 같이 점검하는 편이 진단 시간을 줄여줍니다.


결론

Keycloak OAuth/OIDC 로그인 무한 302 리다이렉트는 대부분 프록시 뒤에서의 “외부 URL 인식 실패” 또는 쿠키(Secure/SameSite) 미설정/미저장에서 시작합니다. 따라서 해결의 핵심은 (1) Keycloak에 외부 hostname/https 정보를 명시하고, (2) Ingress/프록시가 Forwarded 헤더를 정확히 전달하며, (3) Client의 redirect_uri/issuer를 엄격히 일치시키는 것입니다.

위 체크리스트대로 KC_PROXY=edge, KC_HOSTNAME(_URL) 정합성, 쿠키 저장 여부, redirect_uri 일치 여부만 순서대로 확인해도 대부분의 302 루프는 빠르게 종료됩니다.