Chuyển đến nội dung chính

Bài 9: Client Policies và Advanced Client Configuration

Client Policies architecture (Profiles, Conditions, Executors), FAPI 2.0 Security Profile, Client Secret Rotation, Service Accounts, Audience Support, Confidential Client Credentials (Client ID/Secret, Signed JWT, X.509), Standard Token Exchange, JWT Authorization Grant (RFC 7523), và cấu hình cho MCP Servers.

🔒 DevSecOps — Bài 9 Bài 9: Client Policies và Advanced Client Configuration

Keycloak từ Cơ bản đến Nâng cao

Phần 2: SSO Protocols - OpenID Connect và SAML

xdev.asia

1. Client Policies

Client Policies là framework cho phép enforce security requirements lên clients một cách tự động. Thay vì phải kiểm tra thủ công cấu hình từng client, bạn định nghĩa policies để Keycloak tự động validate và enforce.

1.1 Tại sao cần Client Policies?

  • Consistency: Đảm bảo tất cả clients tuân thủ cùng security standards

  • Automation: Tự động reject requests không tuân thủ

  • Compliance: Enforce industry standards (FAPI, PSD2, Open Banking)

  • Governance: Kiểm soát client registration và configuration

1.2 Architecture: Profiles, Conditions, Executors

Client Policies gồm 3 thành phần chính:

┌─────────────────────────────────────────────────┐
│                  Client Policy                   │
│                                                   │
│  ┌──────────────┐     ┌──────────────────────┐   │
│  │  Conditions   │     │      Profiles        │   │
│  │ (Khi nào?)    │────>│   (Áp dụng gì?)      │   │
│  │               │     │                      │   │
│  │ • Client Role │     │  ┌────────────────┐  │   │
│  │ • Client Scope│     │  │   Executors    │  │   │
│  │ • Any Client  │     │  │ (Làm gì?)      │  │   │
│  │ • Client      │     │  │                │  │   │
│  │   Access Type │     │  │ • PKCE Enforcer│  │   │
│  │ • Client      │     │  │ • Secure Alg   │  │   │
│  │   Update      │     │  │ • DPoP Verify  │  │   │
│  │   Source      │     │  │ • ...          │  │   │
│  └──────────────┘     │  └────────────────┘  │   │
│                        └──────────────────────┘   │
└─────────────────────────────────────────────────┘
Thành phầnMô tảVí dụ
ProfileTập hợp các Executors — định nghĩa "enforce cái gì"fapi-2-security-profile
ConditionĐiều kiện xác định client nào bị ảnh hưởng — "enforce cho ai"Clients có role fapi-client
ExecutorLogic enforcement cụ thể — "enforce như thế nào"Bắt buộc PKCE S256

1.3 Tạo Client Profile

  1. Vào Realm Settings → Client Policies → tab Profiles

  2. Click Create client profile

  3. Nhập Name và Description

  4. Click Save → mở profile → click Add executor

1.4 Executors có sẵn

ExecutorMô tảTham số
Secure Client AuthenticatorBắt buộc phương thức authentication cụ thểAllowed authenticators: client-secret, client-jwt, client-x509
PKCE EnforcerBắt buộc PKCEAugment: ON (tự thêm nếu client thiếu)
Secure Signing AlgorithmChỉ cho phép algorithms an toànDefault: RS256, ES256, PS256
Secure Signing Algorithm for Signed JWTAlgorithm cho client JWT authPS256, ES256 (không cho phép RS256)
Holder-of-Key EnforcerBắt buộc token binding (mTLS hoặc DPoP)Auto-configure: ON
DPoP Proof VerifierBắt buộc DPoP proof trong token requests
Confidential Client EnforcerChỉ cho phép confidential clients
Consent RequiredBắt buộc consent screen
Full Scope DisabledTắt full scope mapping
Reject Implicit GrantKhông cho phép implicit flow
Reject Resource Owner Password Credentials GrantKhông cho phép ROPC
Secure Redirect URIs EnforcerValidate redirect URIsRequire HTTPS, không wildcard
Secure Request ObjectBắt buộc JAR (JWT-Secured Authorization Request)
Secure Response TypeChỉ cho phép response types an toànAllowed: code (không token, id_token)
Secure Session EnforcerEnforce session settings

