rgoussu@goussu: ~/library/java/java-security
~/library/java/java-security cat oauth2-and-oidc.md

OAuth2 & OIDC in Java

# The OAuth2/OIDC roles — resource server, client, authorization server — and how Spring, Quarkus and Micronaut implement each.

Conceptsaved 2026-08-09 #java#security#oauth#oidc#jwt#spring#quarkus#micronaut

Overview

OAuth2 assigns roles — authorization server, resource server, client — and a Java service usually plays exactly one of them. The protocol theory lives in the Security theme; the Java question is which dependency implements which role, what the token-validation machinery actually checks, and how to test any of it without a production IdP. All three major frameworks converge on the same shape: a resource server validates JWTs against the IdP's published keys; a client drives the auth-code redirect dance; almost nobody writes their own authorization server.

Key points

  • Resource server (an API accepting bearer tokens): Spring Security oauth2-resource-server, Quarkus quarkus-oidc (service mode) or quarkus-smallrye-jwt (MicroProfile JWT), Micronaut micronaut-security-jwt. Configuration is essentially one property: the issuer URL, from which JWKS discovery follows.
  • Client / login (an app obtaining tokens): spring-security-oauth2-client, Quarkus quarkus-oidc in web-app mode, micronaut-security-oauth2. All implement the OIDC auth-code flow with session-backed login.
  • Authorization server: Spring Authorization Server exists and is solid, but the pragmatic default is delegating to Keycloak (or a SaaS IdP — Auth0, Entra ID, Okta). Running an AS is an operational commitment, not a library choice.
  • Flows that matter: Authorization Code + PKCE (all user-facing clients, SPAs and mobile included) and Client Credentials (service-to-service). Dead flows: implicit and resource-owner password — removed from OAuth 2.1; do not build new code on them.
  • JWT validation = signature against a JWKS key + iss + aud + exp/nbf with bounded clock skew. Every framework does this for you; your job is configuring issuer and audience correctly.
  • Opaque tokens trade self-containment for revocability and need RFC 7662 introspection on every request (supported by all three stacks) — fine behind a gateway, costly at high fan-out.
  • Propagation: pass the incoming bearer token to downstream calls (or exchange it); each stack has client-side filters for this rather than hand-built headers.
  • Testing: never test against a live IdP — use mock OIDC servers or Keycloak in Testcontainers; Quarkus Dev Services gives you a dev-mode Keycloak for free.

Details

Resource server — validating JWTs

The 90% case. Per framework:

Dependency Key config
Spring spring-boot-starter-oauth2-resource-server spring.security.oauth2.resourceserver.jwt.issuer-uri
Quarkus quarkus-oidc (application-type=service) or quarkus-smallrye-jwt quarkus.oidc.auth-server-url, quarkus.oidc.client-id / mp.jwt.verify.issuer, mp.jwt.verify.publickey.location
Micronaut micronaut-security-jwt micronaut.security.token.jwt.signatures.jwks.<name>.url

Mechanics common to all three: on startup (or first request) the framework fetches /.well-known/openid-configuration from the issuer, discovers the jwks_uri, caches the JWKS and refreshes it on unknown-kid misses — which is how IdP key rotation works without redeploys. Validation then checks signature, iss (must equal the configured issuer), aud (set it — a token minted for another API must not be accepted), and exp/nbf with a small tolerated clock skew (default ~60 s in most stacks). Claims map to the security context: Spring exposes a Jwt principal and maps scopes to SCOPE_x authorities (customisable via a converter); Quarkus injects JsonWebToken; Micronaut exposes Authentication.getAttributes(). Role claims rarely match defaults out of the box — Keycloak's realm_access.roles, for instance, needs a mapper in every stack.

Client and login

For server-side web apps, the framework handles the redirect to the IdP, state/PKCE, the code-for-token exchange and session establishment. Spring: register providers under spring.security.oauth2.client.registration.*; Quarkus: quarkus-oidc with application-type=web-app (cookie-backed session, token refresh handled); Micronaut: micronaut-security-oauth2 with micronaut.security.oauth2.clients.*. For service-to-service calls, the client-credentials grant is likewise config-driven: Spring's OAuth2AuthorizedClientManager behind WebClient/RestClient, Quarkus quarkus-oidc-client (with a REST-client filter that attaches and refreshes the token), Micronaut's client-credentials support with client-credentials.enabled per client.

Running an authorization server

Spring Authorization Server is the one credible embed-it-yourself option on the JVM (successor to the deprecated Spring Security OAuth AS) — reasonable for product-embedded identity or exotic token requirements. Otherwise delegate: Keycloak is the pragmatic default (self-hosted, OIDC-certified, admin API, realm import for reproducible config), SaaS IdPs where operating one is not your business. The decision is organisational more than technical: token minting concentrates risk, and patching/HA for an IdP is real work.

Propagation between services

Passing identity along a call chain: simplest is forwarding the incoming bearer token — Spring servlet stacks propagate via a RestClient/WebClient interceptor reading the SecurityContext; Quarkus has quarkus-rest-client-oidc-token-propagation; Micronaut sets micronaut.security.token.propagation.enabled with a service-selector regex. The forwarded token's aud should cover the downstream service — where it doesn't, RFC 8693 token exchange (supported by Keycloak) is the correct, less-travelled road.

Testing

  • Mock OIDC servers: Spring's spring-security-test fakes the token layer entirely (jwt() post-processors / @WithMockUser); quarkus-test-oidc-server (WireMock-based) and libraries like mock-oauth2-server (Nav) stand up a fake issuer with a real JWKS.
  • Keycloak Testcontainers: dasniko/testcontainers-keycloak boots a real Keycloak with a realm-import JSON; the test obtains real tokens via the token endpoint — highest fidelity, slower.
  • Quarkus Dev Services for Keycloak: with quarkus-oidc present and no auth-server-url configured, dev/test mode auto-starts a Keycloak container and wires it in; @QuarkusTest plus KeycloakTestClient covers integration tests with no setup.

Examples

# Spring Boot resource server — application.yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://idp.example.com/realms/main
          audiences: my-api
# Quarkus resource server — application.properties
quarkus.oidc.auth-server-url=https://idp.example.com/realms/main
quarkus.oidc.client-id=my-api
quarkus.oidc.token.audience=my-api
# dev mode: omit auth-server-url and Dev Services boots Keycloak for you

Related