Keycloak 이 OIDC 표준을 활용해 어플리케이션 사용자를 인증하는 방법에 대해 알아보자
위 코드는 OIDC 의 일반적인 개념을 이해하는 데 초점을 맞추기 위해서 OIDC 를 안전한 방식으로 구현되어 있지 않다.
앞서 포스트에 이어서 Client 를 위 플레이그라운드 어플리케이션에 맞춰 생성해준다.
cleint id: oidc-playground
access type: public
valid redirect URIs: http://localhost:8000/
web origins: http://localhost:8000
이후 http://localhost:8080/realms/myrealm/.well-known/openid-configuration
에 접속하여 표준 엔드포인트인 OpenID 제공자 메타데이터를 확인할 수 있다. 플레이그라운드 어플리케이션에서도 해당 기능을 제공하고 있으니 확인할 수 있다.
위 메타데이터를 분해하면 다음과 같다.
authorization_endpoint: 인증 요청을 위한 URL
token_endpoint: 토큰 요청을 위한 URL
introspection_endpoint: 점검 요청을 위한 URL
userinfo_endpoint: UserInfo 요청을 위한 URL
grant_types_supported: 지원 가능한 승인 유형 리스트
response_types_supported: 지원 가능한 응답 타입 리스트
위 grant_types_supported
에서 지원하는 authorization_code 유형으로 사용자 인증 방법을 구현해보자.
위 이미지는 카카오 서버를 통해 어플리케이션에 로그인하는 흐름을 나타낸다. 간단하게 요약하면 다음과 같다.
1. 클라이언트는 어플리케이션 서버에 로그인 요청한다.
2. 어플리케이션 서버는 카카오 인증 요청을 생성하여 클라이언트에게 보낸다. 이때 인증 요청에는 어플리케이션 서버에서 명시한 쿼리 파라미터도 포함된다.
3. 클라이언트는 카카오로 리다이렉트되어 로그인을 진행한다.
4. 카카오 로그인이 완료됐다면 콜백 url 를 통해 인가 코드를 어플리케이션에 전달한다.
5. 어플리케이션 서버는 인가 코드를 통해 ID토큰과 액세스 토큰을 발급받는다. ID 토큰을 기존에 카카오로부터 받은 public key 를 통해 해당 ID 토큰이 유효한지 확인하고 유저에게 세션을 할당한다.
이제 플레이그라운드 어플리케이션에서 Keycloak 을 이용해 OIDC 를 통해 사용자 인증 처리하는 방법을 확인해보자
Generate Authentication Request
버튼을 눌러 인증 요청을 생성한다. 그럼 Authentication Request 섹션에 어디로 로그인 해야되는지, 파라미터는 어떻게 명시되었는지 확인할 수 있다. Send Authentication Request
버튼을 누르면 Keycloak 로그인 페이지로 넘어가고, 로그인을 하면 다시 원래 페이지로 돌아와 Authentication Response 섹션에서 Keycloak 으로부터 받은 인가 코드를 확인할 수 있다.
이제 발급받은 인가 코드를 ID token 및 refresh token 을 얻는데 사용해보자.
Send Token Request
버튼을 누르면 액세스 토큰 및 ID 토큰을 확인할 수 있다. 아래 ID Token(JWT) 을 디코딩한 header, payload, signature 을 디테일하게 볼 수 있다.
ID 토큰에 포함된 몇몇 클레임들을 살펴보자.
exp: 1732180417, # 토큰 만료 일시
iat: 1732180117, # 토큰 발행 일시
auth_time: 1732179835, # 사용자 최종 인증 일시
jti: "16c13bdd-ad26-4049-817b-bb5cd5e9594e" # 토큰의 고유 ID
aud: "oidc-playground" # 사용자를 인증하는 신뢰자가 포함된 토큰 사용 주체
azp: "oidc-playground" # 토큰 발생 대상
sub: "a3630f64-5b82-443f-841a-87a4132b2473" # 인증된 사용자의 고유 ID
이제 토큰을 리프레쉬해보자.
만료 시간(exp) 발행 시간(iat), 토큰 ID(jti) 가 변경되고, 나머지는 거의 그대로인 것을 확인할 수 있다. 이때 응답에 refresh token 이 포함되어 있는데, 이는 다음과 같은 이유로 중요하다.
이제 발급받은 ID 토큰을 이용해 userInfo 를 업데이트해보자. 필자같은 경우엔 다음과 같이 user 를 업데이트 해주었다.
그리고 Client Scope 에 다음과 같이 스코프를 추가해주었다.
이후 해당 스코프에서 mapper 를 이용해 클라이언트 범위에 사용자 정의 속성을 추가한다.
이제 생성한 scope 를 아까 만들어둔 client 에 연결해준다.
이제 다시 플레이 그라운드 어플리케이션으로 돌아와 토큰을 재발급 받으면 ID Token 에 Team 속성이 들어가 있는걸 확인할 수 있다.
또한 ID Token 을 통해 UserInfo API 를 호출하면 이때도 team 이 잘 들어가 있는 걸 확인할 수 있다.
2-Authentication 단계에 scope 에서 openid
를 제거한다면 어떻게될까? openid
를 제거한 다음에 토큰을 발급받아봤다.
보는 것과 같이 ID Token 이 존재하지 않는다. 마찬가지로 5-UserInfo 단계에서도 403 에러가 발생한다.
이는 OAuth2.0 까지만 진행되어 액세스 토큰까지만 발급을 받고, 이후 OpenID 단계는 거치지 않아 ID 토큰은 발급받지 못했기 때문에 발생한 것이다. 즉 OIDC 흐름에서 마지막 사용자 인증단계가 생략된 것이다.
로그아웃은 일반적으로 사용자가 어플리케이션의 로그아웃 버튼을 클릭함으로써 수행된다. 로그아웃 버튼을 클릭하면 어플리케이션은 OpenID Connect RP-Initiated logout 으로 요청한다.
어플리케이션은 사용자를 Keycloak End Session 엔드포인트로 리다이렉트한다. Keycloak End Session 엔드포인트는 OpenID 제공자 메타데이터에 end_session_endpoint
를 통해 확인할 수 있다.
해당 엔드포인트는 다음과 같은 파라미터를 사용한다.
id_token_hint: 이미 발급된 ID 토큰
post_logout_redirect_uri: Keycloak 에서 로그아웃을 수행한 후 리다이렉트할 주소
state: 로그아웃 요청과 리다이렉트 과정에서 클라이언트 상태를 유지하는 파라미터
ui_locales: UI 에서 사용될 언어
Keycloak 은 해당 엔드포인트에서 동일 세션의 클라이언트들에게 로그아웃을 알린다. 그런 다음 세션을 만료시켜 모든 토큰을 무효화한다. 만약 클라이언트가 토큰 리프레쉬를 요청하면 세션이 만료됐기 때문에 더이상 재발급을 받을 수 없다. 만약 토큰 만료시간이 길다면 로그아웃 요청은 했지만 어플리케이션에서 로그아웃될 때까지는 어느정도 시간이 소요될 수 있다. (재발급 받을 때까지는 인증된 걸로 처리)
이런 경우 토큰 점검 엔드포인트를 호출해 주기적으로 토큰의 유효성을 검증하는 방법도 있다.
Keycloak 이 관리하는 특수 세션 쿠키의 상태를 모니터링하는 방법이다. Keyclaok 과 어플리케이션의 도메인이 다르기 때문에 보통 iframe 을 넣어 실시간으로 Keycloak 의 특수 세션 쿠키를 모니터링한다. 그러나 이 방법은 iframe 이 정상적으로 로드되지 않거나 악의적으로 유지시킬 수도 있다. 이를 해결하는 방법이 존재하나 딱히 추천하는 방법은 아니다
Keycloak 에서 로그아웃 신호를 받으면 등록된 백엔드 로그아웃 엔드포인트 이벤트를 발생시키는 방법이다. 백엔드는 Keycloak 으로부터 로그아웃 토큰을 전송받아 유효성 체크를 하고 세션을 로그아웃시킨다. 이는 서버 사이드 어플리케이션에서 유용하게 사용된다.
Keycloak 에서 로그아웃 신호를 받으면 등록된 프론트엔드 엔드포인트로 이벤트를 발생시킨다. 보통 숨김 처리된 iframe 을 렌더링하여 로그아웃을 처리한다. 이는 비상태 유지 서버 사이드 어플리케이션 또는 클라이언트 어플리케이션에서 유용하게 사용되는 방법이다. 그러나 클라이언트가 특정 서드파티를 차단하는 익스텐션을 사용할 수 있고, OpenID 제공자가 iframe 에서 로그아웃을 하게된다면 어플리케이션 수준 쿠키에 접근할 수 없기 때문에 어플리케이션이 현재 인증 세션에 접근할 수 없다.
요약하자면 최대한 토큰의 만료시간을 짧게하는 게 보안 유지에 좋으며 즉시 로그아웃을 원한다면 OIDC 백-채널 로그아웃을 활용한다.