keycloak-admin
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseKeycloak Admin Skill
Keycloak管理技能
Comprehensive Keycloak administration for the keycloak-alpha multi-tenant MERN platform with OAuth 2.0 Authorization Code Flow.
适用于keycloak-alpha多租户MERN平台的全功能Keycloak管理方案,支持OAuth 2.0授权码流。
When to Use This Skill
何时使用该技能
Activate this skill when:
- Setting up Keycloak realms and clients
- Configuring OAuth 2.0 Authorization Code Flow
- Managing users with custom attributes (org_id)
- Deploying custom themes
- Troubleshooting authentication issues
- Configuring token lifetimes and session management
在以下场景下激活本技能:
- 搭建Keycloak realm和客户端
- 配置OAuth 2.0授权码流
- 管理带自定义属性(org_id)的用户
- 部署自定义主题
- 排查身份认证相关问题
- 配置令牌有效期和会话管理
Keycloak Admin REST API
Keycloak Admin REST API
Authentication
身份认证
Use the admin-cli client to obtain an access token:
bash
undefined使用admin-cli客户端获取访问令牌:
bash
undefinedGet admin access token
Get admin access token
TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "username=admin"
-d "password=admin"
-d "grant_type=password"
-d "client_id=admin-cli" | jq -r '.access_token')
-H "Content-Type: application/x-www-form-urlencoded"
-d "username=admin"
-d "password=admin"
-d "grant_type=password"
-d "client_id=admin-cli" | jq -r '.access_token')
TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "username=admin"
-d "password=admin"
-d "grant_type=password"
-d "client_id=admin-cli" | jq -r '.access_token')
-H "Content-Type: application/x-www-form-urlencoded"
-d "username=admin"
-d "password=admin"
-d "grant_type=password"
-d "client_id=admin-cli" | jq -r '.access_token')
Use token in subsequent requests
Use token in subsequent requests
curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/master"
"http://localhost:8080/admin/realms/master"
undefinedcurl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/master"
"http://localhost:8080/admin/realms/master"
undefinedKey API Endpoints
核心API端点
| Endpoint | Method | Purpose |
|---|---|---|
| GET | List all realms |
| POST | Create realm |
| GET/POST | Manage clients |
| GET/POST | Manage users |
| GET/POST | Manage roles |
| GET/POST | Manage groups |
| 端点 | 请求方法 | 用途 |
|---|---|---|
| GET | 列出所有realm |
| POST | 创建realm |
| GET/POST | 管理客户端 |
| GET/POST | 管理用户 |
| GET/POST | 管理角色 |
| GET/POST | 管理用户组 |
Realm Creation and Configuration
Realm创建与配置
Create a New Realm
创建新Realm
bash
undefinedbash
undefinedCreate realm with basic configuration
Create realm with basic configuration
curl -X POST "http://localhost:8080/admin/realms"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "realm": "lobbi", "enabled": true, "displayName": "Lobbi Platform", "sslRequired": "external", "registrationAllowed": false, "loginWithEmailAllowed": true, "duplicateEmailsAllowed": false, "resetPasswordAllowed": true, "editUsernameAllowed": false, "bruteForceProtected": true, "permanentLockout": false, "maxFailureWaitSeconds": 900, "minimumQuickLoginWaitSeconds": 60, "waitIncrementSeconds": 60, "quickLoginCheckMilliSeconds": 1000, "maxDeltaTimeSeconds": 43200, "failureFactor": 30, "defaultSignatureAlgorithm": "RS256", "revokeRefreshToken": false, "refreshTokenMaxReuse": 0, "accessTokenLifespan": 300, "accessTokenLifespanForImplicitFlow": 900, "ssoSessionIdleTimeout": 1800, "ssoSessionMaxLifespan": 36000, "offlineSessionIdleTimeout": 2592000, "accessCodeLifespan": 60, "accessCodeLifespanUserAction": 300, "accessCodeLifespanLogin": 1800 }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "realm": "lobbi", "enabled": true, "displayName": "Lobbi Platform", "sslRequired": "external", "registrationAllowed": false, "loginWithEmailAllowed": true, "duplicateEmailsAllowed": false, "resetPasswordAllowed": true, "editUsernameAllowed": false, "bruteForceProtected": true, "permanentLockout": false, "maxFailureWaitSeconds": 900, "minimumQuickLoginWaitSeconds": 60, "waitIncrementSeconds": 60, "quickLoginCheckMilliSeconds": 1000, "maxDeltaTimeSeconds": 43200, "failureFactor": 30, "defaultSignatureAlgorithm": "RS256", "revokeRefreshToken": false, "refreshTokenMaxReuse": 0, "accessTokenLifespan": 300, "accessTokenLifespanForImplicitFlow": 900, "ssoSessionIdleTimeout": 1800, "ssoSessionMaxLifespan": 36000, "offlineSessionIdleTimeout": 2592000, "accessCodeLifespan": 60, "accessCodeLifespanUserAction": 300, "accessCodeLifespanLogin": 1800 }'
undefinedcurl -X POST "http://localhost:8080/admin/realms"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "realm": "lobbi", "enabled": true, "displayName": "Lobbi Platform", "sslRequired": "external", "registrationAllowed": false, "loginWithEmailAllowed": true, "duplicateEmailsAllowed": false, "resetPasswordAllowed": true, "editUsernameAllowed": false, "bruteForceProtected": true, "permanentLockout": false, "maxFailureWaitSeconds": 900, "minimumQuickLoginWaitSeconds": 60, "waitIncrementSeconds": 60, "quickLoginCheckMilliSeconds": 1000, "maxDeltaTimeSeconds": 43200, "failureFactor": 30, "defaultSignatureAlgorithm": "RS256", "revokeRefreshToken": false, "refreshTokenMaxReuse": 0, "accessTokenLifespan": 300, "accessTokenLifespanForImplicitFlow": 900, "ssoSessionIdleTimeout": 1800, "ssoSessionMaxLifespan": 36000, "offlineSessionIdleTimeout": 2592000, "accessCodeLifespan": 60, "accessCodeLifespanUserAction": 300, "accessCodeLifespanLogin": 1800 }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "realm": "lobbi", "enabled": true, "displayName": "Lobbi Platform", "sslRequired": "external", "registrationAllowed": false, "loginWithEmailAllowed": true, "duplicateEmailsAllowed": false, "resetPasswordAllowed": true, "editUsernameAllowed": false, "bruteForceProtected": true, "permanentLockout": false, "maxFailureWaitSeconds": 900, "minimumQuickLoginWaitSeconds": 60, "waitIncrementSeconds": 60, "quickLoginCheckMilliSeconds": 1000, "maxDeltaTimeSeconds": 43200, "failureFactor": 30, "defaultSignatureAlgorithm": "RS256", "revokeRefreshToken": false, "refreshTokenMaxReuse": 0, "accessTokenLifespan": 300, "accessTokenLifespanForImplicitFlow": 900, "ssoSessionIdleTimeout": 1800, "ssoSessionMaxLifespan": 36000, "offlineSessionIdleTimeout": 2592000, "accessCodeLifespan": 60, "accessCodeLifespanUserAction": 300, "accessCodeLifespanLogin": 1800 }'
undefinedConfigure Realm Settings
配置Realm设置
javascript
// In keycloak-alpha: services/keycloak-service/src/config/realm-config.js
export const realmDefaults = {
realm: process.env.KEYCLOAK_REALM || 'lobbi',
enabled: true,
displayName: 'Lobbi Platform',
// Security settings
sslRequired: 'external',
registrationAllowed: false,
loginWithEmailAllowed: true,
duplicateEmailsAllowed: false,
// Token lifespans (seconds)
accessTokenLifespan: 300, // 5 minutes
accessTokenLifespanForImplicitFlow: 900, // 15 minutes
ssoSessionIdleTimeout: 1800, // 30 minutes
ssoSessionMaxLifespan: 36000, // 10 hours
offlineSessionIdleTimeout: 2592000, // 30 days
// Login settings
resetPasswordAllowed: true,
editUsernameAllowed: false,
// Brute force protection
bruteForceProtected: true,
permanentLockout: false,
maxFailureWaitSeconds: 900,
minimumQuickLoginWaitSeconds: 60,
failureFactor: 30
};javascript
// In keycloak-alpha: services/keycloak-service/src/config/realm-config.js
export const realmDefaults = {
realm: process.env.KEYCLOAK_REALM || 'lobbi',
enabled: true,
displayName: 'Lobbi Platform',
// Security settings
sslRequired: 'external',
registrationAllowed: false,
loginWithEmailAllowed: true,
duplicateEmailsAllowed: false,
// Token lifespans (seconds)
accessTokenLifespan: 300, // 5 minutes
accessTokenLifespanForImplicitFlow: 900, // 15 minutes
ssoSessionIdleTimeout: 1800, // 30 minutes
ssoSessionMaxLifespan: 36000, // 10 hours
offlineSessionIdleTimeout: 2592000, // 30 days
// Login settings
resetPasswordAllowed: true,
editUsernameAllowed: false,
// Brute force protection
bruteForceProtected: true,
permanentLockout: false,
maxFailureWaitSeconds: 900,
minimumQuickLoginWaitSeconds: 60,
failureFactor: 30
};Client Configuration for OAuth 2.0 Authorization Code Flow
OAuth 2.0授权码流的客户端配置
Create Client
创建客户端
bash
undefinedbash
undefinedCreate client for Authorization Code Flow
Create client for Authorization Code Flow
curl -X POST "http://localhost:8080/admin/realms/lobbi/clients"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "clientId": "lobbi-web-app", "name": "Lobbi Web Application", "enabled": true, "protocol": "openid-connect", "publicClient": false, "standardFlowEnabled": true, "implicitFlowEnabled": false, "directAccessGrantsEnabled": false, "serviceAccountsEnabled": false, "redirectUris": [ "http://localhost:3000/auth/callback", "https://.lobbi.com/auth/callback" ], "webOrigins": [ "http://localhost:3000", "https://.lobbi.com" ], "attributes": { "pkce.code.challenge.method": "S256" }, "defaultClientScopes": [ "email", "profile", "roles", "web-origins" ], "optionalClientScopes": [ "address", "phone", "offline_access" ] }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "clientId": "lobbi-web-app", "name": "Lobbi Web Application", "enabled": true, "protocol": "openid-connect", "publicClient": false, "standardFlowEnabled": true, "implicitFlowEnabled": false, "directAccessGrantsEnabled": false, "serviceAccountsEnabled": false, "redirectUris": [ "http://localhost:3000/auth/callback", "https://.lobbi.com/auth/callback" ], "webOrigins": [ "http://localhost:3000", "https://.lobbi.com" ], "attributes": { "pkce.code.challenge.method": "S256" }, "defaultClientScopes": [ "email", "profile", "roles", "web-origins" ], "optionalClientScopes": [ "address", "phone", "offline_access" ] }'
undefinedcurl -X POST "http://localhost:8080/admin/realms/lobbi/clients"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "clientId": "lobbi-web-app", "name": "Lobbi Web Application", "enabled": true, "protocol": "openid-connect", "publicClient": false, "standardFlowEnabled": true, "implicitFlowEnabled": false, "directAccessGrantsEnabled": false, "serviceAccountsEnabled": false, "redirectUris": [ "http://localhost:3000/auth/callback", "https://.lobbi.com/auth/callback" ], "webOrigins": [ "http://localhost:3000", "https://.lobbi.com" ], "attributes": { "pkce.code.challenge.method": "S256" }, "defaultClientScopes": [ "email", "profile", "roles", "web-origins" ], "optionalClientScopes": [ "address", "phone", "offline_access" ] }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "clientId": "lobbi-web-app", "name": "Lobbi Web Application", "enabled": true, "protocol": "openid-connect", "publicClient": false, "standardFlowEnabled": true, "implicitFlowEnabled": false, "directAccessGrantsEnabled": false, "serviceAccountsEnabled": false, "redirectUris": [ "http://localhost:3000/auth/callback", "https://.lobbi.com/auth/callback" ], "webOrigins": [ "http://localhost:3000", "https://.lobbi.com" ], "attributes": { "pkce.code.challenge.method": "S256" }, "defaultClientScopes": [ "email", "profile", "roles", "web-origins" ], "optionalClientScopes": [ "address", "phone", "offline_access" ] }'
undefinedClient Configuration in keycloak-alpha
keycloak-alpha中的客户端配置
javascript
// In: apps/web-app/src/config/keycloak.config.js
export const keycloakConfig = {
url: process.env.VITE_KEYCLOAK_URL || 'http://localhost:8080',
realm: process.env.VITE_KEYCLOAK_REALM || 'lobbi',
clientId: process.env.VITE_KEYCLOAK_CLIENT_ID || 'lobbi-web-app',
};
// OAuth 2.0 Authorization Code Flow with PKCE
export const authConfig = {
flow: 'standard',
pkceMethod: 'S256',
responseType: 'code',
scope: 'openid profile email roles',
// Redirect URIs
redirectUri: `${window.location.origin}/auth/callback`,
postLogoutRedirectUri: `${window.location.origin}/`,
// Token handling
checkLoginIframe: true,
checkLoginIframeInterval: 5,
onLoad: 'check-sso',
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`
};javascript
// In: apps/web-app/src/config/keycloak.config.js
export const keycloakConfig = {
url: process.env.VITE_KEYCLOAK_URL || 'http://localhost:8080',
realm: process.env.VITE_KEYCLOAK_REALM || 'lobbi',
clientId: process.env.VITE_KEYCLOAK_CLIENT_ID || 'lobbi-web-app',
};
// OAuth 2.0 Authorization Code Flow with PKCE
export const authConfig = {
flow: 'standard',
pkceMethod: 'S256',
responseType: 'code',
scope: 'openid profile email roles',
// Redirect URIs
redirectUri: `${window.location.origin}/auth/callback`,
postLogoutRedirectUri: `${window.location.origin}/`,
// Token handling
checkLoginIframe: true,
checkLoginIframeInterval: 5,
onLoad: 'check-sso',
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`
};Client Secret Management
客户端密钥管理
bash
undefinedbash
undefinedGet client secret
Get client secret
CLIENT_UUID=$(curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/clients?clientId=lobbi-web-app"
| jq -r '.[0].id')
"http://localhost:8080/admin/realms/lobbi/clients?clientId=lobbi-web-app"
| jq -r '.[0].id')
curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/client-secret"
| jq -r '.value'
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/client-secret"
| jq -r '.value'
CLIENT_UUID=$(curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/clients?clientId=lobbi-web-app"
| jq -r '.[0].id')
"http://localhost:8080/admin/realms/lobbi/clients?clientId=lobbi-web-app"
| jq -r '.[0].id')
curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/client-secret"
| jq -r '.value'
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/client-secret"
| jq -r '.value'
Regenerate client secret
Regenerate client secret
curl -X POST -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/client-secret"
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/client-secret"
undefinedcurl -X POST -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/client-secret"
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/client-secret"
undefinedUser Management with Custom Attributes
带自定义属性的用户管理
Create User with org_id
创建带org_id的用户
bash
undefinedbash
undefinedCreate user with custom org_id attribute
Create user with custom org_id attribute
curl -X POST "http://localhost:8080/admin/realms/lobbi/users"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "username": "john.doe@acme.com", "email": "john.doe@acme.com", "firstName": "John", "lastName": "Doe", "enabled": true, "emailVerified": true, "attributes": { "org_id": ["org_acme"], "tenant_name": ["ACME Corporation"] }, "credentials": [{ "type": "password", "value": "temp_password_123", "temporary": true }] }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "username": "john.doe@acme.com", "email": "john.doe@acme.com", "firstName": "John", "lastName": "Doe", "enabled": true, "emailVerified": true, "attributes": { "org_id": ["org_acme"], "tenant_name": ["ACME Corporation"] }, "credentials": [{ "type": "password", "value": "temp_password_123", "temporary": true }] }'
undefinedcurl -X POST "http://localhost:8080/admin/realms/lobbi/users"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "username": "john.doe@acme.com", "email": "john.doe@acme.com", "firstName": "John", "lastName": "Doe", "enabled": true, "emailVerified": true, "attributes": { "org_id": ["org_acme"], "tenant_name": ["ACME Corporation"] }, "credentials": [{ "type": "password", "value": "temp_password_123", "temporary": true }] }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "username": "john.doe@acme.com", "email": "john.doe@acme.com", "firstName": "John", "lastName": "Doe", "enabled": true, "emailVerified": true, "attributes": { "org_id": ["org_acme"], "tenant_name": ["ACME Corporation"] }, "credentials": [{ "type": "password", "value": "temp_password_123", "temporary": true }] }'
undefinedUser Service in keycloak-alpha
keycloak-alpha中的用户服务
javascript
// In: services/user-service/src/controllers/user.controller.js
import axios from 'axios';
export class UserController {
async createUser(req, res) {
const { email, firstName, lastName, orgId } = req.body;
// Get admin token
const adminToken = await this.getAdminToken();
// Create user in Keycloak
const userData = {
username: email,
email,
firstName,
lastName,
enabled: true,
emailVerified: false,
attributes: {
org_id: [orgId],
created_by: [req.user.sub]
},
credentials: [{
type: 'password',
value: this.generateTemporaryPassword(),
temporary: true
}]
};
try {
const response = await axios.post(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users`,
userData,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
// Extract user ID from Location header
const userId = response.headers.location.split('/').pop();
// Assign default roles
await this.assignRoles(userId, ['user'], adminToken);
// Send verification email
await this.sendVerificationEmail(userId, adminToken);
res.status(201).json({ userId, email });
} catch (error) {
console.error('User creation failed:', error.response?.data);
res.status(500).json({ error: 'Failed to create user' });
}
}
async getAdminToken() {
const response = await axios.post(
`${process.env.KEYCLOAK_URL}/realms/master/protocol/openid-connect/token`,
new URLSearchParams({
username: process.env.KEYCLOAK_ADMIN_USER,
password: process.env.KEYCLOAK_ADMIN_PASSWORD,
grant_type: 'password',
client_id: 'admin-cli'
}),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
return response.data.access_token;
}
}javascript
// In: services/user-service/src/controllers/user.controller.js
import axios from 'axios';
export class UserController {
async createUser(req, res) {
const { email, firstName, lastName, orgId } = req.body;
// Get admin token
const adminToken = await this.getAdminToken();
// Create user in Keycloak
const userData = {
username: email,
email,
firstName,
lastName,
enabled: true,
emailVerified: false,
attributes: {
org_id: [orgId],
created_by: [req.user.sub]
},
credentials: [{
"type": "password",
"value": this.generateTemporaryPassword(),
"temporary": true
}]
};
try {
const response = await axios.post(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users`,
userData,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
// Extract user ID from Location header
const userId = response.headers.location.split('/').pop();
// Assign default roles
await this.assignRoles(userId, ['user'], adminToken);
// Send verification email
await this.sendVerificationEmail(userId, adminToken);
res.status(201).json({ userId, email });
} catch (error) {
console.error('User creation failed:', error.response?.data);
res.status(500).json({ error: 'Failed to create user' });
}
}
async getAdminToken() {
const response = await axios.post(
`${process.env.KEYCLOAK_URL}/realms/master/protocol/openid-connect/token`,
new URLSearchParams({
username: process.env.KEYCLOAK_ADMIN_USER,
password: process.env.KEYCLOAK_ADMIN_PASSWORD,
grant_type: 'password',
client_id: 'admin-cli'
}),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
return response.data.access_token;
}
}Query Users by org_id
按org_id查询用户
bash
undefinedbash
undefinedSearch users by org_id attribute
Search users by org_id attribute
curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/users?q=org_id:org_acme"
"http://localhost:8080/admin/realms/lobbi/users?q=org_id:org_acme"
curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/users?q=org_id:org_acme"
"http://localhost:8080/admin/realms/lobbi/users?q=org_id:org_acme"
Get user with attributes
Get user with attributes
curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/users/{user-id}"
"http://localhost:8080/admin/realms/lobbi/users/{user-id}"
undefinedcurl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/users/{user-id}"
"http://localhost:8080/admin/realms/lobbi/users/{user-id}"
undefinedRole and Group Management
角色与用户组管理
Create Realm Roles
创建Realm角色
bash
undefinedbash
undefinedCreate organization-level roles
Create organization-level roles
curl -X POST "http://localhost:8080/admin/realms/lobbi/roles"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_admin", "description": "Organization Administrator", "composite": false, "clientRole": false }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_admin", "description": "Organization Administrator", "composite": false, "clientRole": false }'
curl -X POST "http://localhost:8080/admin/realms/lobbi/roles"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_user", "description": "Organization User", "composite": false, "clientRole": false }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_user", "description": "Organization User", "composite": false, "clientRole": false }'
undefinedcurl -X POST "http://localhost:8080/admin/realms/lobbi/roles"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_admin", "description": "Organization Administrator", "composite": false, "clientRole": false }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_admin", "description": "Organization Administrator", "composite": false, "clientRole": false }'
curl -X POST "http://localhost:8080/admin/realms/lobbi/roles"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_user", "description": "Organization User", "composite": false, "clientRole": false }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_user", "description": "Organization User", "composite": false, "clientRole": false }'
undefinedAssign Roles to User
为用户分配角色
javascript
// In: services/user-service/src/services/role.service.js
export class RoleService {
async assignRolesToUser(userId, roleNames, adminToken) {
// Get role definitions
const roles = await Promise.all(
roleNames.map(async (roleName) => {
const response = await axios.get(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/roles/${roleName}`,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
return response.data;
})
);
// Assign roles to user
await axios.post(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${userId}/role-mappings/realm`,
roles,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
}
async getUserRoles(userId, adminToken) {
const response = await axios.get(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${userId}/role-mappings`,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
return response.data;
}
}javascript
// In: services/user-service/src/services/role.service.js
export class RoleService {
async assignRolesToUser(userId, roleNames, adminToken) {
// Get role definitions
const roles = await Promise.all(
roleNames.map(async (roleName) => {
const response = await axios.get(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/roles/${roleName}`,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
return response.data;
})
);
// Assign roles to user
await axios.post(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${userId}/role-mappings/realm`,
roles,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
}
async getUserRoles(userId, adminToken) {
const response = await axios.get(
`${process.env.KEYCLOAK_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users/${userId}/role-mappings`,
{ headers: { Authorization: `Bearer ${adminToken}` } }
);
return response.data;
}
}Create Groups for Organizations
为企业创建用户组
bash
undefinedbash
undefinedCreate group for organization
Create group for organization
curl -X POST "http://localhost:8080/admin/realms/lobbi/groups"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_acme", "attributes": { "org_id": ["org_acme"], "org_name": ["ACME Corporation"] } }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_acme", "attributes": { "org_id": ["org_acme"], "org_name": ["ACME Corporation"] } }'
curl -X POST "http://localhost:8080/admin/realms/lobbi/groups"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_acme", "attributes": { "org_id": ["org_acme"], "org_name": ["ACME Corporation"] } }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_acme", "attributes": { "org_id": ["org_acme"], "org_name": ["ACME Corporation"] } }'
Add user to group
Add user to group
GROUP_ID="..."
USER_ID="..."
curl -X PUT "http://localhost:8080/admin/realms/lobbi/users/$USER_ID/groups/$GROUP_ID"
-H "Authorization: Bearer $TOKEN"
-H "Authorization: Bearer $TOKEN"
undefinedGROUP_ID="..."
USER_ID="..."
curl -X PUT "http://localhost:8080/admin/realms/lobbi/users/$USER_ID/groups/$GROUP_ID"
-H "Authorization: Bearer $TOKEN"
-H "Authorization: Bearer $TOKEN"
undefinedTheme Deployment
主题部署
Theme Structure
主题结构
keycloak-alpha/
└── services/
└── keycloak-service/
└── themes/
├── lobbi-base/
│ ├── login/
│ │ ├── theme.properties
│ │ ├── login.ftl
│ │ ├── register.ftl
│ │ └── resources/
│ │ ├── css/
│ │ │ └── login.css
│ │ ├── img/
│ │ │ └── logo.png
│ │ └── js/
│ │ └── login.js
│ ├── account/
│ └── email/
└── org-acme/
├── login/
│ ├── theme.properties (parent=lobbi-base)
│ └── resources/
│ ├── css/
│ │ └── custom.css
│ └── img/
│ └── org-logo.pngkeycloak-alpha/
└── services/
└── keycloak-service/
└── themes/
├── lobbi-base/
│ ├── login/
│ │ ├── theme.properties
│ │ ├── login.ftl
│ │ ├── register.ftl
│ │ └── resources/
│ │ ├── css/
│ │ │ └── login.css
│ │ ├── img/
│ │ │ └── logo.png
│ │ └── js/
│ │ └── login.js
│ ├── account/
│ └── email/
└── org-acme/
├── login/
│ ├── theme.properties (parent=lobbi-base)
│ └── resources/
│ ├── css/
│ │ └── custom.css
│ └── img/
│ └── org-logo.pngTheme Properties
主题配置
properties
undefinedproperties
undefinedthemes/lobbi-base/login/theme.properties
themes/lobbi-base/login/theme.properties
parent=keycloak
import=common/keycloak
styles=css/login.css
parent=keycloak
import=common/keycloak
styles=css/login.css
Localization
Localization
locales=en,es,fr
locales=en,es,fr
Custom properties
Custom properties
logo.url=/resources/img/logo.png
undefinedlogo.url=/resources/img/logo.png
undefinedDeploy Theme
部署主题
bash
undefinedbash
undefinedCopy theme to Keycloak
Copy theme to Keycloak
docker cp themes/lobbi-base keycloak:/opt/keycloak/themes/
docker cp themes/lobbi-base keycloak:/opt/keycloak/themes/
Restart Keycloak to pick up new theme
Restart Keycloak to pick up new theme
docker restart keycloak
docker restart keycloak
Set theme for realm
Set theme for realm
curl -X PUT "http://localhost:8080/admin/realms/lobbi"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "loginTheme": "lobbi-base", "accountTheme": "lobbi-base", "emailTheme": "lobbi-base" }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "loginTheme": "lobbi-base", "accountTheme": "lobbi-base", "emailTheme": "lobbi-base" }'
undefinedcurl -X PUT "http://localhost:8080/admin/realms/lobbi"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "loginTheme": "lobbi-base", "accountTheme": "lobbi-base", "emailTheme": "lobbi-base" }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "loginTheme": "lobbi-base", "accountTheme": "lobbi-base", "emailTheme": "lobbi-base" }'
undefinedTheme Customization per Organization
按企业自定义主题
javascript
// In: services/keycloak-service/src/middleware/theme-mapper.js
export const themeMapper = {
org_acme: 'org-acme',
org_beta: 'org-beta',
default: 'lobbi-base'
};
export function getThemeForOrg(orgId) {
return themeMapper[orgId] || themeMapper.default;
}
// Apply theme dynamically via query parameter
// URL: http://localhost:8080/realms/lobbi/protocol/openid-connect/auth?kc_theme=org-acmejavascript
// In: services/keycloak-service/src/middleware/theme-mapper.js
export const themeMapper = {
org_acme: 'org-acme',
org_beta: 'org-beta',
default: 'lobbi-base'
};
export function getThemeForOrg(orgId) {
return themeMapper[orgId] || themeMapper.default;
}
// Apply theme dynamically via query parameter
// URL: http://localhost:8080/realms/lobbi/protocol/openid-connect/auth?kc_theme=org-acmeToken Configuration and Session Management
令牌配置与会话管理
Token Lifetime Configuration
令牌有效期配置
bash
undefinedbash
undefinedUpdate token lifespans
Update token lifespans
curl -X PUT "http://localhost:8080/admin/realms/lobbi"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "accessTokenLifespan": 300, "accessTokenLifespanForImplicitFlow": 900, "ssoSessionIdleTimeout": 1800, "ssoSessionMaxLifespan": 36000, "offlineSessionIdleTimeout": 2592000, "accessCodeLifespan": 60, "accessCodeLifespanUserAction": 300 }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "accessTokenLifespan": 300, "accessTokenLifespanForImplicitFlow": 900, "ssoSessionIdleTimeout": 1800, "ssoSessionMaxLifespan": 36000, "offlineSessionIdleTimeout": 2592000, "accessCodeLifespan": 60, "accessCodeLifespanUserAction": 300 }'
undefinedcurl -X PUT "http://localhost:8080/admin/realms/lobbi"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "accessTokenLifespan": 300, "accessTokenLifespanForImplicitFlow": 900, "ssoSessionIdleTimeout": 1800, "ssoSessionMaxLifespan": 36000, "offlineSessionIdleTimeout": 2592000, "accessCodeLifespan": 60, "accessCodeLifespanUserAction": 300 }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "accessTokenLifespan": 300, "accessTokenLifespanForImplicitFlow": 900, "ssoSessionIdleTimeout": 1800, "ssoSessionMaxLifespan": 36000, "offlineSessionIdleTimeout": 2592000, "accessCodeLifespan": 60, "accessCodeLifespanUserAction": 300 }'
undefinedCustom Token Mapper for org_id
org_id的自定义令牌映射
bash
undefinedbash
undefinedCreate protocol mapper to include org_id in token
Create protocol mapper to include org_id in token
CLIENT_UUID="..."
curl -X POST "http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/protocol-mappers/models"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_id", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "config": { "user.attribute": "org_id", "claim.name": "org_id", "jsonType.label": "String", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_id", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "config": { "user.attribute": "org_id", "claim.name": "org_id", "jsonType.label": "String", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }'
undefinedCLIENT_UUID="..."
curl -X POST "http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/protocol-mappers/models"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_id", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "config": { "user.attribute": "org_id", "claim.name": "org_id", "jsonType.label": "String", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org_id", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "config": { "user.attribute": "org_id", "claim.name": "org_id", "jsonType.label": "String", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }'
undefinedVerify Token Claims
校验令牌声明
javascript
// In: services/api-gateway/src/middleware/auth.middleware.js
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const client = jwksClient({
jwksUri: `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/certs`
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
export async function verifyToken(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
jwt.verify(token, getKey, {
audience: 'account',
issuer: `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}`,
algorithms: ['RS256']
}, (err, decoded) => {
if (err) {
return res.status(401).json({ error: 'Invalid token' });
}
// Verify org_id claim exists
if (!decoded.org_id) {
return res.status(403).json({ error: 'Missing org_id claim' });
}
req.user = decoded;
next();
});
}javascript
// In: services/api-gateway/src/middleware/auth.middleware.js
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const client = jwksClient({
jwksUri: `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/certs`
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
export async function verifyToken(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
jwt.verify(token, getKey, {
audience: 'account',
issuer: `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}`,
algorithms: ['RS256']
}, (err, decoded) => {
if (err) {
return res.status(401).json({ error: 'Invalid token' });
}
// Verify org_id claim exists
if (!decoded.org_id) {
return res.status(403).json({ error: 'Missing org_id claim' });
}
req.user = decoded;
next();
});
}Common Troubleshooting
常见问题排查
Issue: CORS Errors
问题:CORS错误
Solution: Configure Web Origins in client settings
bash
curl -X PUT "http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webOrigins": ["+"]
}'解决方案: 在客户端设置中配置Web Origins
bash
curl -X PUT "http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webOrigins": ["+"]
}'Issue: Invalid Redirect URI
问题:重定向URI无效
Solution: Verify redirect URIs match exactly
javascript
// Check configured URIs
const redirectUris = [
'http://localhost:3000/auth/callback',
'https://app.lobbi.com/auth/callback'
];
// Ensure callback URL matches
const callbackUrl = `${window.location.origin}/auth/callback`;解决方案: 校验重定向URI完全匹配
javascript
// Check configured URIs
const redirectUris = [
'http://localhost:3000/auth/callback',
'https://app.lobbi.com/auth/callback'
];
// Ensure callback URL matches
const callbackUrl = `${window.location.origin}/auth/callback`;Issue: Token Not Including Custom Claims
问题:令牌未包含自定义声明
Solution: Verify protocol mapper is added to client scopes
bash
undefined解决方案: 校验客户端范围已添加协议映射
bash
undefinedCheck client scopes
Check client scopes
curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/default-client-scopes"
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/default-client-scopes"
curl -H "Authorization: Bearer $TOKEN"
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/default-client-scopes"
"http://localhost:8080/admin/realms/lobbi/clients/$CLIENT_UUID/default-client-scopes"
Add custom scope with org_id mapper
Add custom scope with org_id mapper
curl -X POST "http://localhost:8080/admin/realms/lobbi/client-scopes"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org-scope", "protocol": "openid-connect", "protocolMappers": [...] }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org-scope", "protocol": "openid-connect", "protocolMappers": [...] }'
undefinedcurl -X POST "http://localhost:8080/admin/realms/lobbi/client-scopes"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org-scope", "protocol": "openid-connect", "protocolMappers": [...] }'
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{ "name": "org-scope", "protocol": "openid-connect", "protocolMappers": [...] }'
undefinedIssue: User Cannot Login
问题:用户无法登录
Checklist:
- Verify user is enabled:
GET /admin/realms/lobbi/users/{id} - Check email is verified (if required)
- Verify password is not temporary
- Check realm login settings allow email login
- Review authentication flow configuration
检查清单:
- 验证用户已启用:
GET /admin/realms/lobbi/users/{id} - 检查邮箱是否已验证(如果要求验证)
- 验证密码不是临时密码
- 检查realm登录设置是否允许邮箱登录
- 检查身份认证流配置
Issue: Theme Not Applied
问题:主题未生效
Solution:
- Verify theme is copied to Keycloak themes directory
- Restart Keycloak container
- Clear browser cache
- Check theme name in realm settings matches theme directory name
解决方案:
- 验证主题已复制到Keycloak的themes目录
- 重启Keycloak容器
- 清除浏览器缓存
- 检查realm设置中的主题名称与主题目录名称一致
File Locations in keycloak-alpha
keycloak-alpha中的文件位置
| Path | Purpose |
|---|---|
| Keycloak configuration and themes |
| User management API |
| Token verification |
| Frontend Keycloak config |
| Authentication hooks |
| 路径 | 用途 |
|---|---|
| Keycloak配置与主题 |
| 用户管理API |
| 令牌校验 |
| 前端Keycloak配置 |
| 身份认证钩子 |
Best Practices
最佳实践
- Always use PKCE for Authorization Code Flow in SPAs
- Never expose client secrets in frontend code
- Validate org_id claim in every backend request
- Use short access token lifespans (5-15 minutes)
- Implement refresh token rotation for enhanced security
- Enable brute force protection in realm settings
- Use groups for organization-level permissions
- Version control themes in the repository
- Test theme changes in development realm first
- Monitor token usage and session metrics
- 单页应用的授权码流必须使用PKCE
- 永远不要在前端代码中暴露客户端密钥
- 每个后端请求都要校验org_id声明
- 使用较短的访问令牌有效期(5-15分钟)
- 实现刷新令牌轮换提升安全性
- 在realm设置中启用暴力破解防护
- 使用用户组管理企业级权限
- 主题文件纳入版本控制
- 先在开发环境realm测试主题变更
- 监控令牌使用和会话指标