개요
OpenID Connect(OIDC)를 통해 애플리케이션과 Keycloak 간의 인증 및 데이터 요청 흐름을 분석하고, 주요 상호작용을 세부적으로 다룹니다.
주제
- 실습용 OIDC Playground 애플리케이션 실행
- Discovery 엔드포인트 이해
- 사용자 인증 흐름 분석
- ID 토큰 및 UserInfo 활용
배경지식
OIDC에서의 scope 의미와 사용 방법
[정의]
- scope는 OAuth 2.0 및 OpenID Connect(OIDC)에서 클라이언트가 요청할 수 있는 권한의 범위를 정의하는 매개변수입니다.
- 이를 통해 클라이언트가 액세스하려는 사용자 정보 또는 API 리소스의 종류를 명시할 수 있습니다. 사용자의 동의를 기반으로 애플리케이션이 접근할 수 있는 데이터 범위를 제어하는 데 사용됩니다.
- OIDC의 scope는 클라이언트 애플리케이션의 권한을 명확히 정의하고, 사용자 데이터 접근을 통제하는 중요한 요소입니다.
[주요 OIDC scope의 종류]
OIDC에서 scope는 표준적으로 정의된 값과 사용자 지정 값으로 나뉩니다.
- 표준 scope:
- openid:
- OIDC 요청에서 반드시 포함되어야 하며, 인증 요청임을 나타냅니다.
- 이 scope가 포함되면 ID 토큰을 클라이언트에 반환합니다.
- profile:
- 사용자의 기본 정보에 접근(예: 이름, 성, 프로필 사진).
- 반환 정보: name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, updated_at.
- email:
- 사용자의 이메일 주소와 이메일 확인 상태 반환.
- 반환 정보: email, email_verified.
- address:
- 사용자의 주소 정보 반환.
- 반환 정보: formatted, street_address, locality, region, postal_code, country.
- phone:
- 사용자의 전화번호와 확인 상태 반환.
- 반환 정보: phone_number, phone_number_verified.
- openid:
- 사용자 정의 scope:
- 개발자가 API나 리소스를 보호하기 위해 정의한 특정 권한.
- 예: read:messages, write:messages.
[scope의 사용 방법]
- 클라이언트 요청에 포함: 클라이언트는 OIDC 인증 요청 시 필요한 scope를 명시합니다. 이 정보는 Authorization Server(Keycloak 등)에 전달됩니다.
GET /authorize?response_type=code &client_id=example-client &redirect_uri=https://example.com/callback &scope=openid profile email &state=xyz123
- openid: ID 토큰을 요청.
- profile: 사용자 기본 정보 요청.
- email: 사용자 이메일 요청.
- 예제:
- 사용자의 동의:
- 사용자는 클라이언트가 요청한 scope를 승인(또는 거부)합니다.
- 사용자가 동의한 scope의 범위 내에서만 클라이언트가 정보에 접근할 수 있습니다.
- Access Token 및 ID Token 반환:
- 승인된 scope는 토큰에 포함됩니다.
- 클라이언트는 토큰을 기반으로 사용자 정보(UserInfo Endpoint)나 API를 호출.
[유의할 점]
- scope 최소화:
- 꼭 필요한 데이터만 요청해 사용자의 신뢰를 유지.
- 예: 이메일만 필요한 경우 email만 요청.
- 권한 거부 처리:
- 사용자가 요청한 scope를 승인하지 않을 경우의 처리 로직 필요.
- 보안 고려:
- Access Token과 Refresh Token은 안전한 저장소에 보관.
- 가능한 짧은 수명(Time-to-Live, TTL) 설정.
OIDC 플레이그라운드 애플리케이션 실행
app.js
var KC_URL = process.env.KC_URL || "<http://10.77.77.41:31180/auth>";
index.html
<label>Issuer</label><input id="input-issuer" value="KC_URL/realms/jeff-realm">
Keycloak
클라이언트 생성:
- ID: oidc-playground
Discovery
검색 엔드포인트 이해
OIDC Discovery(검색) 명세
- OIDC Relying Party 라이브러리의 상호 운용성과 가용성 측면에서 모두 중요합니다.
- OIDC 제공자가 구현 여부를 결정할 수 있는 명세입니다. Keycloak을 포함한 대부분의 OIDC 제공자는 해당 명세를 구현합니다.
- OIDC Provider에 대한 base URL(issuer URL)을 알기만 하면 신뢰자는 제공자에 대한 유용한 정보를 많이 검색할 수 있습니다.
[OpenID Provider Configuration]
- authorization_endpoint: 인증 요청을 위한 URL
- token_endpoint: 토큰 요청
- introspection_endpoint: 점검 요청
- userinfo_endpoint: UserInfo 요청
- grant_types_supported: 지원 가능한 승인 유형 리스트
- response_types_supported: 지원 가능한 응답 유형 리스트
Authentication
Keycloak을 통해 사용자를 인증하는 가장 보편적인 방법은 OIDC 인가 코드 흐름을 사용하는 것입니다.
- client_id
- keycloak에 등록된 애플리케이션 client ID
- scope
- default: openid
- prompt:
- none: keycloak에 로그인 된 경우만 사용자를 인증
- login: 재인증을 항상 요청
- max_age
- 사용자가 인증을 유지하는 최대 시간(s)
[Authentication Request]
Keycloak 로그인을 하면 다시 애플리케이션으로 리다이렉트되고 인증 응답이 표시됩니다.
- code: 애플리케이션 ID 토큰과 리프레시 토큰을 얻기 위해 사용되는 인가 코드이다.
- 애플리케이션 인가 코드를 토큰으로 교환할 수 있다.
code=a596683a-c6b4-4e51-9875-538010ace673.65357d64-2d07-4498-bbc8-46e5af7084d4.af12e150-814d-4911-a43f-0af6873c3446
Token
[Token Request]
- max_age를 60초로 설정했기에 인증 응답을 수신하고 토큰 요청을 전송하는 데 1분 이상 걸리면 요청이 실패합니다.
- 인가 코드는 한 번만 유효하므로 2개 이상의 토큰 요청에 포함된 경우 요청이 실패합니다.
[Token Response]
- access_token: JWS(Json Web Signature) 포맷을 가지는 Keycloak 접근 토큰
- expires_in: 접근 토큰의 만료 시간
- refresh_expires_in: 리프레시 토큰의 만료 시간
- token_type: 접근 토큰의 유형, Keycloak은 항상 Bearer를 사용
- session_state: Keycloak 사용자의 세션 ID
- scope: 애플리케이션은 인증 요청에서 Keycloak의 scope를 요청하지만 반환된 토큰의 실제 범위는 요청된 범위와 일치하지 않을 수 있습니다.
ID 토큰 이해하기
- 포맷: <Header>.<Playload>.<Signature>
- Header,Playload: Base64URL로 인코딩된 JSON 문서
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJOLXRTZmVjR3d4VkxJMnBmTERVaUI1d2ZUVDBFLWlHS2RUeWRZbHdFWVNrIn0.eyJleHAiOjE3MzI0NjYxMDksImlhdCI6MTczMjQ2NTgwOSwiYXV0aF90aW1lIjoxNzMyNDY1NzgyLCJqdGkiOiJiNmQzYTk5NC05NTA4LTQ4NmMtYjliMi0yM2NiY2MxNTM1NDIiLCJpc3MiOiJodHRwOi8vMTAuNzcuNzcuNDE6MzExODAvcmVhbG1zL2plZmYtcmVhbG0iLCJhdWQiOiJvaWRjLXBsYXlncm91bmQiLCJzdWIiOiJkZTJmODEzYS1jNGRhLTQ0YmYtYmFhMi1iNTRjZTA1OGIyNWIiLCJ0eXAiOiJJRCIsImF6cCI6Im9pZGMtcGxheWdyb3VuZCIsInNpZCI6IjY1MzU3ZDY0LTJkMDctNDQ5OC1iYmM4LTQ2ZTVhZjcwODRkNCIsImF0X2hhc2giOiJvMTJkclZvdkhBWFFkQnVTamhySUtRIiwiYWNyIjoiMSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IkplZmYgSGFuIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiamVmZiIsImdpdmVuX25hbWUiOiJKZWZmIiwiZmFtaWx5X25hbWUiOiJIYW4iLCJlbWFpbCI6ImplZmZAZXhhbXBsZS5jb20ifQ.B5E3HijFc3kH6QxsSKEJoFFCLjFPyzXbMBKkq5lzMd5q3sQQ2l6WOFSqwSZwXT6y5URoP-zybR_S5Fq76EIJ3dKOrq5w25wevbCg-fehvV5NGmCqsB937qMtHU1_xJEun-BMxxgHK685sEnp5VUkae_C4I4PD42WGRfzJLSdtU7HJQ5NYwVYzvtoC7IGwS1DQbbz7CNt-8HiN53tEm0dvMRkFb8FlRQ-0uHQVe1bwjVeV6EyIIP9-CIJgtWjXTvxiqAVqqW6hTE7u_otgAqHMgQT9JsX04Yox2aesfAzilFYbzy-ILfG1sVNSdEgh-JEgqPE8ld_aR9wn_5HEs8O1g",
- 일반적으로 ID 토큰은 노출 위험을 줄이기 위해 짧은 지속 시간을 가진다. 이는 애플리케이션이 사용자를 재인증해야 한다는 의미가 아니라 업데이트된 ID 토큰을 획득하는데 사용되는 별도의 리프레시 토큰이 있다는 의미입니다.
- 리프레시 토큰은 유효 기간이 훨씬 길며 keycloak에서만 직접 사용할 수 있습니다.
[디코딩된 ID Token]
Payload:
- exp: 토큰 만료 일시
- unix epoch 시간 변환 사이트 https://www.epochconverter.com/
- iat: 토큰 발행 일시
- auth_time: 사용자 최종 인증 일시
- jti: 토큰의 고유 ID
- aud: 사용자를 인증하는 신뢰자가 포함된 토큰 사용 주체
- azp: 토큰 발생 대상
- sub: 인증된 사용자의 고유 ID.
Refresh
- 리프레시 응답에 리프레시 토큰도 포함되어 있습니다.
- 애플리케이션이 추후 ID 토큰을 갱신할 때 업데이트된 리프레시 토큰을 사용하는 것이 중요합니다.
- 키 순환:
- Keycloak은 보안을 위해 주기적으로 토큰 서명에 사용하는 암호화 키를 변경하며, 이를 "키 순환"이라고 합니다.
- 클라이언트는 갱신된 키로 서명된 새 리프레시 토큰을 사용합니다.
- 유휴 세션: 클라이언트는 유휴 세션 기능을 가지며 리프레시 토큰은 관련 세션보다 더 빨리 만료될 수 있습니다.
- 리프레시 토큰 노출 탐지: 리프레시 토큰 노출을 탐지하기 위해 Keycloak은 리프레시 토큰의 재사용을 허용하지 않습니다.
- 키 순환:
[ID Token]
- exp, iat, jti이 변경된 것을 제외하고 거의 동일한 값을 갇는다.
[토큰 갱신의 이점]
- 애플리케이션이 재인증을 수행하지 않고 사용자의 정보를 업데이트할 수 있습니다.
사용자 프로파일 업데이트
[사용자의 이메일, 성명 변경 후 리프레시 요청]
email과 name이 변경된걸 확인할 수 있습니다.
[group attribute 추가]
[property 추가]
[Add Client scopes to Clients]
optional client scopes에 해당 클레임을 추가했기 때문에 클라이언트가 해당 범위를 명시적으로 요청해야합니다.(디폴트: 범위에 추가하면 클라이언트에 기본적으로 포함)
클라이언트는 특정 시간에 필요한 정보만 요청할 수 있으며 해당 작업은 사용자가 애플리케이션에 접근하기 위해 동의가 필요할때 유용합니다.
현재는 Send Refresh Request 버튼을 클릭해도 사용자 정의 속성이 ID 토큰에 추가되지 않습니다.
[신규 인증 요청]
- myclaim scope 포함
ID 토큰에 역할 추가
- 기본적으로 ID 토큰은 역할을 가지고 있지 않습니다.
- 기본적으로 모든 역할은 전체 클라이언트에 추가되며 개별 클라이언트의 접근 권한을 제한해야 하는 경우에 적절하지 않습니다.
- ID토큰은 특정 클라이언트에 대해 사용자를 인증하기 위해서만 사용되기 때문에 큰 영향이 없지만, 다른 서비스에 접근하기 위해 사용되는 접근 토큰의 경우에는 큰 문제가 될 수 있습니다.
[Client Scopes] → [roles] → [Mappers] → [realm reols] → [Add to ID Token enabled]
[Send Refresh Request] realm_access
ID 토큰에서 realm_access 를 확인할 수 있다.
UserInfo 엔드포인트 호출
- ID 토큰을 통해 인증된 사용자에 대한 정보를 검색할 수 있을 뿐만 아니라 OIDC 흐름을 통해 획득한 접근 토큰으로 UserInfo 엔드포인트를 호출할 수도 있다.
[UserInfo]
- 해당 응답은 사용자 속성만 포함하는 단순한 JSON 응답입니다.
- 클라이언트 범위 및 프로토콜 매퍼를 통해 Keycloak ID 토큰에서 반환하는 정보를 설정할 수 있는것 처럼 UserInfo 엔드포인트에서 반환되는 정보도 설정할 수 있습니다.
[dedicated clinet scopes]
[Add mapper]
[Refresh] → [UserInfo]
사용자 로그아웃 처리
로그아웃 흐름
OIDC 기반 로그아웃 처리의 일반적인 흐름은 다음과 같습니다:
- 사용자가 애플리케이션의 로그아웃 버튼을 클릭합니다.
- 애플리케이션은 OIDC RP-Initiated Logout 요청을 생성하여 OIDC Provider(Keycloak)와 상호작용합니다.
- 사용자는 Keycloak End Session 엔드포인트로 리다이렉트되어 세션 종료 처리가 진행됩니다.
주요 엔드포인트와 매개변수
- End Session 엔드포인트: OIDC Metadata의 end_session_endpoint에 정의되어 있음.
- 매개변수:
- id_token_hint: 발급된 ID 토큰으로 로그아웃할 클라이언트 및 세션 식별.
- post_logout_redirect_uri: 로그아웃 후 사용자를 리다이렉트할 URI.
- state: 로그아웃 요청 및 리다이렉트 과정에서 클라이언트 상태 유지.
- ui_locales: 로그아웃 시 표시할 UI의 지역 설정.
Keycloak의 처리 방식
Keycloak은 End Session 요청을 수신하면 다음 작업을 수행합니다:
- 동일한 세션에 속한 모든 클라이언트들에게 로그아웃 신호를 전송.
- 세션을 만료시켜 모든 토큰(ID, 접근, 리프레시)을 무효화.
로그아웃 구현 방법
ID 및 접근 토큰 만료를 활용한 로그아웃
OIDC 환경에서 ID 및 접근 토큰의 만료 시간은 자연스러운 로그아웃 상태를 구현하는 데 유용합니다.
[동작 원리]
- ID 및 접근 토큰은 기본적으로 짧은 유효 기간을 가짐.
- Keycloak에서 세션이 만료되면 리프레시 토큰도 더 이상 사용할 수 없음.
- 애플리케이션은 신규 토큰 요청이 실패하면 사용자 로그아웃을 처리.
[장점]
- 설정 및 구현이 간단.
- 모든 클라이언트와 애플리케이션에서 동작.
[단점]
- 모든 애플리케이션에 로그아웃 상태가 반영되기까지 몇 분 소요 가능.
- 긴 토큰 유효기간을 사용하는 환경에서는 Token Introspection 엔드포인트를 사용해 주기적으로 유효성을 확인해야 함.
OIDC 세션 관리를 활용한 로그아웃 (비추천)
Keycloak은 특수 세션 쿠키를 통해 사용자의 세션 상태를 모니터링합니다.
[동작 원리]
- Keycloak의 iframe을 사용하여 세션 쿠키 상태를 확인.
- 세션 상태 변경이 탐지되면 애플리케이션에 이벤트 전송.
[문제점]
- iframe 의존성:
- 브라우저의 서드파티 콘텐츠 차단 정책으로 인해 iframe 로드가 실패할 수 있음.
- Cross-Origin 문제:
- 애플리케이션이 Keycloak과 다른 도메인에 있을 경우 쿠키 접근 불가.
[결론]
iframe을 사용하는 이 방식은 브라우저 호환성 및 신뢰성 문제로 인해 추천되지 않습니다.
OIDC 백-채널 로그아웃
Keycloak이 로그아웃 요청을 처리할 때, 백-채널 로그아웃 엔드포인트로 등록된 모든 클라이언트에 로그아웃 토큰을 전송합니다.
[동작 원리]
- 로그아웃 요청 시 Keycloak은 로그아웃 토큰(서명된 JWT)을 클라이언트에 전송.
- 클라이언트는 로그아웃 토큰의 서명을 검증 후 연관된 세션을 종료.
[장점]
- 서버 사이드 애플리케이션에서 효과적.
- 사용자 세션을 확실히 종료 가능.
[단점]
- 상태 유지 클러스터 환경:
- 동일 세션에 대한 로그아웃 요청을 특정 인스턴스로 라우팅하는 로드 밸런서 설정 필요
- 하지만 로드 밸런서에서 처리하는 것이 쉽지 않기 때문에 일반적으로 애플리케이션단에 처리
- 비상태 유지 애플리케이션:
- 로그아웃 요청 처리 및 세션 만료 동기화가 어려움.
OIDC 프론트-채널 로그아웃
프론트-채널 로그아웃은 OIDC 제공자의 로그아웃 페이지에서 각 클라이언트의 iframe을 렌더링하여 로그아웃을 수행합니다.
[동작 원리]
- OIDC 로그아웃 페이지가 각 애플리케이션의 프론트-채널 로그아웃 엔드포인트를 호출.
- 애플리케이션이 로그아웃 작업을 처리.
[문제점]
- OpenID Provider가 로그아웃 성공 여부를 확인할 방법이 없음.
- 브라우저 서드파티 콘텐츠 차단, iframe 제한 등의 문제로 신뢰성 낮음.
[결론]
클라이언트 애플리케이션에 로그아웃 신호를 전달하는 용도로는 적합하지만, 신뢰성을 요구하는 환경에는 비추천.
선택
- ID 및 접근 토큰 만료: 기본적으로 모든 환경에서 추천.
- 백-채널 로그아웃: 서버 사이드 애플리케이션에서 필수적.
- 프론트-채널 로그아웃: 클라이언트 애플리케이션의 로그아웃 보조 수단.
- 세션 관리 기반 로그아웃: 실시간 로그아웃이 필수적일 때만 제한적으로 사용.
'DevOps > Keycloak' 카테고리의 다른 글
[Keycloak]애플리케이션 보안설정: 인증 및 권한 관리 (0) | 2025.01.19 |
---|---|
[Keycloak]Install keycloak with helm charts (0) | 2025.01.19 |
[Keycloak]개요 (0) | 2025.01.19 |
[Keycloak]OAuth, OpenID Connect, SAML, Zero Trust 개념 (0) | 2025.01.12 |