- Published on
Keycloak OAuth 로그인 무한 리다이렉트 8가지 원인
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
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) 리다이렉트 루프를 “재현 가능하게” 만드는 최소 진단법
무한 리다이렉트는 브라우저 캐시/쿠키/확장프로그램 영향도 받습니다. 아래처럼 최소 조건으로 재현하면서 원인을 좁히는 것이 빠릅니다.
- 시크릿 창에서 재현
- DevTools → Network에서 Preserve log 켜고, 302 체인을 끝까지 확인
- 어떤 지점에서 쿠키가 세팅/전송되지 않는지 확인
- 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/callbackvshttps://app.example.com/callback/(슬래시)http↔https혼재(프록시 뒤에서 발생)- 포트 포함 여부(예:
: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=Lax나 SameSite=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
issuer가http://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분 컷)
- Network 302 체인에서
http로 떨어지는 순간이 있는가? → (2) redirect_uri가 Keycloak 설정과 1바이트라도 다른가? → (1)- 쿠키가 저장/전송되는가(
SameSite,Secure)? → (3) - 앱 로그에
state/nonce/correlation실패가 있는가? → (4) /token호출이 400/401인가(PKCE/secret)? → (5)- discovery의
issuer가 외부 URL과 일치하는가? → (6) - 특정 노드에서만 재현되는가(시간/NTP)? → (7)
- callback 경로가 인증 보호로 묶였는가? → (8)
인증 루프는 “원인 하나”로 끝나기도 하지만, 실제로는 (2) 프록시/HTTPS 인식 문제 + (3) 쿠키 정책 문제가 함께 얽혀 발생하는 경우가 많습니다. 먼저 외부 URL/헤더/쿠키가 정상인지 잡고, 그 다음 state/PKCE/issuer 순으로 내려가면 대부분 빠르게 해결됩니다.
부가적으로, EKS 환경에서 특정 경로만 외부 HTTPS가 실패하거나(인증 서버/토큰 엔드포인트만 실패) 네트워크 계층 문제가 보이면 EKS에서 Pod DNS는 되는데 외부 HTTPS만 실패할 때처럼 L7이 아닌 L3/L4 원인도 같이 의심해보는 것이 좋습니다.