Programming-[Backend]/Keycloak

Keycloak - 3. OpenID Connect 인증, 토큰 관리, 로그아웃

컴퓨터 탐험가 찰리 2024. 7. 30. 10:55
728x90
반응형

KeyCIoak - 모던 애플리케이션을 위한 ID 및 접근관리 | 에이콘출판(주) | 스티안 토르거센, 페드로 이고르 실바 지음, 최만균 옮김

서적을 참고하여 요약한 시리즈 글이다.


 

 

1. OIDC PlayGround

 

OpenID Connect를 줄여서 OIDC라고 하며, 실습용 playground 애플리케이션이 책의 코드 4장에 들어있다. 실습 시작 단계는 아래 과정을 따르면 된다.

 

  1. npm install, npm start
  2. realm 및 client 설정을 한다
    1. Client ID: oidc-playground
    2. Access Type: public
    3. Valid Redirect URIs: http://localhost:8000/
    4. Web Origins: http://localhost:8000
  3. localhost:8000에 접속하면 아래 화면이 보인다.
  4. 이번 장의 실습 내용 역시 2장과 마찬가지로 path에 /auth가 있다. index.html, app.js 부분에서 이 부분들을 삭제해야 정상 작동된다.

 

 

1.1 검색 엔드포인트

OpenID 제공자가 구현 여부를 결정할 수 있는 선택적인 명세인데, KeyCloak 및 대부분의 OpenID 제공자는 해당 명세를 구현한다. 이런 엔드포인트를 통해 신뢰자가 제공자에 대한 정보들을 검색할 수 있게된다. 검색을 위한 엔드포인트도 표준화 되어 있으며, 아래 URL이다.

 

<base URL>/.well-known/openid-configuration

 

 

Load OpenID Provider Configuration 버튼을 누르면 위 엔드포인트로 요청을 보낸다. base URL은 http://localhost:8080/realms/myrealm 이다. 프로덕션에서 실행하는 경우, http://localhost:8080/은 실제 도메인이 되고, realms/myrealm은 지정한 realm이 된다.

 

configuration을 로드해보면 각종 요청을 위한 URL 리스트, 지원 가능한 응답 유형 리스트가 출력되는 것을 볼 수 있다.

 

 

 

 

1.2 인증

인증 과정을 수행해본다. 이전 글에서 배운 인가 flow대로 진행된다. 

 

 

각 form의 내용은 다음과 같다.

  • client_id: Keycloak에 등록된 애플리케이션의 client ID
  • scope: 디폴트 값이 openid이며 OpenID 요청을 수행할 것임을 의미함
  • prompt: login을 입력하면 사용자가 이미 로그인한 경우에도 다시 로그인을 요청한다. none을 입력하면 Keycloak은 사용자에게 로그인 화면을 표시하지 않고 사용자가 이미 Keycloak에 로그인한 경우에만 사용자를 인증한다
  • max_age: 사용자 인증을 유지하기 위한 시간이며 초 단위이다. 인가 코드는 기본적으로 1분 동안만 유효하다!
  • login_hint: 애플리케이션이 사용자의 이름을 알고 있는 경우 이 파라미터로 로그인 페이지의 사용자 이름이 자동 입력되도록 한다

 

다음 단계들을 진행하면 Request와 Response 정보들을 볼 수 있다. Response로 온 code 값이 인가 코드(authorization code) 이다. 이 인가 코드를 갖고 다음 단계를 진행하거나 다른 토큰과 교환할 수 있다.

 

다음 3단계로 진행하면 이전 단계에서 얻어온 code값이 복사되어있는 것을 볼 수 있다. Send Token Request를 클릭하여 각 제목에 해당하는 값들을 확인한다.

 

