개요, 인가란 무엇인가?
들어가기 전에 인가(Authorization)이 무엇인지 한번 더 개념을 정리해보자.
인가는 "이 사용자가 특정 리소스나 기능에 접근할 권한이 있는지 확인하는 과정"이다.
예를들자면 어떤 사용자가 ID/PW를 입력하고 사진첩 어플리케이션에 로그인했다면,
사용자 본인의 사진첩에만 접근 가능해야하며 다른 사용자의 사진첩에는 접근해서는 안된다.
구분 | 개념 | 예시 |
인증 Authentication |
누구인지 확인하는 과정 | 로그인 |
인가 Authorization |
무엇을 할 수 있는지 확인하는 과정 | 내 사진첩에만 접근 |
즉, 인가란 사용자가 허용된 리소스에만 접근하도록 하는것이 인가의 개념이다.
인가를 위해 고려해야하는 요소는 정말 많다.
- 사용자 컨텍스트 (Who): 사용자 신원, 역할, 그룹, 속성, 조직 구조 내 위치
- 리소스 컨텍스트 (What): 리소스 유형, 소유권, 민감도 수준, 계층 구조, 메타데이터
- 접근 조건 (When/How): 시간/위치 기반 제약, 디바이스/네트워크 조건, 다중 인증, 접근 빈도
- 운영 관점 (Operation): 권한 위임/상속, 긴급 접근, 임시 권한, 권한 취소
- 감사 및 모니터링 (Audit): 접근 로그, 권한 변경 이력, 비정상 탐지, 컴플라이언스, 사용 패턴
- 정책 관리 (Policy): 버전 관리, 충돌 해결, 테스트, 배포 전략, 예외 처리
- 보안 고려사항: 최소 권한 원칙, 직무 분리, 권한 에스컬레이션 방지, 세션/토큰 관리
- 확장성 및 성능: 캐싱, 분산 시스템, 정책 처리, 응답 시간, 장애 복구
- 통합 고려사항: 레거시 통합, SSO, 외부 시스템 연동, API 보안, 프로토콜
- 규정 준수: 산업 표준, 데이터 보호, 감사 요구사항, 법규, 개인정보 보호
그렇다면, Keycloak에서는 인가를 어떻게 처리할것인가?
공식문서 Authorization Services Guide 에서는 주요 접근제어모델과 정책 집행 포인트에 대해 제공하고있고,
이전 실습에서도 아주 잠깐 다룬적이 있다. (👉실습)
접근제어모델 | 설명 |
Attribute-based access control (ABAC) |
사용자, 리소스, 환경의 속성을 기반으로 접근을 제어 예: 사용자의 부서, 직급, 위치 등의 속성을 기반으로 접근 권한 허용 |
Role-based access control (RBAC) |
사용자에게 할당된 역할을 기반으로 접근을 제어 예: Realm Role에 매핑된 사용자만 접근 권한 허용 |
User-based access control (UBAC) |
특정 사용자를 기반으로 접근을 제어 (개별 사용자 단위의 세밀한 권한 제어 가능) 예, user01에게만 접근 권한 허용 |
Context-based access control (CBAC) |
요청의 컨텍스트(시간, 위치, 디바이스 등)를 기반으로 접근을 제어 예: 특정 IP 범위에서만 접근 허용 |
Rule-based access control: | JavaScript를 사용한 커스텀 규칙 정의 (복잡한 비즈니스 로직을 구현할 수 있음) |
Time-based access control: | 시간 기반의 접근 제어 (특정 시간대나 기간 동안만 접근 허용) |
Custom ACMs through SPI: | Service Provider Interface를 통한 커스텀 접근 제어 메커니즘 구현 가능 (필요한 경우 자체 정책 타입을 개발하여 확장 가능) |
정책 집행 포인트 | 설명 |
Policy Information Point (PIP/정책 정보) |
정책 평가에 필요한 정보 제공 사용자 속성, 환경 정보 등 수집 |
Policy Administration Point (PAP/정책 관리) |
정책을 생성, 관리, 저장하는 곳 Keycloak 관리 콘솔에서 정책 설정 |
Policy Decision Point (PDP/정책 결정) |
접근 허용/거부 결정 정책과 속성 정보를 기반으로 판단 |
Policy Enforcement Point (PEP/정책 집행) |
실제 접근 제어 실행 PDP의 결정을 강제 |
1. PAP (정책 관리) 엔드포인트
# 기본 URL
${keycloak_url}/admin/realms/${realm_name}/clients/${client_id}/authz/resource-server
# 정책 관리
GET /policies # 정책 목록 조회
POST /policies # 정책 생성
GET /policies/{id} # 특정 정책 조회
PUT /policies/{id} # 정책 업데이트
DELETE /policies/{id} # 정책 삭제
2. PIP (정보 수집) 엔드포인트
# 사용자 정보
GET /admin/realms/${realm_name}/users/{id}
GET /admin/realms/${realm_name}/users/{id}/groups
GET /admin/realms/${realm_name}/users/{id}/role-mappings
# 리소스 정보
GET /authz/protection/resource_set
GET /authz/protection/resource_set/{id}
3. PDP (결정) 엔드포인트
# 권한 평가
POST /realms/${realm_name}/protocol/openid-connect/token
{
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"audience": "${client_id}",
"permission": "${resource}#${scope}"
}
# 정책 평가
POST /realms/${realm_name}/clients/${client_id}/authz/resource-server/policy/evaluate
4. PEP (실행) 엔드포인트
# 토큰 검증
GET /realms/${realm_name}/protocol/openid-connect/userinfo
GET /realms/${realm_name}/protocol/openid-connect/token/introspect
# 권한 확인
POST /realms/${realm_name}/clients/${client_id}/authz/resource-server/permission/ticket
오늘은 KeyCloak에서의 접근제어 메커니즘 중 RBAC, ABAC에 대해 확인해보자.
RBAC (Role-Based Access Control)
개념 이해하기
Keycloak에서는 Role은 사용자 혹은 그룹에 매핑되어 사용된다. (공식문서 링크)
Role이란, 일반적으로 조직 또는 애플리케이션 컨텍스트에서 사용자가 가지는 역할을 말한다.
(e.g. administrator, system-manager, people-manager, audit-user, readonly-user ..)
Keycloak의 RBAC을 이해하기 위해서는 user, group의 개념도 함께 이해해야 한다.
📎 User
- Keycloak에서 인증과 인가의 주체이다.
- 직접적으로 Role을 할당받을 수 있으며(UBAC), Group의 멤버가 될 수 있다.
- e.g. 사용자1(user01@test.com), 사용자2(user02@test.com)
📎 Group
- 여러 User를 묶어서 관리하는 단위이며, Role을 할당받을 수 있다. (GBAC)
- 계층적 구조가 가능(하위 그룹 생성 가능)하여 여러 User를 한번에 관리 가능하다.
- Role 관리 효율성 증가된다.
📎 Role
keycloak에는 세 종류의 역할이 있다.
구분 | 설명 |
Realm Role [링크] |
- 전역적(Global) 범위의 역할 - Realm 내의 모든 클라이언트 애플리케이션에서 공통으로 사용 - 일반적으로 조직 전체에 적용되는 광범위한 권한을 정의 ⚠️주의⚠️ 전역적으로 사용 가능하지만, 자동으로 모든 사용자에게 할당되지는 않음 |
Client Role [링크] |
- 특정 클라이언트 애플리케이션에 한정된 역할 - 해당 애플리케이션에 특화된 세부적인 권한을 정의 - 다른 클라이언트와 독립적으로 관리 |
Default Role [링크] |
- 새로운 사용자가 생성될 때 자동으로 할당되는 역할 - `default-roles-{realm-name}` Composite Role을 통해 관리 - Realm Role이나 Client Role을 Default Role로 설정 가능 |
Role들은 상호 베타적인 것이 아니며, 조합하여 계층적이고 세밀한 접근 제어가 가능해진다.
아래 시나리오에서 각 Role들을 함께 사용하는 예시를 확인해보자.
[Realm Role & Client Role 혼용 시나리오]
Realm Role (USER)
- 시스템 전반의 기본 접근 권한
- 모든 클라이언트 앱에서 유효
- "이 사용자가 앨범 서비스를 사용할 자격이 있는가?"
Client Role ({사용자ID}:all_access)
- 사용자 본인의 앨범에 대한 모든 권한
- 조회/수정/삭제 등 모든 기능 사용 가능
- "이 사용자가 본인의 앨범에 대한 모든 권한을 가지는가?"
이런식으로 사용자가 사진앱을 사용할수는 있지만, 모든 사진첩이 아닌 본인의 사진첩만 접근하여 수정할 수 있도록
두 권한을 조합하여 계층적으로 사용할 수 있다.
📎 정리
- Role은 단독으로 사용할 수 없고, 반드시 User혹은 Group에 매핑되어야 한다.
- Keycloak에서는 RBAC, UBAC, GBAC 별개의 다른 개념이 아니라 같은 접근제어 메커니즘을 가지고 있다고 할 수 있다.
- 모든 종류의 Role은 Group이나 User에게 직접 할당 가능하다.
- Group에 할당된 Role은 해당 Group의 모든 멤버가 상속받는다.
- 한 사용자는 여러 경로(Default, Group, 직접 할당)로 Role을 가질 수 있다.
실습) Role 사용 실습하기
RealmRole, User에 할당
Realm Role은 아래와 같이 설정하면 되며,
Client Role은 [Clients > 생성한 Client > Roles 탭 > Create role] 에서 생성할 수 있다.
이 실습에서는 Realm Role만 간략히 설명한다.
1. Realm Role을 생성한다.
2. 사용자에게 Realm Role을 할당한다.
3. 사용자에 연결된 Realm Role 확인
먼저 Users에 Realm Role이 잘 연결되었는지 점검한다.
터미널에서 호출할 예정이므로, Direct access grants를 허용해준다.
이후 아래 환경변수 부분을 내 환경에 맞춰 변경하고, 터미널에서 실행해보자.
# 1. keycloak 테스트서버 실행
docker run -p 8080:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak \
start-dev
# 2. 환경변수 설정
export ADMIN_USERNAME="admin"
export ADMIN_PASSWORD="admin"
export CLIENT_ID="admin-cli"
export REALM_NAME="my-realm"
export KEYCLOAK_URL="http://localhost:8080"
export KEYCLOAK_USERNAME=user01
# 3. 관리자 토큰 획득
export ADMIN_TOKEN=$(curl -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=${CLIENT_ID}" \
-d "username=${ADMIN_USERNAME}" \
-d "password=${ADMIN_PASSWORD}" \
-d "grant_type=password" | jq -r '.access_token')
# 4. 사용자 ID 조회
export USER_ID=$(curl -X GET "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users?username=${KEYCLOAK_USERNAME}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.[0].id')
# 5. Role 매핑 확인 (관리자 토큰 사용)
curl -X GET "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/realm" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq '.' --color-output
결과로 아래와 같이 새로 생성한 my-realm-role이 매핑된것을 확인할 수 있다.
ClientRole, Group에 할당
1. Client Role 생성
[client > 'my-client' > Roles > create role] 으로 새로운 Role (my-client-role)을 생성한다.
2. 그룹에 ClientRole을 매핑한다.
3. 그룹에 ClientRole 매핑된것 확인
# 1. 환경변수 설정
export ADMIN_USERNAME="admin"
export ADMIN_PASSWORD="admin"
export CLIENT_ID="my-client"
export REALM_NAME="my-realm"
export KEYCLOAK_URL="http://localhost:8080"
export KEYCLOAK_USERNAME="user01"
export KEYCLOAK_GROUPNAME="my-group"
# 2. 관리자 토큰 획득
export ADMIN_TOKEN=$(curl -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=admin-cli" \
-d "username=${ADMIN_USERNAME}" \
-d "password=${ADMIN_PASSWORD}" \
-d "grant_type=password" | jq -r '.access_token')
# 3. 사용자 ID 조회
export USER_ID=$(curl -X GET "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users?username=${KEYCLOAK_USERNAME}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.[0].id')
# 4. Client ID(UUID) 조회
export MY_CLIENT_ID=$(curl -X GET "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients?clientId=${CLIENT_ID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.[0].id')
# 5. 사용자의 Client Role 매핑 확인 (직접 할당된 role)
echo "Direct Client Roles:"
curl -X GET "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/clients/${MY_CLIENT_ID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq '.' --color-output
# 6. 사용자의 Effective Client Role 매핑 확인 (그룹에서 상속받은 role 포함)
echo -e "\nEffective Client Roles (including inherited from groups):"
curl -X GET "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/clients/${MY_CLIENT_ID}/composite" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq '.' --color-output
# 7. 그룹 ID 조회 및 Client Role 매핑 확인
export MY_GROUP_ID=$(curl -X GET "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups?search=${KEYCLOAK_GROUPNAME}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.[0].id')
echo -e "\nGroup Client Roles:"
curl -X GET "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups/${MY_GROUP_ID}/role-mappings/clients/${MY_CLIENT_ID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq '.' --color-output
그룹에 연결된 ClientRole, 그리고 사용자에게 상속된 ClientRole도 위 명령어를 통해 확인할 수 있다.
ABAC (Attribute-Based Access Control)
개념 이해하기
Keycloak에서 ABAC(Attribute-Based Access Control)은 사용자, 리소스, 환경의 속성(Attribute)을 기반으로 접근을 제어하는 방식이며, Role기반의 접근제어보다 더 유연하고 세밀한 접근제어가 가능하다.
Attribute도 마찬가지로 기본적으로는 User와 Group에 할당하여 사용하며,
그 외에도 Organization, Client, Identity Provider 등 다양한 Keycloak 컴포넌트에 Attribute를 추가할 수 있다.
(core concepts and terms)
ABAC을 확인하기 전에 먼저 주요 개념부터 확인해보자.
📎 Attribute
- Keycloak에서 엔티티(User, Group 등)의 추가 정보를 저장하는 Key-Value 형태의 데이터를 말한다.
- 동적으로 추가/수정이 가능하며, 하나의 Key에 여러 Value를 가질 수 있으며,
ABAC(Attribute Based Access Control)에서 접근 제어 결정에 사용된다. - Group에서 User로 상속 가능 하다. (Group → User)
- e.g. department: "HR", location: ["Seoul", "Busan"]
📎 Protocol Mapper
- Attribute를 Token Claim으로 변환하는 설정이다.
- Keycloak의 JavaDoc 인터페이스 구현체 링크
- Mapper 종류:
- User Property Mapper: 기본 사용자 속성 매핑
- User Attribute Mapper: 커스텀 사용자 속성 매핑
- Group Membership Mapper: 그룹 정보 매핑
- Role Mapper: 역할 정보 매핑
{
"name": "department", // Mapper의 이름 (관리자가 구분하기 위한 용도)
"protocol": "openid-connect", // 사용할 프로토콜 (보통 OpenID Connect 사용)
"protocolMapper": "oidc-usermodel-attribute-mapper",// Mapper의 타입 (User Attribute를 매핑)
"config": { // 실제 매핑 설정
"user.attribute": "department", // Keycloak에 저장된 속성 이름
"claim.name": "department", // 토큰에 포함될 때 사용할 이름
"token.claim": "true" // 토큰에 포함할지 여부
}
}
📎 Token Claim
- 토큰에 포함되는 정보의 한 조각으로, 사용자나 토큰 자체에 대한 statement를 말한다.
- Protocol Mapper를 통해 Attribute를 Claim으로 변환하여 토큰에 포함시킬 수 있다.
- 클라이언트 애플리케이션에서 사용자 정보나 권한 확인에 사용한다.
{
// 기본 Claims
"sub": "user123",
"iss": "http://keycloak.example.com",
"exp": 1516239022,
// User Attributes -> Claims
"user_attributes": {
"department": "HR",
"location": ["Seoul"]
},
// Group Attributes -> Claims
"group_attributes": {
"costCenter": "HR001"
}
}
📎 Attribute와 Token Claim 관계
사용자나 그룹의 속성(attributes)은 protocol mapper설정을 통해 Token에 Claims 형태로 담겨 전달된다.
즉,Attribute는 Keycloak 내부 데이터이고, Claim은 이 데이터를 토큰을 통해 외부로 전달하는 방식이다.
실습) Attribute 사용방법
User Attribute 사용하기
먼저 사용자 속성(Attributes)를 설정해보자.
1. [realm settings > User profile > create attribute]를 클릭하고 department 속성을 만든다.
2. User Details에 새로 생긴 Department에 값을 입력한다.
3. 사용자에 Attribute가 제대로 설정 되었는지 확인한다.
# 1. 환경변수 설정
export ADMIN_USERNAME="admin"
export ADMIN_PASSWORD="admin"
export CLIENT_ID="my-client"
export REALM_NAME="my-realm"
export KEYCLOAK_URL="http://localhost:8080"
export USER_NAME="user01"
# 2. 관리자 토큰 획득
export ADMIN_TOKEN=$(curl -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=admin-cli" \
-d "username=${ADMIN_USERNAME}" \
-d "password=${ADMIN_PASSWORD}" \
-d "grant_type=password" | jq -r '.access_token')
# 3. 사용자 ID 조회
export USER_ID=$(curl -X GET "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users?username=${USER_NAME}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.[0].id')
echo $USER_ID
# 4. Client ID(UUID) 조회
export CLIENT_UUID=$(curl -X GET "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients?clientId=${CLIENT_ID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.[0].id')
echo $CLIENT_UUID
# 5. 현재 사용자 정보 조회
export USER_INFO=$(curl -X GET "${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json")
echo $USER_INFO | jq
# 6. User Attribute 조회
echo "Current User Attributes:"
echo ${USER_INFO} | jq '.attributes' --color-output
user attribute에 department가 추가된것을 볼 수 있다.
4. Protocol Mapper를 설정한다.
5. 이제 AccessToken을 발급받고 Attribute가 포함되었는지 확인한다.
# 1. 환경변수 설정
export USER_NAME="user01"
export USER_PASSWORD="user01"
export CLIENT_ID="my-client"
export REALM_NAME="my-realm"
export KEYCLOAK_URL="http://localhost:8080"
# 1. 사용자 토큰 발급
export USER_TOKEN=$(curl -X POST "${KEYCLOAK_URL}/realms/${REALM_NAME}/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=${CLIENT_ID}" \
-d "username=${USER_NAME}" \
-d "password=${USER_PASSWORD}" \
-d "grant_type=password")
# 2. Access Token 추출
export ACCESS_TOKEN=$(echo $USER_TOKEN | jq -r '.access_token')
# 3. Access Token 디코딩 (JWT)
echo $ACCESS_TOKEN | cut -d"." -f2 | tr -d '-' | tr '_' '/' | sed -e 's/$/\=/' | base64 -d | jq '.' --color-output
protocol mapper까지 설정하고 나면 AccessToken에 Department가 추가되어 전달된다.
RBAC vs ABAC
실제로는 하나만 사용하는것이 아니라
기본적인 권한은 RBAC으로 관리하고, 특수한 상황은 ABAC으로 보완하는 형태로 두 방식을 혼합하여 사용하는 경우가 많다.
구분 | RBAC | ABAC |
설명 | Role기반의 접근제어 {
"user": "user01",
"roles": ["admin"],
"groups": ["/hr/manager"],
"permissions": ["read", "write"]
}
|
속성 기반의 접근제어 {
"user": {
"department": "HR",
"level": "senior",
"location": "Seoul",
"clearance": "top-secret"
},
"resource": {
"type": "document",
"classification": "confidential",
"department": "HR"
},
"action": "read",
"environment": {
"time": "09:00-18:00",
"ip_range": "10.0.0.0/24"
}
}
|
특징 | 미리 정의된 역할/그룹에 기반 관리가 단순 변경이 적은 환경에 적합 확장성 제한적 |
다양한 속성 기반 결정 유연한 정책 설정 가능 상황에 따른 동적 접근 제어 복잡한 규칙 지원 |
예시 | @RolesAllowed("admin") public void adminMethod() { // 관리자만 접근 가능 } |
@Attribute("department == 'HR' && time.between('09:00', '18:00')") public void hrDocumentAccess() { // HR부서 직원이 업무시간 내 접근 } |
시나리오 | 조직 구조가 명확한 기업 시스템 권한 체계가 단순한 웹 애플리케이션 사용자 그룹이 명확한 시스템 |
복잡한 보안 요구사항이 있는 시스템 시간/위치 기반 접근 제어 필요 다양한 조건에 따른 동적 권한 필요 |
📎 주의사항
Role Explosion (역할 폭발) 문제를 주의해야한다. 역할은 세분화된 인가를 위한 역할 사용하는것을 지양하고, 조직 구조/직무를 반영하도록 하는것이 좋다. (ADMIN, READ_USER 등)
- 역할(Role)은 큰 범주로 정의한다. (admin, superuser, readonlyuser 혹은 department 등)
- 세부 접근제어는 속성(attributes)를 사용한다.
{
"accessPolicy": {
"role": "MANAGER",
"requiredAttributes": {
"department": ["HR", "IT"],
"location": "Seoul",
"timeWindow": "businessHours"
}
}
}
Role Explosion (역할 폭발) 문제란,
세부적인 부분까지 전부 Role로 정의하여 Role이 엄청 많아지는것