1.5 Conditions có sẵn

ConditionMô tảVí dụ
Any ClientÁp dụng cho tất cả clientsGlobal security policy
Client Access TypeDựa trên client type (public/confidential)Enforce PKCE cho tất cả public clients
Client RolesClient có role cụ thểClients có role fapi-compliant
Client ScopesClient sử dụng scope cụ thểClients request scope payment
Client Update Source GroupsDựa trên nguồn tạo/update clientClients tạo qua Dynamic Registration
Client Update ContextContext khi client được updateAuthorization request, Token request

1.6 Tạo Client Policy

  1. Vào Realm Settings → Client Policies → tab Policies

  2. Click Create client policy

  3. Nhập Name và Description

  4. Thêm Conditions (xác định client nào bị ảnh hưởng)

  5. Thêm Client Profiles (profile nào apply)

# Ví dụ: Tạo policy enforce PKCE cho tất cả public clients
Profile: pkce-required-profile
  Executors:
    - PKCE Enforcer
        Augment: ON (auto-add PKCE nếu client không gửi)

Policy: enforce-pkce-for-public
  Conditions:
    - Client Access Type: public
  Profiles:
    - pkce-required-profile

1.7 Ví dụ Policy thực tế

Policy 1: Baseline Security cho tất cả clients

Profile: baseline-security
  Executors:
    - Reject Implicit Grant
    - Reject Resource Owner Password Credentials Grant
    - PKCE Enforcer (S256)
    - Secure Signing Algorithm (RS256, ES256, PS256)

Policy: baseline-all-clients Conditions: - Any Client Profiles: - baseline-security

Policy 2: High-Security cho Financial APIs

Profile: financial-api-profile
  Executors:
    - Confidential Client Enforcer
    - Holder-of-Key Enforcer (mTLS hoặc DPoP)
    - Secure Client Authenticator (private_key_jwt, client-x509)
    - Secure Request Object Required
    - Consent Required
    - Secure Redirect URIs Enforcer (HTTPS only)

Policy: financial-api-policy Conditions: - Client Scopes: fapi-scope Profiles: - financial-api-profile

2. FAPI 2.0 Security Profile

FAPI (Financial-grade API) là bộ tiêu chuẩn bảo mật cao do OpenID Foundation phát triển, được sử dụng rộng rãi trong Open Banking, Payment Services Directive 2 (PSD2), và các ứng dụng tài chính.

2.1 FAPI 2.0 Baseline Profile

Keycloak cung cấp sẵn built-in profiles cho FAPI 2.0:

Yêu cầuMô tả
Authorization Code Flow onlyKhông cho phép implicit, ROPC
PKCE (S256)Bắt buộc cho tất cả clients
Confidential ClientBắt buộc client authentication
Secure Signing AlgorithmsPS256, ES256 (không RS256)
Sender-constrained tokensDPoP hoặc mTLS token binding
Redirect URI exact matchKhông wildcard
HTTPS requiredCho tất cả endpoints

2.2 FAPI 2.0 Advanced Profile (Message Signing)

Ngoài baseline, Advanced Profile thêm:

  • PAR (Pushed Authorization Requests) — RFC 9126: gửi authorization request qua backchannel trước khi redirect

  • JAR (JWT-Secured Authorization Request) — RFC 9101: authorization parameters được ký trong JWT

  • JARM (JWT-Secured Authorization Response Mode): authorization response được ký trong JWT

# PAR request — gửi authorization params qua backchannel
POST /realms/my-realm/protocol/openid-connect/ext/par/request
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

response_type=code&
client_id=my-fapi-client&
redirect_uri=https://myapp.com/callback&
scope=openid payments&
state=random-state&
code_challenge=code_challenge_value&
code_challenge_method=S256

