对于最新的稳定版本,请使用 Spring Security 6.4.3! |
OAuth 2.0 资源服务器不透明令牌
Introspection 的最小依赖项
如 JWT 的最小依赖项中所述,大多数 Resource Server 支持都收集在spring-security-oauth2-resource-server
.
但是,除非自定义OpaqueTokenIntrospector
,则资源服务器将回退到 NimbusOpaqueTokenIntrospector。
这意味着spring-security-oauth2-resource-server
和oauth2-oidc-sdk
是必需的,以便拥有支持不透明 Bearer Token 的工作最小 Resource Server。
请参考spring-security-oauth2-resource-server
为了确定oauth2-oidc-sdk
.
Introspection 的最小配置
通常,可以通过授权服务器托管的 OAuth 2.0 Introspection Endpoint 验证不透明令牌。 当需要吊销时,这可能很方便。
使用 Spring Boot 时,将应用程序配置为使用内省的资源服务器包括两个基本步骤。 首先,包括所需的依赖项,其次,指示自省终端节点详细信息。
指定 Authorization Server
要指定自省端点的位置,只需执行以下作:
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
哪里idp.example.com/introspect
是由授权服务器托管的 Introspection 终端节点,client-id
和client-secret
是命中该终端节点所需的凭证。
Resource Server 将使用这些属性进一步自我配置并随后验证传入的 JWT。
使用内省时,授权服务器的话就是法律。 如果授权服务器响应令牌有效,则令牌有效。 |
就是这样!
运行时预期
应用程序启动后,Resource Server 将尝试处理任何包含Authorization: Bearer
页眉:
GET / HTTP/1.1
Authorization: Bearer some-token-value # Resource Server will process this
只要指定了此方案,Resource Server 就会尝试根据 Bearer Token 规范处理请求。
给定一个不透明的令牌,Resource Server 将
-
使用提供的凭证和令牌查询提供的自省终端节点
-
检查响应中是否有
{ 'active' : true }
属性 -
将每个范围映射到带有前缀的颁发机构
SCOPE_
结果Authentication#getPrincipal
默认情况下,是 Spring SecurityOAuth2AuthenticatedPrincipal
object 和Authentication#getName
映射到令牌的sub
属性(如果存在)。
从这里,您可能希望跳转到:
不透明令牌身份验证的工作原理
接下来,让我们看看 Spring Security 用于在基于 servlet 的应用程序中支持不透明令牌身份验证的架构组件,就像我们刚刚看到的一样。
让我们来看看OpaqueTokenAuthenticationProvider
在 Spring Security 中工作。
该图详细介绍了AuthenticationManager
在 Reading the Bearer Token works 中的数字中。

