Spring Boot 4 Complete Guide · Lezione

Ispezione dei token opachi e scambio dei token

Convalidi i token opachi tramite introspezione e propaghi l’identità con lo scambio dei token.

Lezione 4 di 413 passaggi

Ispezione dei token opachi e scambio dei token è una lezione Spring Boot 4 Complete Guide gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Spring Boot 4 Complete Guide, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Spring Boot 4 Complete Guide include 4 lezioni in totale.

Parti di questa lezione non sono ancora state tradotte e vengono mostrate in inglese.

Why Opaque Tokens Need Introspection

A JWT carries its claims inside the token, so a resource server can validate it offline by checking the signature. An opaque token is just a random reference string — it contains no claims and no signature you can verify.

  • To learn who the token belongs to and whether it is still valid, the resource server must ask the authorization server.
  • That call is the OAuth2 Token Introspection endpoint, defined by RFC 7662.
  • The authorization server replies with active: true/false plus claims like sub, scope, and exp.

Opaque tokens trade offline speed for instant revocation: revoke at the AS and the very next introspection returns active: false.

The Introspection Response

Per RFC 7662, the introspection endpoint accepts the token as a form parameter and returns a JSON document. The single mandatory field is active.

A typical successful response looks like the JSON below. If the token is unknown, expired, or revoked, the server returns simply {"active": false} and Spring rejects the request.

{
  "active": true,
  "sub": "user-123",
  "scope": "orders:read orders:write",
  "client_id": "web-app",
  "username": "alice",
  "token_type": "Bearer",
  "exp": 1735689600,
  "iat": 1735686000
}

Configuring Opaque Token Validation

In a Spring Boot 4 resource server you enable opaque-token validation with a single properties block. Spring Security autoconfigures an OpaqueTokenIntrospector from these values.

  • introspection-uri — the AS endpoint that implements RFC 7662.
  • client-id / client-secret — the resource server's own credentials to authenticate the introspection call.
# application.yml
spring:
  security:
    oauth2:
      resourceserver:
        opaque-token:
          introspection-uri: https://auth.example.com/oauth2/introspect
          client-id: resource-server
          client-secret: ${INTROSPECT_SECRET}

Enabling It in the Security Filter Chain

The properties only take effect once you opt into opaqueToken() on the resource server DSL. This wires the autoconfigured introspector into the bearer-token filter.