Token Response 값을 확인해보면 아래 값들이 눈에 띈다.

 

 

  • access_token: JWS 포맷을 갖는 Access Token이다.
  • expires_in: 토큰이 언제 만료되는지 알려준다
  • refresh_token_expires_in: 리프레시 토큰이 언제 만료되는지 알려준다
  • token_type: Access Token의 유형을 나타내며, Keycloak은 항상 Bearer를 사용한다
  • session_state: 사용자 세션 ID
  • scope: 사용자에게 허용되는 범위. 요청된 scope와 일치하지 않을 수도 있다

 

1.2.1 ID 토큰

중요한 ID 토큰에 대해서 좀 더 다뤄본다. ID 토큰은 JWT(JSON Web Token)로 서명되어 있으며 아래 포맷을 사용한다

<Header>.<Payload>.<Signature>

 

토큰이 ey... 형식으로 되어있는 것은 헤더 및 페이로드가 Base64URL 형식으로 인코딩 된 것이다. 이것을 디코딩하면 상세 정보들을 확인할 수 있다.

 

jwt.io 사이트에 들어가서 확인해보면 정보들을 볼 수 있다.

 

각 값들이 어떤 것을 의미하는지는 대충만 알면 될 것 같다. 이 중 jti는 토큰의 고유 ID를 의미한다.

 

 

1.2.2 Refresh 토큰

토큰의 유효기간이 살아있는 채로 4. Refresh에서 요청을 보내면 아래와 같은 응답을 받는다. Refresh Response에 Refresh_token이 포함되어있다. 애플리케이션이 추후 ID 토큰을 갱신할 때 업데이트된 refresh_token을 사용해야한다는 점이 중요하다.

 

 

1.2.3 사용자 프로파일 업데이트

사용자의 이메일, 이름을 변경하고 리프레시 요청 전송을 하면 정상적으로 변경된 것을 볼 수 있다. 사용자 정의 속성도 추가할 수 있는데, 다음 과정을 따르면 된다.

 

1. Client Scopes -> Create client scope, myattribute라는 이름으로 생성 후 save를 누른다.

 

2. Mappers -> add mapper -> By Configuration에서 User Attribute를 클릭한다.

 

다음과 같이 속성 값들을 입력한다.

  • name: myattribute
  • Mapper Type: User Attribute
  • User Attribute: myattribute
  • Token Claim Name: myattribute
  • Claim JSON Type: String

그리고 책에는 나오지 않는데, Include in token scope를 on 해줘야 아래의 token 결과에서 확인이 가능하다.

 

 

3. Clients -> oidc-playground -> Client Scopes -> Add client scope -> myattribute를 선택하고 add를 클릭한다. 이후 Default | Optional 중 Optional을 선택한다. Optional의 경우 클라이언트가 해당 범위를 명시적으로 요청해야만 한다.

 

 

그냥 2. Authentication 요청 후 3. Token 요청을 하면 myattribute가 보이지 않는다. 2. Authentication에서 scope 항목을 'openid myattribute'로 myattribute 값 까지 추가하여 전송하면, 3. Token의 Response의 scope에 myattribute가 포함된 것을 볼 수 있다.

 

 

1.2.4 역할 추가

기본적으로 ID 토큰은 역할을 갖고 있지 않다. 토큰에 Realm Roles를 추가하기 위해 아래 과정을 따라해본다.

1. Client Scopes -> roles에 들어간다.

 

2. Mappers -> realm roles에 들어가서 Add to ID token을 활성화한다.

 

이후 다시 ID Token을 확인해보면 realm access가 추가된 것을 확인할 수 있다.

 

 

1.3 UserInfo 엔드포인트 호출

 

Authentication -> Token 호출 뒤 5. User Info를 호출하면 아래 내용과 같은 응답을 얻을 수 있다.

 

 

Request에 Authorization으로 ID Token 값을 보내서 User 정보를 받아오는 것을 확인할 수 있다.

 

 

UserInfo 엔드포인트 호출 시 반환되는 정보를 변경하기 위해서는 아래 과정을 따른다.

 

