참고자료
* 책: https://www.yes24.com/Product/Goods/122459785
실습 준비) Keycloak & 테스트 애플리케이션 세팅
Keycloak을 로컬에서 먼저 실행한다.
docker run -p 8080:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak \
start-dev
이후 테스트 코드 어플리케이션을 실행한다.
git clone https://github.com/PacktPublishing/Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition.git
cd Keycloak---Identity-and-Access-Management-for-Modern-Applications-2nd-Edition/ch4
npm install
npm start
실습 #1) OIDC Discovery Endpoint 확인
*[참고]OpenID Connect로 보안 애플리케이션 및 서비스
실습해보기
테스트 페이지 (http://localhost:8000)에 접속한다.
'1 - Discovery' 클릭 및 올바른 Issuer를 입력 후 Load OpenID Provider Configuration을 클릭한다.
그럼 아래 캡쳐사진처럼 메타데이터가 출력된다.
이 메타데이터들은 Well-Known 설정 엔드포인트 (검색(discovery)엔드포인트)이며,
OpenID Provider(OP)의 설정 정보를 자동으로 찾을 수 있게 해주는 표준화된 엔드포인트이다.
주요 메타데이터 의미
아래 표는 주요 메타데이터에 대한 의미를 설명하고있다.
구분 | 설명 |
issuer | OpenID Provider의 식별자 URL |
authorization_endpoint | OpenID Provider의 식별자 URL (인증요청) |
token_endpoint | 토큰을 발급받는 URL (토큰요청) |
introspection_endpoint | 액세스 토큰이나 리프레시 토큰의 유효성을 확인하는 URL (점검요청) |
userinfo_endpoint | 사용자 정보를 가져오는 URL (UserInfo요청) |
end_session_endpoint | 로그아웃을 처리하는 URL |
jwks_uri | 공개 키를 제공하는 URL |
grant_types_supported | 지원하는 인증 방식(예: authorization_code, client_credentials 등) |
response_types_supported | 클라이언트가 요청 시 사용할 수 있는 응답 형식(예: code, token, id_token 등) |
OIDC discovery endpoint 표준 spec
OpenID Connect Discovery 1.0 스펙은 OpenID Foundation에서 관리하고 있다.
- 공식 스펙 문서: OpenID Connect Discovery 1.0
- 기타 관련 링크들:
Discovery endpoint의 표준 경로는 아래와 같다.
http://{keycloak domain}/realms/{realm-name}/.well-known/openid-configuration
#http://localhost:8080/realms/myrealm/.well-known/openid-configuration
실습 #2) Authorization Code(인가코드) 발급과정
Authentication 필드 확인
먼저 keycloak에서는 oidc-playground 라는 클라이언트를 생성한다. (realm -> clients -> client생성)
또한 사용자도 생성해야한다.
테스트 유저는 임의 ID로 생성한 후 (여기에서는 user01로 생성했다), Credentials탭에서 비밀번호를 설정한다.
이후 테스트 페이지 (http://localhost:8000)에서 2 - Authentication를 클릭한다.
올바른 값을 입력(아래 참고)하고 Generate Authentication Request를 클릭하여 인증요청이 어떻게 생성되는지 확인한다.
첫번째 회색박스 Authentication에서의 각 필드는 스크린샷 내용 혹은 아래 내용을 참고한다.
- client_id: keycloak에서 생성한 client id
- scope:
- 클라이언트가 사용자의 어떤 정보나 리소스에 접근하고 싶은지 나타내는 권한 범위
- openid는 기본값이며, 그 외에도 profile, email, phone, address 등 사용 가능
- prompt: 사용자 인증 시 특정 동작을 강제하는 파라미터
- none: 이미 로그인 된 경우 사용자 상호작용 없이 진행
- login: 강제로 다시 로그인하도록 함
- consent: 권한 등의 화면을 강제로 보여줌
- select_account: 계정 선택 화면을 보여줌
- max_age: 로그인 세션의 최대 허용 시간(초)
- login_hint: 로그인 페이지에서 사용자 ID를 미리 채워넣을 때 사용
- 예를들어 user01을 입력해두면, 사용자 로그인시 미리 user01이 채워져있다.
- 사용자 편의성을 위해 사용한다.
Authentication Request 확인
이번 단계에서는 실제 Authentication Request가 어떻게 생겼는지(?) 확인할 수 있다.
Authentication Response 확인
위 요청이 처리되면 Authentication Response를 받게 된다.
응답으로 받은 코드는 ID토큰과 리프레시 토큰을 얻기위해 사용하는 인가코드(Authorization Code)이다.
💡 여기서 궁금한부분 -인증코드라고 하지 않고 인가코드라고 썼을까? |
인증과 인가는 사전적 정의로 보자면 아래와 같다. - 인증(Authentication): "이 사람이 user01이 맞다" - 인가(Authorization): "user01은 이 파일을 읽을 수 있다" OIDC는 OAuth 2.0위에 구축되어있어 사용자를 인증하면서 어플리케이션에 대한 권한도 발급받게 된다. 사용자인증->권한부여 과정이 한번에 연속적으로 일어나며, 이번 단계에서는 인증 흐름 중 인가코드를 발급받는 부분이다. ┌───┐ ┌───┐ ┌───┐ ┌────┐ ┌────┐
│ 사용자 │ -> │ 인증 │ -> │ 인가 │ -> │ Token │ -> │ Token │
│ 로그인 │ │ 성공 │ │ 코드 │ │ 요청 │ │ 발급 │
└───┘ └───┘ └───┘ └────┘ └──┬─┘
(지금여기) ↓
┌───────┐
│ ID Token │
│ Access Token │
│ Refresh Token │
└───────┘
인가코드(Authorization code)는 중간교환수단으로, 실제 리소스 접근권한은 ID Token이 가지고 있고, OIDC 사용자 인증(Authentication) 과정은 Authorization Code를 통해 ID Token을 발급받음으로써 완성된다. |
실습 #3) ID Token 발급과정
이제 실습#2에서 발급받은 Authorization Code를 가지고 ID Token을 발급받아보자.
실습해보기
테스트 페이지 (http://localhost:8000)에서 3 - Token를 클릭한다.
실습 #2에서 발급받은 인가(Authorization)코드를 가지고 ID Token & Refresh Token, Access Token 등을 발급받았다.
Token Response 필드 알아가기
구분 | 설명 |
access_token | - 보호된 리소스에 접근하기 위한 토큰 - JWT 형식으로 인코딩된 문자열 - 리소스 서버에 접근할 때 사용 |
expires_in | - 액세스 토큰의 유효 시간 (초) |
refresh_expires_in | - 리프레시 토큰의 유효 시간 (초) |
refresh_token | - 액세스 토큰이 만료되었을 때 새로운 토큰을 발급받기 위한 토큰 - 사용자가 다시 로그인하지 않아도 됨 |
token_type | - 토큰의 타입을 나타냄 - Bearer는 토큰 소지자에게 권한을 부여 |
id_token | - 사용자의 신원 정보를 포함한 JWT - OIDC에서 추가된 토큰 - 사용자 인증 정보 포함 |
not-before-policy | - 토큰이 유효해지는 시작 시간 - 0은 즉시 유효함을 의미 |
session_state | - 현재 세션의 고유 식별자 |
scope | - 토큰이 가진 권한 범위 - "openid profile email": 기본 인증, 프로필 정보, 이메일 정보 접근 가능 |
id_token, access_token, refresh_token 이해하기
제일 중요한건 아래 세 가지 토큰이다.
이 세 토큰 모두 JWT형식이다.
[클라이언트] [인증 서버] [리소스 서버]
│ │ │
│ ┌──────────┐ │ │
├─>│Access │────────────────────────>│ API 접근
│ │Token │ │ │
│ └──────────┘ │ │
│ │ │
│ ┌──────────┐ │ │
├─>│Refresh │───>│ 새 토큰 발급 │
│ │Token │ │ │
│ └──────────┘ │ │
│ │ │
│ ┌──────────┐ │ │
└─>│ID Token │───>│ 사용자 인증 │
└──────────┘ │ │
- access_token: API 접근용 토큰 (API 호출 시 권한 증명)
- 권한범위(Scope) / 리소스 접근 권한 / 만료시간 / Client ID - refresh_token: 만료된 Access Token 갱신용 토큰
- 토큰 갱신용으로 최소한의 정보만 담음(보안정보) - id_token: 사용자 인증 정보용 토큰 (사용자 신원 정보 확인)
- 사용자정보 / 인증시점 / 인증방법 / 토큰발급자
Access Token이 만료되면 가지고있던 Refresh Token으로 인증서버에 요청하게 되며,
이때 Access Token 뿐만아니라 모든 Token이 갱신된다.
[클라이언트] [인증 서버]
│ │
│ Refresh Token │
│─────────────────> │
│ │
│ │
│ 새로운 토큰 세트 │
│ <─────────────────│
│ - Access Token │
│ - ID Token │
│ - Refresh Token │
실습 #4) Token Refresh
이제 실습#3에서 발급받은 refresh token 를 가지고 토큰을 모두 재발급 해보자!
실습해보기
테스트 페이지 (http://localhost:8000)에서 4 - Refresh를 클릭한다.
Refresh Request 필드에는 RefreshToken이 포함되며,
모든 Token은 재사용되지 않고, Refresh요청이 오면 모두 재발급 된다.
실습 #5) UserInfo 요청하기
Access Token으로 UserInfo를 요청해보자.
실습해보기
테스트 페이지 (http://localhost:8000)에서 5 - UserInfo를 클릭한다.
UserInfo Endpoint 설명
UserInfo Endpoint는 OIDC의 중요한 기능 중 하나이며 주요 특징은 아래와 같다.
- ID Token에 있는 정보를 보완
- 항상 최신 사용자 정보 제공
- scope 설정에 따라 반환되는 정보가 달라짐
- Access Token의 권한 범위 확인
UserInfo는 AccessToken을 사용해서 요청하며 아래와 같은 방식으로 동작한다.
#1. UserInfo Request
┌──────────────┐ ┌──────────────┐
│ 클라이언트 │ │ UserInfo │
│ │─── GET ───────>│ Endpoint │
│ │ │ │
│ │ Authorization: │ │
│ │ Bearer [Access │ │
│ │ Token] │ │
└──────────────┘ └──────────────┘
#2. UserInfo Response
┌──────────────┐ ┌──────────────┐
│ 클라이언트 │ │ UserInfo │
│ │<── JSON ───────│ Endpoint │
│ │ │ │
│ { │ │
│ "sub": "...", │ │
│ "name": "user234 test", │ │
│ "email": "user01@..." │ │
│ } │ │
└──────────────┘ └──────────────┘