OpaqueTokenAuthenticationProvider
用法身份验证
Filter
从 Reading the Bearer Token 传递一个BearerTokenAuthenticationToken
到AuthenticationManager
它由ProviderManager
.
这
ProviderManager
配置为使用 AuthenticationProvider 类型的OpaqueTokenAuthenticationProvider
.
OpaqueTokenAuthenticationProvider
内省不透明令牌并使用OpaqueTokenIntrospector
.
身份验证成功后,Authentication
返回的 类型为BearerTokenAuthentication
并且有一个主体,该主体是OAuth2AuthenticatedPrincipal
由配置的OpaqueTokenIntrospector
.
最终,返回的BearerTokenAuthentication
将在SecurityContextHolder
通过身份验证Filter
.
身份验证后查找属性
令牌通过身份验证后,BearerTokenAuthentication
在SecurityContext
.
这意味着它可用于@Controller
方法时使用@EnableWebMvc
在您的配置中:
-
Java
-
Kotlin
@GetMapping("/foo")
public String foo(BearerTokenAuthentication authentication) {
return authentication.getTokenAttributes().get("sub") + " is the subject";
}
@GetMapping("/foo")
fun foo(authentication: BearerTokenAuthentication): String {
return authentication.tokenAttributes["sub"].toString() + " is the subject"
}
因为BearerTokenAuthentication
持有OAuth2AuthenticatedPrincipal
,这也意味着它也可用于控制器方法:
-
Java
-
Kotlin
@GetMapping("/foo")
public String foo(@AuthenticationPrincipal OAuth2AuthenticatedPrincipal principal) {
return principal.getAttribute("sub") + " is the subject";
}
@GetMapping("/foo")
fun foo(@AuthenticationPrincipal principal: OAuth2AuthenticatedPrincipal): String {
return principal.getAttribute<Any>("sub").toString() + " is the subject"
}
通过 SPEL 查找属性
当然,这也意味着可以通过 SPEL 访问属性。
例如,如果使用@EnableGlobalMethodSecurity
这样您就可以使用@PreAuthorize
annotations 中,您可以执行以下作:
-
Java
-
Kotlin
@PreAuthorize("principal?.attributes['sub'] == 'foo'")
public String forFoosEyesOnly() {
return "foo";
}
@PreAuthorize("principal?.attributes['sub'] == 'foo'")
fun forFoosEyesOnly(): String {
return "foo"
}
覆盖或替换引导自动配置
有两个@Bean
的 Spring Boot 代表 Resource Server 生成的。
第一个是SecurityFilterChain
,将应用程序配置为资源服务器。
当使用 Opaque Token 时,这个SecurityFilterChain
看来:
-
Java
-
Kotlin
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
opaqueToken { }
}
}
return http.build()
}
如果应用程序没有公开SecurityFilterChain
bean,则 Spring Boot 将公开上述默认的 bean。
替换它就像在应用程序中公开 bean 一样简单:
-
Java
-
Kotlin
import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
@Configuration
@EnableWebSecurity
public class MyCustomSecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/messages/**").access(hasScope("message:read"))
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaqueToken -> opaqueToken
.introspector(myIntrospector())
)
);
return http.build();
}
}
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
@Configuration
@EnableWebSecurity
class MyCustomSecurityConfiguration {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize("/messages/**", hasScope("SCOPE_message:read"))
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
opaqueToken {
introspector = myIntrospector()
}
}
}
return http.build()
}
}
The above requires the scope of message:read
for any URL that starts with /messages/
.
Methods on the oauth2ResourceServer
DSL will also override or replace auto configuration.
For example, the second @Bean
Spring Boot creates is an OpaqueTokenIntrospector
, which decodes String
tokens into validated instances of OAuth2AuthenticatedPrincipal
:
-
Java
-
Kotlin
@Bean
public OpaqueTokenIntrospector introspector() {
return new NimbusOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret);
}
@Bean
fun introspector(): OpaqueTokenIntrospector {
return NimbusOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret)
}
If the application doesn’t expose an OpaqueTokenIntrospector
bean, then Spring Boot will expose the above default one.
And its configuration can be overridden using introspectionUri()
and introspectionClientCredentials()
or replaced using introspector()
.
If the application doesn’t expose an OpaqueTokenAuthenticationConverter
bean, then spring-security will build BearerTokenAuthentication
.
Or, if you’re not using Spring Boot at all, then all of these components - the filter chain, an OpaqueTokenIntrospector
and an OpaqueTokenAuthenticationConverter
can be specified in XML.
The filter chain is specified like so:
Default Opaque Token Configuration
-
Xml
<http>
<intercept-uri pattern="/**" access="authenticated"/>
<oauth2-resource-server>
<opaque-token introspector-ref="opaqueTokenIntrospector"
authentication-converter-ref="opaqueTokenAuthenticationConverter"/>
</oauth2-resource-server>
</http>
And the OpaqueTokenIntrospector
like so:
Opaque Token Introspector
-
Xml
<bean id="opaqueTokenIntrospector"
class="org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector">
<constructor-arg value="${spring.security.oauth2.resourceserver.opaquetoken.introspection_uri}"/>
<constructor-arg value="${spring.security.oauth2.resourceserver.opaquetoken.client_id}"/>
<constructor-arg value="${spring.security.oauth2.resourceserver.opaquetoken.client_secret}"/>
</bean>
And the OpaqueTokenAuthenticationConverter
like so:
Opaque Token Authentication Converter
-
Xml
<bean id="opaqueTokenAuthenticationConverter"
class="com.example.CustomOpaqueTokenAuthenticationConverter"/>
Using introspectionUri()
An authorization server’s Introspection Uri can be configured as a configuration property or it can be supplied in the DSL:
Introspection URI Configuration
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableWebSecurity
public class DirectlyConfiguredIntrospectionUri {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaqueToken -> opaqueToken
.introspectionUri("https://idp.example.com/introspect")
.introspectionClientCredentials("client", "secret")
)
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
class DirectlyConfiguredIntrospectionUri {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
opaqueToken {
introspectionUri = "https://idp.example.com/introspect"
introspectionClientCredentials("client", "secret")
}
}
}
return http.build()
}
}
<bean id="opaqueTokenIntrospector"
class="org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector">
<constructor-arg value="https://idp.example.com/introspect"/>
<constructor-arg value="client"/>
<constructor-arg value="secret"/>
</bean>
Using introspectionUri()
takes precedence over any configuration property.
Using introspector()
More powerful than introspectionUri()
is introspector()
, which will completely replace any Boot auto configuration of OpaqueTokenIntrospector
:
Introspector Configuration
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableWebSecurity
public class DirectlyConfiguredIntrospector {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaqueToken -> opaqueToken
.introspector(myCustomIntrospector())
)
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
class DirectlyConfiguredIntrospector {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
opaqueToken {
introspector = myCustomIntrospector()
}
}
}
return http.build()
}
}
<http>
<intercept-uri pattern="/**" access="authenticated"/>
<oauth2-resource-server>
<opaque-token introspector-ref="myCustomIntrospector"/>
</oauth2-resource-server>
</http>
This is handy when deeper configuration, like authority mapping, JWT revocation, or request timeouts, is necessary.
Exposing a OpaqueTokenIntrospector
@Bean
Or, exposing a OpaqueTokenIntrospector
@Bean
has the same effect as introspector()
:
@Bean
public OpaqueTokenIntrospector introspector() {
return new NimbusOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret);
}
Configuring Authorization
An OAuth 2.0 Introspection endpoint will typically return a scope
attribute, indicating the scopes (or authorities) it’s been granted, for example:
{ …, "scope" : "messages contacts"}
When this is the case, Resource Server will attempt to coerce these scopes into a list of granted authorities, prefixing each scope with the string "SCOPE_".
This means that to protect an endpoint or method with a scope derived from an Opaque Token, the corresponding expressions should include this prefix:
Authorization Opaque Token Configuration
-
Java
-
Kotlin
-
Xml
import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
@Configuration
@EnableWebSecurity
public class MappedAuthorities {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/contacts/**").access(hasScope("contacts"))
.requestMatchers("/messages/**").access(hasScope("messages"))
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
return http.build();
}
}
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope
@Configuration
@EnableWebSecurity
class MappedAuthorities {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize("/contacts/**", hasScope("contacts"))
authorize("/messages/**", hasScope("messages"))
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
opaqueToken { }
}
}
return http.build()
}
}
<http>
<intercept-uri pattern="/contacts/**" access="hasAuthority('SCOPE_contacts')"/>
<intercept-uri pattern="/messages/**" access="hasAuthority('SCOPE_messages')"/>
<oauth2-resource-server>
<opaque-token introspector-ref="opaqueTokenIntrospector"/>
</oauth2-resource-server>
</http>
Or similarly with method security:
-
Java
-
Kotlin
@PreAuthorize("hasAuthority('SCOPE_messages')")
public List<Message> getMessages(...) {}
@PreAuthorize("hasAuthority('SCOPE_messages')")
fun getMessages(): List<Message?> {}
Extracting Authorities Manually
By default, Opaque Token support will extract the scope claim from an introspection response and parse it into individual GrantedAuthority
instances.
For example, if the introspection response were:
{
"active" : true,
"scope" : "message:read message:write"
}
Then Resource Server would generate an Authentication
with two authorities, one for message:read
and the other for message:write
.
This can, of course, be customized using a custom OpaqueTokenIntrospector
that takes a look at the attribute set and converts in its own way:
-
Java
-
Kotlin
public class CustomAuthoritiesOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret");
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal principal = this.delegate.introspect(token);
return new DefaultOAuth2AuthenticatedPrincipal(
principal.getName(), principal.getAttributes(), extractAuthorities(principal));
}
private Collection<GrantedAuthority> extractAuthorities(OAuth2AuthenticatedPrincipal principal) {
List<String> scopes = principal.getAttribute(OAuth2IntrospectionClaimNames.SCOPE);
return scopes.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}
class CustomAuthoritiesOpaqueTokenIntrospector : OpaqueTokenIntrospector {
private val delegate: OpaqueTokenIntrospector = NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret")
override fun introspect(token: String): OAuth2AuthenticatedPrincipal {
val principal: OAuth2AuthenticatedPrincipal = delegate.introspect(token)
return DefaultOAuth2AuthenticatedPrincipal(
principal.name, principal.attributes, extractAuthorities(principal))
}
private fun extractAuthorities(principal: OAuth2AuthenticatedPrincipal): Collection<GrantedAuthority> {
val scopes: List<String> = principal.getAttribute(OAuth2IntrospectionClaimNames.SCOPE)
return scopes
.map { SimpleGrantedAuthority(it) }
}
}
Thereafter, this custom introspector can be configured simply by exposing it as a @Bean
:
-
Java
-
Kotlin
@Bean
public OpaqueTokenIntrospector introspector() {
return new CustomAuthoritiesOpaqueTokenIntrospector();
}
@Bean
fun introspector(): OpaqueTokenIntrospector {
return CustomAuthoritiesOpaqueTokenIntrospector()
}
Configuring Timeouts
By default, Resource Server uses connection and socket timeouts of 30 seconds each for coordinating with the authorization server.
This may be too short in some scenarios.
Further, it doesn’t take into account more sophisticated patterns like back-off and discovery.
To adjust the way in which Resource Server connects to the authorization server, NimbusOpaqueTokenIntrospector
accepts an instance of RestOperations
:
-
Java
-
Kotlin
@Bean
public OpaqueTokenIntrospector introspector(RestTemplateBuilder builder, OAuth2ResourceServerProperties properties) {
RestOperations rest = builder
.basicAuthentication(properties.getOpaquetoken().getClientId(), properties.getOpaquetoken().getClientSecret())
.setConnectTimeout(Duration.ofSeconds(60))
.setReadTimeout(Duration.ofSeconds(60))
.build();
return new NimbusOpaqueTokenIntrospector(introspectionUri, rest);
}
@Bean
fun introspector(builder: RestTemplateBuilder, properties: OAuth2ResourceServerProperties): OpaqueTokenIntrospector? {
val rest: RestOperations = builder
.basicAuthentication(properties.opaquetoken.clientId, properties.opaquetoken.clientSecret)
.setConnectTimeout(Duration.ofSeconds(60))
.setReadTimeout(Duration.ofSeconds(60))
.build()
return NimbusOpaqueTokenIntrospector(introspectionUri, rest)
}
Using Introspection with JWTs
A common question is whether or not introspection is compatible with JWTs.
Spring Security’s Opaque Token support has been designed to not care about the format of the token — it will gladly pass any token to the introspection endpoint provided.
So, let’s say that you’ve got a requirement that requires you to check with the authorization server on each request, in case the JWT has been revoked.
Even though you are using the JWT format for the token, your validation method is introspection, meaning you’d want to do:
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.org/introspection
client-id: client
client-secret: secret
In this case, the resulting Authentication
would be BearerTokenAuthentication
.
Any attributes in the corresponding OAuth2AuthenticatedPrincipal
would be whatever was returned by the introspection endpoint.
But, let’s say that, oddly enough, the introspection endpoint only returns whether or not the token is active.
Now what?
In this case, you can create a custom OpaqueTokenIntrospector
that still hits the endpoint, but then updates the returned principal to have the JWTs claims as the attributes:
-
Java
-
Kotlin
public class JwtOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret");
private JwtDecoder jwtDecoder = new NimbusJwtDecoder(new ParseOnlyJWTProcessor());
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal principal = this.delegate.introspect(token);
try {
Jwt jwt = this.jwtDecoder.decode(token);
return new DefaultOAuth2AuthenticatedPrincipal(jwt.getClaims(), NO_AUTHORITIES);
} catch (JwtException ex) {
throw new OAuth2IntrospectionException(ex);
}
}
private static class ParseOnlyJWTProcessor extends DefaultJWTProcessor<SecurityContext> {
JWTClaimsSet process(SignedJWT jwt, SecurityContext context)
throws JOSEException {
return jwt.getJWTClaimsSet();
}
}
}
class JwtOpaqueTokenIntrospector : OpaqueTokenIntrospector {
private val delegate: OpaqueTokenIntrospector = NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret")
private val jwtDecoder: JwtDecoder = NimbusJwtDecoder(ParseOnlyJWTProcessor())
override fun introspect(token: String): OAuth2AuthenticatedPrincipal {
val principal = delegate.introspect(token)
return try {
val jwt: Jwt = jwtDecoder.decode(token)
DefaultOAuth2AuthenticatedPrincipal(jwt.claims, NO_AUTHORITIES)
} catch (ex: JwtException) {
throw OAuth2IntrospectionException(ex.message)
}
}
private class ParseOnlyJWTProcessor : DefaultJWTProcessor<SecurityContext>() {
override fun process(jwt: SignedJWT, context: SecurityContext): JWTClaimsSet {
return jwt.jwtClaimsSet
}
}
}
Thereafter, this custom introspector can be configured simply by exposing it as a @Bean
:
-
Java
-
Kotlin
@Bean
public OpaqueTokenIntrospector introspector() {
return new JwtOpaqueTokenIntrospector();
}
@Bean
fun introspector(): OpaqueTokenIntrospector {
return JwtOpaqueTokenIntrospector()
}
Calling a /userinfo
Endpoint
Generally speaking, a Resource Server doesn’t care about the underlying user, but instead about the authorities that have been granted.
That said, at times it can be valuable to tie the authorization statement back to a user.
If an application is also using spring-security-oauth2-client
, having set up the appropriate ClientRegistrationRepository
, then this is quite simple with a custom OpaqueTokenIntrospector
.
This implementation below does three things:
-
Delegates to the introspection endpoint, to affirm the token’s validity
-
Looks up the appropriate client registration associated with the /userinfo
endpoint
-
Invokes and returns the response from the /userinfo
endpoint
-
Java
-
Kotlin
public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private final OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret");
private final OAuth2UserService oauth2UserService = new DefaultOAuth2UserService();
private final ClientRegistrationRepository repository;
// ... constructor
@Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal authorized = this.delegate.introspect(token);
Instant issuedAt = authorized.getAttribute(ISSUED_AT);
Instant expiresAt = authorized.getAttribute(EXPIRES_AT);
ClientRegistration clientRegistration = this.repository.findByRegistrationId("registration-id");
OAuth2AccessToken token = new OAuth2AccessToken(BEARER, token, issuedAt, expiresAt);
OAuth2UserRequest oauth2UserRequest = new OAuth2UserRequest(clientRegistration, token);
return this.oauth2UserService.loadUser(oauth2UserRequest);
}
}
class UserInfoOpaqueTokenIntrospector : OpaqueTokenIntrospector {
private val delegate: OpaqueTokenIntrospector = NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret")
private val oauth2UserService = DefaultOAuth2UserService()
private val repository: ClientRegistrationRepository? = null
// ... constructor
override fun introspect(token: String): OAuth2AuthenticatedPrincipal {
val authorized = delegate.introspect(token)
val issuedAt: Instant? = authorized.getAttribute(ISSUED_AT)
val expiresAt: Instant? = authorized.getAttribute(EXPIRES_AT)
val clientRegistration: ClientRegistration = repository!!.findByRegistrationId("registration-id")
val accessToken = OAuth2AccessToken(BEARER, token, issuedAt, expiresAt)
val oauth2UserRequest = OAuth2UserRequest(clientRegistration, accessToken)
return oauth2UserService.loadUser(oauth2UserRequest)
}
}
If you aren’t using spring-security-oauth2-client
, it’s still quite simple.
You will simply need to invoke the /userinfo
with your own instance of WebClient
:
-
Java
-
Kotlin
public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private final OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret");
private final WebClient rest = WebClient.create();
@Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal authorized = this.delegate.introspect(token);
return makeUserInfoRequest(authorized);
}
}
class UserInfoOpaqueTokenIntrospector : OpaqueTokenIntrospector {
private val delegate: OpaqueTokenIntrospector = NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret")
private val rest: WebClient = WebClient.create()
override fun introspect(token: String): OAuth2AuthenticatedPrincipal {
val authorized = delegate.introspect(token)
return makeUserInfoRequest(authorized)
}
}
Either way, having created your OpaqueTokenIntrospector
, you should publish it as a @Bean
to override the defaults:
-
Java
-
Kotlin
@Bean
OpaqueTokenIntrospector introspector() {
return new UserInfoOpaqueTokenIntrospector(...);
}
@Bean
fun introspector(): OpaqueTokenIntrospector {
return UserInfoOpaqueTokenIntrospector(...)
}