Role, group, mapper và nội dung token
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
order-servicecần biếtcustomer_id(id trong DB shop) để lọc đơn của khách, không muốn tra bằng email.- Nhân viên support cần quyền
order:read,order:refund; quản lý thêm quyền khác. Có 50 nhân viên. payment-servicephải từ chối token không dành cho nó.
2. Vấn đề
- Gán role từng user một: 50 người × 5 role, dễ sót khi người chuyển bộ phận.
- Mặc định token chứa mọi role của user ở mọi client: token phình, lộ thông tin thừa.
- Không có claim
audđúng: token choshop-webdùng được ở mọi service.
3. Giải pháp
| Công cụ | Dùng để |
|---|---|
| Client role | Quyền chi tiết theo service: order:read, order:refund |
| Composite role | Role gộp nhiều role: support = order:read + order:refund |
| Group | Gán role cho nhóm; user vào nhóm là có role. Group có cây con kế thừa role. |
| Protocol mapper | Thêm claim vào token: từ user attribute, group, giá trị cố định |
| Audience mapper | Thêm client vào claim aud |
| Full scope allowed = Off + client scope | Chỉ 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ình | Mặc định | Gợi ý shop-app |
|---|---|---|
| Access Token Lifespan | 5 phút | 5 phút (giữ ngắn) |
| SSO Session Idle | 30 phút | 30 phút |
| SSO Session Max | 10 giờ | 10–12 giờ |
| Client override | Có 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
- Nên: gán role qua group; claim nghiệp vụ nhỏ, ổn định (id) qua mapper; audience cho từng service.
- Không nên: nhét danh sách đơn hàng, quyền theo từng record, hay dữ liệu hay đổi vào token. Quyền theo dữ liệu (đơn này của ai) kiểm tra ở service.
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:
- Full scope allowed: Off cho mọi client, chỉ thêm role client đó dùng.
- Bỏ mapper Group Membership nếu không ai đọc claim
groups; group dùng để gán role, không cần trong token. - Dùng composite role thay vì hàng chục role nhỏ, nếu service chỉ cần quyền gộp.
- Bỏ claim profile thừa khỏi access token (để ở ID token hoặc userinfo).
- Gỡ client scope
roleskhỏ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.
- Chấp nhận độ trễ = access token lifespan; đó là lý do giữ nó ngắn.
- Thao tác nhạy cảm (refund lớn): service gọi token introspection hoặc kiểm tra thêm ở DB.
- Muốn cắt ngay: logout session của bob (bài 12.5).
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 để
audchứ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
- Client role → composite → group → user.
- Claim mới = user attribute + mapper trong client scope.
- Audience mapper cho
aud; Full scope allowed Off cho token gọn. - Đổi role có hiệu lực ở token sau.
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.