Notice the contrast with JWT mode: with opaque tokens there is no local key set — every request triggers a network call to the AS.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/**").authenticated()
                .anyRequest().permitAll())
            .oauth2ResourceServer(oauth2 -> oauth2
                .opaqueToken(Customizer.withDefaults()));
        return http.build();
    }
}

A Custom OpaqueTokenIntrospector

You can define your own OpaqueTokenIntrospector bean to control the HTTP client, caching, or response parsing. The built-in SpringOpaqueTokenIntrospector uses a RestClient under the hood.

Returning a bean overrides the autoconfigured one, so you keep the same opaqueToken() DSL while customizing behavior.

@Bean
OpaqueTokenIntrospector introspector(
        @Value("${spring.security.oauth2.resourceserver.opaque-token.introspection-uri}") String uri,
        @Value("${spring.security.oauth2.resourceserver.opaque-token.client-id}") String clientId,
        @Value("${spring.security.oauth2.resourceserver.opaque-token.client-secret}") String secret) {

    RestClient restClient = RestClient.builder()
        .requestInterceptor(new BasicAuthenticationInterceptor(clientId, secret))
        .build();

    return new SpringOpaqueTokenIntrospector(uri, restClient);
}

Mapping Scopes to Authorities

By default the introspected scope string becomes a set of SCOPE_ authorities. To customize that mapping — for example adding role authorities — wrap the default introspector and transform its principal.

The OAuth2AuthenticatedPrincipal exposes the raw introspection claims via getAttributes(), which you remap into your own authorities.

public class CustomAuthoritiesIntrospector implements OpaqueTokenIntrospector {

    private final OpaqueTokenIntrospector delegate;

    public CustomAuthoritiesIntrospector(OpaqueTokenIntrospector delegate) {
        this.delegate = delegate;
    }

    @Override
    public OAuth2AuthenticatedPrincipal introspect(String token) {
        OAuth2AuthenticatedPrincipal principal = delegate.introspect(token);
        List<GrantedAuthority> authorities = new ArrayList<>(principal.getAuthorities());
        String scope = principal.getAttribute("scope");
        if (scope != null && scope.contains("orders:write")) {
            authorities.add(new SimpleGrantedAuthority("ROLE_ORDER_MANAGER"));
        }
        return new DefaultOAuth2AuthenticatedPrincipal(
            principal.getName(), principal.getAttributes(), authorities);
    }
}

Caching Introspection Results

Because every request hits the AS, opaque tokens can become a latency and load bottleneck. A safe optimization is to cache the introspection result for a short window — never longer than the token's remaining lifetime.

  • Cache on the token value as the key.
  • Honor exp: an entry must expire at or before the token's own expiry.
  • Keep the TTL small (seconds to a minute) so revocation stays nearly immediate.

This small helper shows the core TTL math you would apply inside a caching introspector.

import java.time.Instant;

public class IntrospectionCache {

    public static long ttlSeconds(long tokenExpEpoch, long maxCacheSeconds) {
        long now = Instant.now().getEpochSecond();
        long remaining = tokenExpEpoch - now;
        if (remaining <= 0) {
            return 0; // already expired, do not cache
        }
        return Math.min(remaining, maxCacheSeconds);
    }

    public static void main(String[] args) {
        long exp = Instant.now().getEpochSecond() + 300; // expires in 5 min
        System.out.println("Cache for " + ttlSeconds(exp, 30) + "s");
        System.out.println("Cache for " + ttlSeconds(Instant.now().getEpochSecond() - 10, 30) + "s");
    }
}

The Identity Propagation Problem

Now suppose your API gateway received a token for the user, and it must call a downstream service. Forwarding the original token leaks audience and over-grants scope. Re-using the gateway's own client credentials loses the user's identity entirely.

OAuth2 Token Exchange (RFC 8693) solves this: the gateway presents the incoming token to the AS and asks for a new token scoped to the downstream service, while preserving the original subject.

  • Identity is propagated, not impersonated blindly.
  • The new token's aud targets exactly the downstream service.
  • Scopes can be narrowed for least privilege.

The Token Exchange Request

A token exchange is a POST to the AS token endpoint with grant_type=urn:ietf:params:oauth:grant-type:token-exchange. The key parameters are the subject_token (the incoming token) and its subject_token_type.

  • subject_token — the token representing the user.
  • requested_token_type — usually an access token.
  • audience or resource — the downstream service the new token is for.
POST /oauth2/token HTTP/1.1
Host: auth.example.com
Authorization: Basic <gateway-client-credentials>
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<incoming-user-token>
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&requested_token_type=urn:ietf:params:oauth:token-type:access_token
&audience=orders-service

Token Exchange in Spring Security 6.3+

Spring Security ships a TokenExchangeOAuth2AuthorizedClientProvider so a resource server can exchange the current token before calling downstream. Register the provider and a matching client registration with the token-exchange grant type.

The client registration uses authorization-grant-type set to the RFC 8693 URN.

@Bean
OAuth2AuthorizedClientManager authorizedClientManager(
        ClientRegistrationRepository registrations,
        OAuth2AuthorizedClientRepository clients) {

    OAuth2AuthorizedClientProvider provider =
        OAuth2AuthorizedClientProviderBuilder.builder()
            .provider(new TokenExchangeOAuth2AuthorizedClientProvider())
            .build();

    DefaultOAuth2AuthorizedClientManager manager =
        new DefaultOAuth2AuthorizedClientManager(registrations, clients);
    manager.setAuthorizedClientProvider(provider);
    return manager;
}

Calling Downstream With the Exchanged Token

With the manager in place, configure a RestClient (or WebClient) that attaches the exchanged token automatically. The interceptor resolves the authorized client named orders-service, triggering the exchange when needed.

The downstream service then validates a token whose sub is still the original user and whose aud is itself — clean, least-privilege identity propagation.

@Bean
RestClient ordersRestClient(OAuth2AuthorizedClientManager manager) {
    OAuth2ClientHttpRequestInterceptor interceptor =
        new OAuth2ClientHttpRequestInterceptor(manager);
    interceptor.setClientRegistrationIdResolver(request -> "orders-service");

    return RestClient.builder()
        .baseUrl("https://orders.internal")
        .requestInterceptor(interceptor)
        .build();
}

Quick Check: Choosing the Right Mechanism

Test your understanding of when to use introspection versus token exchange.

Recap

You learned how to validate reference tokens and propagate identity across services:

  • Opaque tokens carry no claims, so the resource server calls the AS introspection endpoint (RFC 7662); the only required field is active.
  • Enable it with the opaque-token properties plus oauth2ResourceServer(o -> o.opaqueToken(...)); customize via a custom OpaqueTokenIntrospector.
  • Map scopes to authorities and cache results briefly — never beyond the token's exp — to limit AS load while keeping revocation fast.
  • Token exchange (RFC 8693) swaps an incoming token for a downstream-scoped one, preserving the original sub while setting the correct aud.
  • In Spring, the TokenExchangeOAuth2AuthorizedClientProvider plus an authorized-client interceptor makes this automatic for downstream calls.
Gratis per iniziare

Impara Java con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
21
Lezioni
84

Domande Frequenti

La lezione «Ispezione dei token opachi e scambio dei token» è gratuita?

Sì — il testo completo di «Ispezione dei token opachi e scambio dei token» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Spring Boot 4 Complete Guide, passa a CoddyKit PRO. Il corso Spring Boot 4 Complete Guide include 4 lezioni in totale.

Cosa imparerò in «Ispezione dei token opachi e scambio dei token»?

Convalidi i token opachi tramite introspezione e propaghi l’identità con lo scambio dei token. Eserciti Spring Boot 4 Complete Guide con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Spring Boot 4 Complete Guide?

Non è richiesta alcuna esperienza precedente. Spring Boot 4 Complete Guide su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.

Quanto tempo richiede la lezione «Ispezione dei token opachi e scambio dei token»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Spring Boot 4 Complete Guide?

Sì. Ogni lezione Spring Boot 4 Complete Guide include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Validazione JWT del resource server e claims
  2. Client OAuth2 e flusso Authorization Code
  3. Sicurezza dei metodi con SpEL e voter personalizzati
  4. Ispezione dei token opachi e scambio dei token
← Torna a Spring Boot 4 Complete Guide