# Response
{
  "request_uri": "urn:ietf:params:oauth:request_uri:abc123",
  "expires_in": 60
}

# Authorization request chỉ chứa request_uri
GET /realms/my-realm/protocol/openid-connect/auth?
  client_id=my-fapi-client&
  request_uri=urn:ietf:params:oauth:request_uri:abc123

2.3 Bật FAPI 2.0 trong Keycloak

  1. Vào Realm Settings → Client Policies → tab Profiles

  2. Keycloak cung cấp sẵn Global Profiles:

    • fapi-2-security-profile
    • fapi-2-message-signing-profile
  3. Tạo Policy sử dụng profile tương ứng

  4. Gán Condition để chọn clients cần compliance

# Ví dụ: Enforce FAPI 2.0 cho clients có scope "fapi"
Policy: fapi-2-enforcement
  Conditions:
    - Client Scopes: fapi
  Profiles:
    - fapi-2-security-profile     # Built-in global profile
    - fapi-2-message-signing-profile  # Thêm nếu cần message signing

3. Client Secret Rotation

Client Secret Rotation cho phép thay đổi client secret không gây downtime — secret cũ vẫn hoạt động trong một khoảng thời gian chuyển tiếp.

3.1 Cấu hình Client Secret Rotation

Sử dụng Client Policy với executor Secret Rotation:

# Tạo Profile với Secret Rotation executor
Profile: secret-rotation-profile
  Executors:
    - Secret Rotation
        Secret Expiration: 2592000        # 30 ngày (tính bằng giây)
        Rotated Secret Expiration: 604800  # Grace period: 7 ngày
        Remain Expiration: 604800          # Thời gian cảnh báo trước khi hết hạn

Cách hoạt động:

Timeline:
┌──────────────────────────────────────────────────────────┐
│ Ngày 0         Ngày 23        Ngày 30          Ngày 37  │
│   │               │              │                │     │
│   ▼               ▼              ▼                ▼     │
│ Secret A      Cảnh báo      Secret B           Secret A │
│ created       sắp hết hạn   created + active   hết hạn  │
│                              Secret A vẫn       hoàn toàn│
│                              hoạt động                   │
│                              (grace period)              │
└──────────────────────────────────────────────────────────┘

Khoảng grace period (Ngày 30-37):

  • Secret B: active (primary)
  • Secret A: vẫn valid (rotated secret, grace) → Ứng dụng có 7 ngày để chuyển sang Secret B

3.2 Triển khai Secret Rotation

