Keycloak, Spring Authorization Server & Microservices Security
A single microservice validating its own users doesn't scale to ten microservices. This chapter is centralizing authentication with Keycloak (or your own Spring Authorization Server) across a whole system.
Learning objectives
- Beginner: Explain why centralizing authentication matters once you have more than one microservice.
- Intermediate: Configure a Spring Boot resource server to validate tokens issued by Keycloak.
- Advanced: Reason about when to run your own Spring Authorization Server instead of an off-the-shelf identity provider like Keycloak.
🌱 BEGINNER STORY
Imagine a country of many government departments (your microservices: Accounts, Loans, Cards). Instead of EACH department building and maintaining its own separate ID-verification office (each service implementing its own login/password logic — expensive, inconsistent, and a nightmare to keep in sync), the country builds ONE central Passport Office (Keycloak, or a Spring Authorization Server you build yourself). Every citizen gets ONE passport (a JWT) from this office. Any department can independently verify the passport's authenticity (checking the government seal / cryptographic signature) without needing to call back to the Passport Office for every single check-in — they trust the seal.
-
Single Source of Truth — user credentials, MFA policies, and social login integrations live in ONE place, not duplicated across N services.
-
Consistent security policy — password strength rules, session policies, and token lifetimes are enforced uniformly.
-
Reduced attack surface — fewer places storing/handling raw credentials means fewer places that can leak them.
-
Standardization — every resource server validates tokens the SAME way (via JWKS), regardless of which team wrote which microservice.
Figure: A central Auth Server (Keycloak) issues tokens once; every downstream microservice independently validates them.
Keycloak is a popular, mature, open-source Identity and Access Management (IAM) server implementing OAuth2, OIDC, and SAML out of the box. Key Keycloak concepts:
| Concept | What It Means |
|---|---|
| Realm | An isolated namespace/tenant containing its own users, roles, clients, and configuration — e.g., a separate realm per environment or per customer organization. |
| Client | An application registered within a realm that can request tokens (e.g., your Angular app, or a backend service). |
| Client Scope / Role Mapping | Configuration controlling what claims (roles, custom attributes) get embedded into issued tokens for a given client. |
| User Federation | Keycloak's ability to connect to an external identity store (LDAP, Active Directory) instead of only its own internal user database. |
# application.properties
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8180/realms/eazybank @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .requestMatchers("/api/public/**").permitAll() .anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(keycloakRoleConverter()))); return http.build(); } // Keycloak nests roles inside a custom claim structure (realm_access.roles), // so a custom converter is needed to map them into Spring's GrantedAuthority: @Bean public Converter<Jwt, AbstractAuthenticationToken> keycloakRoleConverter() { return jwt -> { Map<String, Object> realmAccess = jwt.getClaim("realm_access"); List roles = (List) realmAccess.getOrDefault("roles", List.of()); List authorities = roles.stream() .map(r -> new SimpleGrantedAuthority("ROLE_" + r)) .collect(Collectors.toList()); return new JwtAuthenticationToken(jwt, authorities); }; }
Setting issuer-uri is enough for Spring Boot to auto-discover the JWKS endpoint and validate signatures automatically — but Keycloak's default JWT claim structure for roles (nested under realm_access.roles) doesn't automatically map to Spring's expected authorities, which is why a custom JwtAuthenticationConverter is almost always needed in real Keycloak integrations.
Keycloak can also issue OPAQUE tokens (random strings instead of JWTs) for certain client configurations. In that case, the resource server must be configured for introspection instead of local JWT validation:
spring.security.oauth2.resourceserver.opaque-token.introspection-uri=,[object Object]
spring.security.oauth2.resourceserver.opaque-token.client-id=eazybank-introspect spring.security.oauth2.resourceserver.opaque-token.client-secret=***
A critical real-world nuance: token CUSTOMIZATION logic written for JWTs (e.g., a JwtAuthenticationConverter mapping claims to authorities) does NOT run for opaque tokens at all, since there's no local JWT to customize — opaque token role-mapping must instead read from the introspection RESPONSE structure (which is different from the JWT claim structure), commonly under a 'scope' field rather than 'realm_access.roles'. This is a real gotcha that trips up teams switching between token formats.
Because authentication is centralized, adding capabilities like MFA (OTP apps, SMS) or social login (Google, GitHub) is configured ONCE in Keycloak's admin console, and every downstream microservice benefits automatically — none of them need any code changes, since they only ever see the resulting validated token, regardless of how the user actually authenticated at the Keycloak layer.
For teams that want full control (or can't adopt a third-party IAM product), Spring Authorization Server is an official Spring project letting you build a standards-compliant OAuth2/OIDC Authorization Server directly in Java/Spring Boot.
@Bean
public RegisteredClientRepository registeredClientRepository() { RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("eazybank-client") .clientSecret("{noop}secret") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .redirectUri("http://localhost:4200/callback") .scope("accounts.read") .scope(OidcScopes.OPENID) .clientSettings(ClientSettings.builder().requireProofKey(true).build()) // PKCE .build(); return new InMemoryRegisteredClientRepository(client); } @Bean public OAuth2TokenCustomizer jwtTokenCustomizer() { return context -> { Authentication principal = context.getPrincipal(); Set roles = principal.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toSet()); context.getClaims().claim("roles", roles); }; }
A frequent real-world disclaimer worth remembering: Spring Authorization Server is a comparatively newer project within the Spring ecosystem, and its APIs have evolved meaningfully between versions — production teams should pin exact tested versions and consult the current official documentation before assuming behavior carries over unchanged between releases.
⚠ Common pitfalls in Keycloak/Auth-Server setups
Forgetting a JwtAuthenticationConverter means every authenticated user has ZERO granted authorities from Spring Security's point of view (even though Keycloak correctly assigned roles) — @PreAuthorize/hasRole checks silently deny everyone. Mixing JWT-customization logic with an opaque-token resource server configuration silently does nothing — the customizer only ever runs at the Authorization Server when ISSUING a JWT, not on the resource server when receiving an opaque token; role-mapping for opaque tokens must be done differently (from the introspection response). Running multiple resource servers (Accounts, Loans, Cards microservices) that all trust the same Authorization Server but forget to validate the token's 'aud' (intended audience) risk a token issued for one service being replayed against another. Not rotating/expiring Keycloak's realm signing keys periodically, or not handling key ROTATION gracefully on the resource-server side (JWKS caching should respect cache-control / refresh on unknown 'kid'), can cause outages exactly when keys are rotated for a legitimate security reason.
Want a visual for this concept?
Generate a diagram tailored to “Keycloak, Spring Authorization Server & Microservices Security” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →