Role, group, mapper và nội dung token

4 câu hỏi~25 phútTừ khóa: composite role, group, protocol mapper, user attribute, audience mapper, full scope allowed, token lifespan

Tiếp đi: phân quyền trong Keycloak và token chứa gì.

Bài này lo phần đưa đúng thông tin vào token. Phần Spring đọc claim rồi map thành authority đã có ở 4.4.8.

1. Bài toán

2. Vấn đề

3. Giải pháp

Công cụDùng để
Client roleQuyền chi tiết theo service: order:read, order:refund
Composite roleRole gộp nhiều role: support = order:read + order:refund
GroupGán role cho nhóm; user vào nhóm là có role. Group có cây con kế thừa role.
Protocol mapperThêm claim vào token: từ user attribute, group, giá trị cố định
Audience mapperThêm client vào claim aud
Full scope allowed = Off + client scopeChỉ cho role liên quan vào token

4. Code từng bước

Bước 1: Role và group

Clients → order-service → Roles: order:read, order:refund
Realm roles → support (Composite: order-service order:read, order:refund)
Groups → /staff/support → Role mapping: support
Users → bob → Groups: join /staff/support

Bước 2: User attribute + mapper customer_id

Realm settings → User profile → Create attribute: customer_id
   (chỉ admin được sửa, user không tự sửa được)
Client scopes → Create "shop-customer" (type Default)
   → Mappers → By configuration → User Attribute
       User attribute: customer_id
       Token claim name: customer_id
       Claim JSON type: long
       Add to access token: On, Add to ID token: Off
Clients → shop-web → Client scopes → Add "shop-customer" (Default)

Bước 3: Audience

Clients → shop-web → Client scopes → shop-web-dedicated → Add mapper → Audience
   Included Client Audience: order-service
   Add to access token: On

Và giữ token gọn: shop-web → Dedicated scope → Scope tab → Full scope allowed: Off, chỉ thêm customer và role của order-service.

Bước 4: Xem token

# Admin console: Clients → shop-web → Client scopes → Evaluate → chọn user → Generated access token
# Hoặc decode token lấy được:
echo "$ACCESS_TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq
{
  "iss": "http://localhost:8080/realms/shop",
  "aud": ["order-service"],
  "azp": "shop-web",
  "sub": "8f1c...",
  "customer_id": 1042,
  "realm_access": { "roles": ["customer"] },
  "scope": "openid profile email",
  "exp": 1760430000
}

Phía Java chỉ cần đọc claim:

@GetMapping("/orders")
List<OrderDto> myOrders(@AuthenticationPrincipal Jwt jwt) {
    long customerId = ((Number) jwt.getClaim("customer_id")).longValue();
    return orderService.findByCustomer(customerId);
}

5. Cách hoạt động

user bob
  ├─ group /staff/support ── role support (composite)
  │                             ├─ order-service: order:read
  │                             └─ order-service: order:refund
  └─ attribute customer_id
         |
   Keycloak phát token cho client X:
   1. Gom role hiệu lực (trực tiếp + group + composite mở rộng)
   2. Lọc theo scope của client X (Full scope allowed? client scope role mappings)
   3. Chạy mapper của các client scope được yêu cầu → thêm claim
   4. Ký JWT bằng key realm

6. So sánh: token lifetime (Realm settings → Tokens / Sessions)

Cấu hìnhMặc địnhGợi ý shop-app
Access Token Lifespan5 phút5 phút (giữ ngắn)
SSO Session Idle30 phút30 phút
SSO Session Max10 giờ10–12 giờ
Client overrideCó thể đặt riêng cho từng client (Advanced)

Session và refresh token chi tiết ở bài 12.5.

7. Khi nào nên dùng / không nên dùng

Admin của mình thuộc 30 group, token dài tới 12 KB, Nginx trả lỗi 400 "Request Header Too Large". Xử lý sao cho đúng, không phải tăng buffer?

Tăng buffer chỉ là chữa cháy. Cách đúng là bớt thứ không cần:

  1. Full scope allowed: Off cho mọi client, chỉ thêm role client đó dùng.
  2. Bỏ mapper Group Membership nếu không ai đọc claim groups; group dùng để gán role, không cần trong token.
  3. Dùng composite role thay vì hàng chục role nhỏ, nếu service chỉ cần quyền gộp.
  4. Bỏ claim profile thừa khỏi access token (để ở ID token hoặc userinfo).
  5. Gỡ client scope roles khỏi client không cần role (ví dụ client chỉ cần đăng nhập).

Thông tin lớn hoặc hay đổi thì service tự tra (cache) theo sub.

Mình gỡ role support của bob, nhưng bob vẫn refund được thêm vài phút. Có phải Keycloak bị lỗi?

Không lỗi. Role được "chụp" vào access token lúc phát. Service verify JWT offline, không hỏi lại Keycloak, nên token cũ còn hiệu lực tới exp (5 phút). Lần refresh tiếp theo token mới sẽ không còn role.

Phỏng vấn hỏi thiết kế role, group và claim trong Keycloak thì trả lời sao?

Q1. Bạn gán quyền cho user trong Keycloak thế nào?

🇻🇳 Tiếng Việt:

Quyền chi tiết là client role của từng service, gộp thành composite role theo nghiệp vụ, gán composite role cho group, rồi đưa user vào group. Đổi bộ phận chỉ cần đổi group.

🇬🇧 English:

Fine-grained permissions are client roles per service, grouped into composite roles per job function, assigned to groups, and users join groups. When someone changes team we only change their group.

Q2. Thêm claim tùy biến như customer_id vào token thế nào?

🇻🇳 Tiếng Việt:

Khai báo user attribute trong user profile, tạo client scope có User Attribute mapper, bật add to access token, gán scope cho client. Thêm audience mapper để aud chứa service đích.

🇬🇧 English:

Declare a user attribute in the user profile, create a client scope with a User Attribute mapper that adds it to the access token, and assign the scope to the client. An audience mapper puts the target service into aud.

Q3. Làm sao giữ token nhỏ?

🇻🇳 Tiếng Việt:

Tắt Full scope allowed, chỉ cho role liên quan vào token, bỏ mapper group và claim profile không dùng, không để dữ liệu hay đổi trong token.

🇬🇧 English:

Turn off full scope allowed so only relevant roles are included, remove unused group and profile mappers, and keep frequently changing data out of the token.

Cách nhớ ngắn gọn


Tự kiểm tra

Composite role là gì?

Role chứa role khác; gán composite là user có tất cả role con.

Xem trước token của một user cho một client ở đâu?

Clients → client → Client scopes → Evaluate.

Full scope allowed bật thì sao?

Token chứa mọi role của user ở mọi client, phình to và lộ quyền thừa.

Vì sao gỡ role không có hiệu lực ngay?

Access token đã phát vẫn hợp lệ tới exp; service verify offline.