# 1. Lấy current secret
CURRENT_SECRET=$(curl -s -X GET \
  "$KC_URL/admin/realms/my-realm/clients/$CLIENT_UUID/client-secret" \
  -H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.value')

2. Rotate secret — regenerate new secret

curl -s -X POST
"$KC_URL/admin/realms/my-realm/clients/$CLIENT_UUID/client-secret"
-H "Authorization: Bearer $ADMIN_TOKEN"

3. Lấy new secret

NEW_SECRET=$(curl -s -X GET
"$KC_URL/admin/realms/my-realm/clients/$CLIENT_UUID/client-secret"
-H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.value')

4. Update ứng dụng với new secret

Trong grace period, cả current và new secret đều hoạt động

4. Service Accounts

Khi bật Service accounts roles cho confidential client, Keycloak tạo một service account user đặc biệt cho client đó. User này đại diện cho client trong các machine-to-machine operations.

4.1 Service Account User

# Service account user naming convention
Username: service-account-{client-id}
# Ví dụ: service-account-my-backend-service

Service account user có các đặc điểm:

- Không có password (authenticate bằng client credentials)

- Có thể gán realm roles và client roles

- Có thể thêm user attributes

- Xuất hiện trong Users list (với filter service accounts)

4.2 Gán Roles cho Service Account

  1. Mở client → tab Service account roles

  2. Click Assign role

  3. Chọn realm roles hoặc filter by clients để gán client roles

# Admin CLI: Gán roles
# Gán realm role
bin/kcadm.sh add-roles -r my-realm \
  --uusername service-account-my-backend-service \
  --rolename realm-admin

# Gán client role từ client khác
bin/kcadm.sh add-roles -r my-realm \
  --uusername service-account-my-backend-service \
  --cclientid realm-management \
  --rolename manage-users

# REST API: Gán role
# Lấy service account user
SA_USER=$(curl -s -X GET \
  "$KC_URL/admin/realms/my-realm/clients/$CLIENT_UUID/service-account-user" \
  -H "Authorization: Bearer $ADMIN_TOKEN")

SA_USER_ID=$(echo $SA_USER | jq -r '.id')

# Gán realm role
ROLE_ID=$(curl -s -X GET \
  "$KC_URL/admin/realms/my-realm/roles/admin" \
  -H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.id')

curl -s -X POST \
  "$KC_URL/admin/realms/my-realm/users/$SA_USER_ID/role-mappings/realm" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"id":"'$ROLE_ID'","name":"admin"}]'

4.3 Best Practices cho Service Accounts

  • Least privilege: Chỉ gán roles cần thiết cho mỗi service

  • Separate clients: Tạo client riêng cho mỗi microservice, không dùng chung

  • Audit: Enable events logging để track service account activities

  • Short token lifespan: Access token cho service accounts nên ngắn (1-5 phút)

  • Rotate credentials: Sử dụng Client Secret Rotation hoặc certificate-based auth

5. Audience Support

Audience (aud claim) xác định resource server nào access token được dự định sử dụng. Đây là cơ chế bảo mật quan trọng để ngăn token được sử dụng ở service không mong muốn.

5.1 Vấn đề

# Mặc định, access token chỉ có aud = client-id đã request
{
  "aud": "my-frontend-app",     // ← chỉ có client đã request
  "azp": "my-frontend-app"
}

Resource Server (my-api-service) verify token:

→ aud không chứa "my-api-service"

→ REJECT! (nếu resource server validate audience)

5.2 Giải pháp: Audience Protocol Mapper

Thêm Audience Mapper vào client hoặc client scope để thêm resource server vào aud:

# Cách 1: Thêm Audience Mapper trực tiếp vào client
Client: my-frontend-app → Client scopes → Dedicated scope → Add mapper
  Mapper Type: Audience
  Name: api-audience
  Included Client Audience: my-api-service
  Included Custom Audience: (trống)
  Add to ID token: OFF
  Add to access token: ON

# Cách 2: Tạo Client Scope chứa Audience Mapper
Client Scope: api-access
  Mapper: Audience → my-api-service
  Gán scope cho frontend client

# Kết quả trong access token:
{
  "aud": ["my-frontend-app", "my-api-service"],
  "azp": "my-frontend-app"
}

5.3 Audience Resolve Mapper

Keycloak có built-in Audience Resolve mapper (trong default scope roles) — tự động thêm aud cho clients mà user có client roles:

# Nếu user có role "app-admin" của client "my-api-service"
# → Audience Resolve tự động thêm "my-api-service" vào aud
{
  "aud": ["my-frontend-app", "my-api-service"],
  "resource_access": {
    "my-api-service": {
      "roles": ["app-admin"]
    }
  }
}

6. Confidential Client Credentials

6.1 Client ID and Secret

Phương thức đơn giản nhất — client gửi ID và secret trong request:

# Cách 1: Form parameter
POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
client_id=my-client&
client_secret=my-secret

# Cách 2: HTTP Basic Authentication
POST /token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

grant_type=client_credentials

6.2 Signed JWT (private_key_jwt)

Client tạo và ký JWT bằng private key, gửi đến Keycloak. Keycloak verify bằng public key/certificate đã đăng ký.

Cấu hình trong Keycloak:

  1. Client → tab Credentials → Client Authenticator: Signed JWT

  2. Upload client certificate hoặc JWKS URL

# Tạo key pair cho client
openssl genrsa -out client-private.pem 2048
openssl req -new -x509 -key client-private.pem -out client-cert.pem -days 365

# Upload client-cert.pem vào Keycloak client Credentials tab

# Token request với client_assertion
POST /realms/my-realm/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
client_id=my-client&
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
client_assertion=eyJhbGciOiJSUzI1NiIs...

Client Assertion JWT structure:

{
  "iss": "my-client",                    // Client ID
  "sub": "my-client",                    // Client ID
  "aud": "http://localhost:8080/realms/my-realm",  // Token endpoint
  "iat": 1711800000,
  "exp": 1711800060,                     // Short-lived (60s)
  "jti": "unique-jwt-id"                 // Unique ID
}

6.3 X.509 Certificate / Mutual TLS

Client xác thực bằng client TLS certificate (Mutual TLS — mTLS). Đây là phương thức bảo mật nhất.

Cấu hình:

  1. Client → tab Credentials → Client Authenticator: X.509 Certificate

  2. Nhập Subject DN hoặc pattern cho certificate matching

  3. Cấu hình Keycloak server enable mTLS endpoint

# Keycloak mTLS configuration (quarkus)
# conf/keycloak.conf hoặc environment variables
KC_HTTPS_CLIENT_AUTH=request
KC_HTTPS_KEY_STORE_FILE=/opt/keycloak/certs/server-keystore.p12
KC_HTTPS_TRUST_STORE_FILE=/opt/keycloak/certs/truststore.p12

# Client gọi token endpoint với client certificate
curl -s -X POST \
  "https://localhost:8443/realms/my-realm/protocol/openid-connect/token" \
  --cert client-cert.pem \
  --key client-private.pem \
  -d "grant_type=client_credentials" \
  -d "client_id=my-mtls-client"

Kết hợp mTLS với certificate-bound tokens:

# Access token chứa certificate thumbprint
{
  "cnf": {
    "x5t#S256": "sha256-thumbprint-of-client-certificate"
  }
}

Resource server verify:

1. Client gửi request với TLS client certificate

2. Resource server extract certificate thumbprint

3. So sánh với cnf.x5t#S256 trong access token

→ Nếu match → token hợp lệ + bound to correct client

7. Standard Token Exchange (RFC 8693)

Token Exchange cho phép một service exchange token để nhận token mới với quyền hạn hoặc audience khác.

7.1 Use Cases

  • Delegation: Service A muốn gọi Service B "nhân danh" user — exchange access token lấy token mới với audience = Service B

  • Impersonation: Admin muốn hoạt động như user khác

  • Token type conversion: Exchange access token lấy SAML assertion (hoặc ngược lại)

7.2 Cấu hình Token Exchange

Token Exchange trong Keycloak là preview feature — cần enable:

# Bật feature
bin/kc.sh start-dev --features=token-exchange

# Docker
docker run -e KC_FEATURES=token-exchange quay.io/keycloak/keycloak:26.2.4 start-dev

Cấu hình permissions:

  1. Mở target client (client mà bạn muốn exchange token sang) → tab Permissions

  2. Bật Permissions Enabled

  3. Click token-exchange permission → cấu hình policy cho phép source client exchange

# Token Exchange request
POST /realms/my-realm/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=USER_ACCESS_TOKEN&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
requested_token_type=urn:ietf:params:oauth:token-type:access_token&
audience=target-service&
client_id=source-service&
client_secret=SOURCE_SECRET

# Response — token mới cho target-service
{
  "access_token": "new-token-for-target-service",
  "token_type": "Bearer",
  "expires_in": 300,
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
}

7.3 Delegation vs Impersonation

ModeMô tảToken claims
DelegationService B biết rằng Service A đang hành động nhân danh useract.sub = Service A, sub = user
ImpersonationService B không biết — token giống hệt user trực tiếp requestsub = user (không có act)

8. JWT Authorization Grant (RFC 7523)

Cho phép client sử dụng một JWT assertion được cấp bởi trusted issuer để lấy access token mà không cần user interaction.

8.1 Flow

# External issuer (ví dụ: Azure AD, Google) cấp JWT cho client
# Client gửi JWT đến Keycloak để exchange lấy Keycloak access token

POST /realms/my-realm/protocol/openid-connect/token Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer& assertion=eyJhbGciOiJSUzI1NiIs...& # JWT from external issuer client_id=my-client& client_secret=my-secret& scope=openid

8.2 Cấu hình JWT Grant

  1. Realm Settings → Keys → thêm external issuer's signing key

  2. Hoặc cấu hình Identity Provider cho external issuer

  3. Client phải có Service accounts roles enabled

9. Cấu hình Keycloak cho MCP Servers

Model Context Protocol (MCP) servers sử dụng OAuth 2.0 để xác thực clients. Keycloak có thể đóng vai trò Authorization Server cho MCP ecosystem.

9.1 MCP OAuth 2.0 Flow

MCP specification yêu cầu OAuth 2.0 cho server-to-server và client-to-server authentication:

┌──────────┐     ┌──────────┐     ┌──────────┐
│ MCP Host │     │ Keycloak │     │MCP Server│
│ (Client) │     │  (AuthZ) │     │(Resource)│
└────┬─────┘     └────┬─────┘     └────┬─────┘
     │                │                │
     │ 1. Request     │                │
     │    auth info    │                │
     │───────────────────────────────>│
     │ 2. Return      │                │
     │    auth metadata│                │
     │<──────────────────────────────│
     │                │                │
     │ 3. Authorization Code Flow     │
     │    (hoặc Client Credentials)   │
     │───────────────>│                │
     │ 4. Tokens      │                │
     │<───────────────│                │
     │                │                │
     │ 5. API call with access token  │
     │───────────────────────────────>│
     │ 6. MCP Server validates token  │
     │    via Keycloak JWKS/Introspect│
     │<──────────────────────────────│

9.2 Tạo Client cho MCP Host

# MCP Host client — ứng dụng AI/LLM kết nối tới MCP servers
Client ID: mcp-host-app
Client type: OpenID Connect
Client authentication: ON (confidential)

Capability Config: Standard flow: ON # Cho interactive MCP sessions Service accounts roles: ON # Cho automated MCP operations

Access Settings: Valid redirect URIs: http://localhost:3001/callback Web origins: http://localhost:3001

Advanced: PKCE Code Challenge Method: S256 Access Token Lifespan: 300 # 5 phút

9.3 Tạo Client cho MCP Server (Resource Server)

# MCP Server client — validate incoming tokens
Client ID: mcp-tool-server
Client type: OpenID Connect
Client authentication: ON (confidential)

Capability Config: Standard flow: OFF Service accounts roles: ON # Nếu MCP server cần gọi Keycloak APIs

MCP Server cấu hình JWT validation

Sử dụng Keycloak JWKS endpoint để verify access tokens

JWKS_URI: http://localhost:8080/realms/my-realm/protocol/openid-connect/certs ISSUER: http://localhost:8080/realms/my-realm

9.4 Tạo Scopes cho MCP Operations

# Tạo Client Scopes cho MCP permissions
Client Scope: mcp:tools:read
  Type: Optional
  Description: Read access to MCP tools
  Protocol Mapper: Hardcoded claim
    Token Claim Name: mcp_permissions
    Claim Value: ["tools:read"]

Client Scope: mcp:tools:execute Type: Optional Description: Execute MCP tools Protocol Mapper: Hardcoded claim Token Claim Name: mcp_permissions Claim Value: ["tools:execute"]

Client Scope: mcp:resources:read Type: Optional Description: Read MCP resources Protocol Mapper: Hardcoded claim Token Claim Name: mcp_permissions Claim Value: ["resources:read"]

Gán scopes cho MCP Host client

Client: mcp-host-app Default scopes: mcp:tools:read, mcp:resources:read Optional scopes: mcp:tools:execute

9.5 Audience Mapper cho MCP

# MCP Host client cần access token với audience = MCP Server
Client: mcp-host-app → Client scopes → Dedicated scope → Add mapper
  Mapper Type: Audience
  Name: mcp-server-audience
  Included Client Audience: mcp-tool-server
  Add to access token: ON

Access token kết quả:

{ "iss": "http://localhost:8080/realms/my-realm", "sub": "user-or-service-account-id", "aud": ["mcp-host-app", "mcp-tool-server"], "azp": "mcp-host-app", "scope": "openid mcp:tools:read mcp:resources:read", "mcp_permissions": ["tools:read", "resources:read"] }

9.6 Token Exchange cho MCP Multi-Server

Khi MCP Host cần gọi nhiều MCP servers khác nhau, sử dụng Token Exchange để lấy token cho từng server:

# MCP Host có access token cho mcp-tool-server-1
# Cần access mcp-tool-server-2

POST /realms/my-realm/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=CURRENT_ACCESS_TOKEN&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
audience=mcp-tool-server-2&
client_id=mcp-host-app&
client_secret=HOST_SECRET&
scope=mcp:tools:execute

9.7 Client Policy cho MCP

# Enforce security cho tất cả MCP clients
Profile: mcp-security-profile
  Executors:
    - PKCE Enforcer (S256)
    - Confidential Client Enforcer
    - Secure Signing Algorithm (RS256, ES256)
    - Reject Implicit Grant
    - Reject Resource Owner Password Credentials Grant
    - Holder-of-Key Enforcer  # DPoP cho high-security MCP operations

Policy: mcp-clients-policy Conditions: - Client Scopes: mcp:tools:read # Áp dụng cho clients request MCP scopes Profiles: - mcp-security-profile

10. Bài tập thực hành

Lab 1: Client Policies — Baseline Security

  1. Tạo Client Profile baseline-security với executors: Reject Implicit Grant, PKCE Enforcer, Secure Signing Algorithm

  2. Tạo Client Policy enforce-baseline với condition Any Client

  3. Test: Tạo client mới và thử request token không có PKCE → bị reject

  4. Test: Thử bật Implicit flow → bị reject

Lab 2: FAPI 2.0 Compliance

  1. Tạo Client Profile sử dụng built-in FAPI 2.0 Security Profile

  2. Tạo Policy chỉ áp dụng cho clients có role fapi-client

  3. Tạo confidential client với Signed JWT authentication

  4. Test authorization flow đầy đủ với PAR + PKCE + DPoP

Lab 3: Client Secret Rotation

  1. Cấu hình Secret Rotation executor (expiration: 60 giây, grace: 30 giây cho testing)

  2. Tạo confidential client → ghi nhận secret A

  3. Chờ 60 giây → regenerate secret → ghi nhận secret B

  4. Verify: Secret A vẫn hoạt động trong grace period (30 giây)

  5. Verify: Sau grace period, chỉ secret B hoạt động

Lab 4: Service Account + Token Exchange

  1. Tạo 3 clients: frontend-app (public), api-gateway (confidential + service account), payment-service (confidential)

  2. User đăng nhập qua frontend-app → nhận access token

  3. api-gateway nhận token từ frontend, exchange lấy token mới cho payment-service

  4. Verify: Token mới có aud: payment-service và act.sub: api-gateway

Lab 5: MCP Server Configuration

  1. Tạo realm mcp-demo

  2. Tạo clients: mcp-host (confidential), mcp-tools-server (confidential)

  3. Tạo client scopes: mcp:tools:read, mcp:tools:execute

  4. Cấu hình Audience Mapper cho mcp-host → audience = mcp-tools-server

  5. Lấy token với Client Credentials flow

  6. Verify token contents: audience, scopes, permissions

  7. Simulate MCP Server validate token bằng JWKS endpoint

Lab 6: Signed JWT Client Authentication

  1. Generate RSA key pair (openssl)

  2. Tạo confidential client với authenticator = Signed JWT

  3. Upload certificate vào Keycloak

  4. Viết script tạo và ký client_assertion JWT

  5. Request token với client_assertion thay vì client_secret

  6. Verify token nhận được