1. 클라이언트 -> oidc_playground -> Client scopes -> oidc-playground-dedicated에 들어간다.

 

2. Mappers -> Configure a new mapper -> Hardcoded claim을 클릭한다.

 

3. 아래 사진처럼 Mapper 내용들을 입력한다.

 

4. User Info 요청을 다시 보내면, Token Claim Name 값에 적힌 myotherclaim 키 값으로 user info의 항목이 추가된 것을 볼 수 있다.

 

2. 로그아웃 처리

 

2.1 로그아웃 파라미터와 작동방식

openID의 end_session_endpoint를 사용하여 수행한다. 사용할 수 있는 파라미터들은 다음과 같다

  • id_token_hint: 이미 발급된 토큰 ID를 표기. 로그아웃을 하고자하는 대상의 세션을 식별하기 위해 사용됨
  • post_logout_redirect_uri: 로그아웃 후 redirect할 uri. Keycloak에게 로그아웃 URL로 미리 등록해야한다
  • state: 로그아웃 및 리다이렉트 과정에서 클라이언트의 상태를 유지하고 표시하기 위함
  • ui_locales: 로그인 화면에 어떤 지역을 표시해야하는지 Keycloak에게 알린다

 

Keycloak은 로그인 요청을 수신하면 동일 세션의 클라이언트들에게 로그아웃을 지시한다. 그런 다음 세션을 만료시켜서 모든 토큰을 무시하는 형태로 작동한다.

 

 

2.2 OIDC 세션 관리 활용

OIDC 세션 관리를 이용하면 애플리케이션이 Keycloak에 요청을 보내지 않고도 세션의 로그아웃 여부를 확인할 수 있다. Keycloak이 관리하는 특수 세션 쿠키를 모니터링하는 방식인데, HTML iframe 태그의 숨김 속성을 이용해서 Keycloak의 특수 페이지를 로딩하는 방식이다.

 

다만 이 방식은 점점 더 활용도가 떨어지고 있다. 많은 브라우저들이 서드파티 콘텐츠에 대한 접근을 차단하기 시작하고 있기 때문이다.

 

2.3 OIDC 백-채널 로그아웃 활용

로그아웃 이벤트를 수신하기 위해서 애플리케이션이 엔드포인트를 등록하는 방법이다. Keycloak에서 로그아웃 수행 시, 엔드포인트로 등록된 모든 애플리케이션에 로그아웃 토큰을 전송한다. 애플리케이션은 토큰의 서명을 검증하고 세션 ID와 관련된 애플리케이션의 세션을 로그아웃한다.

 

분산된 인스턴스들이 존재하는 경우 상태 유지 애플리케이션이라면 세션들이 각 인스턴스에 분산되어 있다. 이를 로드밸런서 설정 등을 통해 특정 인스턴스에서 로그아웃 되게 하기는 어렵다. 이 경우 애플리케이션 수준에서 로그아웃이 되어야한다.

 

무상태 애플리케이션의 경우에는 일반적으로 세션이 쿠키에 저장되어서 로그아웃 요청을 처리하기가 어렵다. 이럴 경우 다음 요청이 전송될 때까지 로그아웃 요청을 저장하거나 해당 세션을 만료하는 방식으로 처리한다.

 

백-채널 로그아웃 방식이 그나마 가장 효과적인 방식이며, 짧은 애플리케이션 세션과 짧은 토큰 만료시간을 활용하는 것이 즉시 로그아웃에 좋다.

 

2.4 OIDC 프론트-채널 로그아웃 활용

OpenID 제공자의 로그아웃 페이지에 숨김 기능을 가진 iframe 태그를 렌더링한다. 브라우저에서 서드 파티 콘텐츠를 차단당하거나, 성공적으로 확인할 수 있는 효과적인 방법이 없기 때문에 로그아웃에 대한 신뢰도가 낮은 편이다. 

 

 

728x90
반응형