Skip to content

fix: resolve transient admin roles against a single effective role expansion - #52098

Closed
waterWang wants to merge 1 commit into
keycloak:mainfrom
waterWang:fix/52042-admin-api-latency
Closed

waterWang wants to merge 1 commit into
keycloak:mainfrom
waterWang:fix/52042-admin-api-latency

Conversation

@waterWang

Copy link
Copy Markdown
Contributor

Closes #52042

Problem

After upgrading Keycloak 26.7.0 → 26.7.2 (CVE-2026-16102 fix, #51499),
Admin REST API latency grew 30-40x on deployments with many realms/users
(e.g. /admin/realms/{realm}/users 17ms → 508ms; /clients 12.9ms → 546ms).

JFR shows 95%+ of CPU samples in KeycloakModelUtils.removeTransientAdminRoles
→ UserAdapter.hasRole → KeycloakModelUtils.searchFor (HashMap/HashSet frames).

Root cause

KeycloakIdentity.addRolesAsAttributes() calls removeTransientAdminRoles()
for the realm access and each resource access. For every role in the token
it calls user.hasRole(adminRole), and each hasRole() re-walks the whole
role graph (direct role mappings, every composite parent through
KeycloakModelUtils.searchFor, plus group roles). A token with N roles against
a user with a deep role hierarchy costs O(N × M) work, per request, per node.

Fix

Resolve the user's effective role set (direct + composite + group-inherited
roles) exactly once via RoleUtils.getDeepUserRoleMappings(user) — a single
batched composite expansion (O(M)) — then replace each user.hasRole(adminRole)
with an O(1) Set.contains(adminRole.getId()) lookup (O(N)).

Behavior is unchanged: getDeepUserRoleMappings() produces the same effective
role set that hasRole() tests against. This is the same strategy
AdminRoleTokenPostProcessor.removeTransientAdminRoles already uses.

Test

KeycloakModelUtilsTest gains two tests exercising removeTransientAdminRoles:

  • client roles: user holds manage-users on realm-management; token claims
    manage-users + impersonation + some-role → only impersonation is stripped
  • realm-level roles: user holds admin; token claims admin +
    create-realm + view-realm → only admin kept

Signed-off-by: waterWang waterWang@users.noreply.github.com

…h expansion

removeTransientAdminRoles() called user.hasRole() for every role in the access
token. Each user.hasRole() re-walks the whole role graph (direct mappings +
every composite parent via KeycloakModelUtils.searchFor), so a token with N
roles against a user with a deep role graph costs O(N × M) work on every
KeycloakIdentity construction. Under many realms / many client roles per token
this saturates the CPU (95%+ of samples in UserAdapter.hasRole /
KeycloakModelUtils.searchFor) and makes Admin REST API latency grow 30-40x.

Resolve the user's effective role set (direct + composite + group-inherited)
exactly once via RoleUtils.getDeepUserMappings() — one batched composite
expansion (O(M)) plus O(1) set lookups per role. Behavior is unchanged:
getDeepUserRoleMappings() yields the same effective role set that hasRole()
tests for. This is the same strategy AdminRoleTokenPostProcessor already uses.

Signed-off-by: waterWang <waterWang@users.noreply.github.com>
@waterWang
waterWang requested a review from a team as a code owner August 26, 2026 16:40
Copilot AI balanced review requested due to automatic review settings August 26, 2026 16:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes transient admin-role filtering by replacing repeated role-graph searches with effective-role ID lookups.

Changes:

  • Expands effective user roles and filters by role ID.
  • Adds client- and realm-role unit tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
KeycloakModelUtils.java Optimizes transient admin-role filtering.
KeycloakModelUtilsTest.java Tests client and realm filtering behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +1208 to +1210
Set<String> effectiveRoleIds = RoleUtils.getDeepUserRoleMappings(user).stream()
.map(RoleModel::getId)
.collect(Collectors.toSet());
@sschu

sschu commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@waterWang #52042 is closed now since the reporter said the issue is fixed with KC26.7.3. I will go ahead and close this issue, feel free to comment here or reopen if you feel anything is missing,

@sschu sschu closed this Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Severe latency increase in Admin REST API due to addRolesAsAttributes overhead (26.7.2)

4 participants