🌱 Infra/KeyCloak

[keycloak 맛보기 #6] Keycloak의 인가 전략

mini_world 2025. 2. 9. 14:47
목차 접기

개요, 인가란 무엇인가?

 

들어가기 전에 인가(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도 마찬가지로 기본적으로는 UserGroup에 할당하여 사용하며, 
그 외에도 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이 엄청 많아지는것

728x90