If you've wired up a Spring Boot resource server behind Keycloak and your @PreAuthorize("hasRole('ADMIN')") is silently returning 403 for a user you can see has the ADMIN role in the admin console, you're not going crazy. This happens on basically every first Keycloak + Spring Security integration, and the reason is boring once you see it: Spring Security's default JWT converter has no idea Keycloak exists. I've hit this on every Keycloak-secured backend I've built over the past couple of years — most recently on a multi-service Spring Boot 3 platform where I ended up owning the IAM layer, including custom Keycloak SPI authenticators. Same wiring problem, every time, until I finally pulled the fix out into a small library instead of copy-pasting it again. The actual token shape Spring Security's JwtAuthenticationConverter expects authorities to come from a scope (or scp) claim — a flat, space-separated string, OAuth2-style. That's the generic spec-y default. Keycloak doesn't do that. A Keycloak access token puts roles here instead: { "realm_access": { "roles": ["offline_access", "uma_authorization"] }, "resource_access": { "my-client": { "roles": ["ADMIN", "VIEWER"] } } } Enter fullscreen mode Exit fullscreen mode Realm roles under realm_access.roles, client roles nested under resource_access..roles. Spring Security never looks there. So JwtAuthenticationConverter runs, finds no scope claim, produces zero GrantedAuthority objects, and every hasRole(...) check quietly fails — no error, no stack trace, just "access denied" for a user who is very clearly in the right group. The fix, the manual version You write your own Converter that reads both claims, flattens them into SimpleGrantedAuthority with a ROLE_ prefix (Spring Security's convention), and registers it on the JwtAuthenticationConverter: public class KeycloakRealmRoleConverter implements Converter> { @Override public Collection convert(Jwt jwt) { Map realmAccess = jwt.getClaim("realm_access"); List roles = realmAccess != null ? (List) realmAccess.getOrDefault("roles", List.of()) : List.of(); return roles.stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) .collect(Collectors.toList()); } } Enter fullscreen mode Exit fullscreen mode Wire it into a JwtAuthenticationConverter, register that as a bean, and roles start working. That's maybe twenty lines and it's fine for a single service. The annoying part is you'll write this exact converter again on the next service, and the one after that, usually slightly differently each time (some people prefix with ROLE_, some don't, some forget client roles exist at all and only handle realm roles). Where I landed After the third time writing basically the same class, I pulled it into spring-keycloak-toolkit — an auto-configuration that registers the converter for you, handles both realm and client roles, and also fixes the second thing that bites you right after the first one: Spring Security's default 401/403 responses are empty bodies, which is useless if anything downstream (a frontend, an API gateway, a support engineer reading logs) needs to know why a request got rejected. The library adds RFC 7807 problem+json bodies for both cases instead. What it deliberately does not do is touch your SecurityFilterChain or guess which endpoints should be public. I thought about auto-wiring that too, and decided against it — guessing your endpoint matchers wrong and failing silently is worse than making you write four lines of config yourself. A library that gets your security posture subtly wrong is more dangerous than one that does less. If you're wiring up Keycloak with Spring Security and hitting this, the manual converter above will get you unstuck in five minutes. If you're doing it more than once, the library's on Maven via JitPack — link's in the repo.
Your Keycloak roles aren't working in Spring Security. Here's the actual reason.
Full Article
Original Source
Read the full article at Dev →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.