Проверка непрозрачных токенов и обмен токенами
Проверяйте непрозрачные токены через механизм интроспекции и передавайте сведения о пользователе с помощью обмена токенами.
«Проверка непрозрачных токенов и обмен токенами» — бесплатный урок Spring Boot 4 Complete Guide на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Spring Boot 4 Complete Guide, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Spring Boot 4 Complete Guide содержит 4 уроков всего.
Части этого урока еще не переведены и отображаются на английском.
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/falseplus claims likesub,scope, andexp.
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
audtargets 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.audienceorresource— 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-serviceToken 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-tokenproperties plusoauth2ResourceServer(o -> o.opaqueToken(...)); customize via a customOpaqueTokenIntrospector. - 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
subwhile setting the correctaud. - In Spring, the
TokenExchangeOAuth2AuthorizedClientProviderplus an authorized-client interceptor makes this automatic for downstream calls.
Изучай Java с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 21
- Уроки
- 84
Часто задаваемые вопросы
Урок «Проверка непрозрачных токенов и обмен токенами» бесплатный?
Да — полный текст урока «Проверка непрозрачных токенов и обмен токенами» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Spring Boot 4 Complete Guide, подпишись на CoddyKit PRO. Курс Spring Boot 4 Complete Guide содержит 4 уроков всего.
Чему я научусь в уроке «Проверка непрозрачных токенов и обмен токенами»?
Проверяйте непрозрачные токены через механизм интроспекции и передавайте сведения о пользователе с помощью обмена токенами. Ты практикуешь Spring Boot 4 Complete Guide с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Spring Boot 4 Complete Guide?
Предыдущий опыт не требуется. Spring Boot 4 Complete Guide на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Проверка непрозрачных токенов и обмен токенами»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Spring Boot 4 Complete Guide?
Да. Каждый урок Spring Boot 4 Complete Guide включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Проверка JWT и утверждений на сервере ресурсов
- Клиент OAuth2 и поток кода авторизации
- Безопасность методов с SpEL и пользовательскими средствами голосования
- Проверка непрозрачных токенов и обмен токенами