Environment
My environment is:Spring Security 7.0.5 和 Spring Authorization Server 7.0.5
Reproduce
Spring Authorization Server is used as the authentication server, with Opaque Tokens being used by default. Currently, since we need to support dynamic client registration, I enabled this feature by modifying the code configuration. After enabling dynamic client registration, the system throws an error when started: Spring Security only supports JWTs or Opaque Tokens, not both at the same time..
I thought it was because my code configuration wasn’t compatible with the new version of Spring Authorization Server. So I tried using a custom authenticationManagerResolver to resolve the issue, but I still encountered errors. The error message was as follows:
Caused by: java.lang.IllegalStateException: If an authenticationManagerResolver() is configured, then it takes precedence over any jwt() or opaqueToken() configuration.
at org.springframework.util.Assert.state(Assert.java:80) ~[spring-core-7.0.7.jar:7.0.7]
at org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer.validateConfiguration(OAuth2ResourceServerConfigurer.java:324) ~[spring-security-config-7.0.5.jar:7.0.5]
at org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer.init(OAuth2ResourceServerConfigurer.java:273) ~[spring-security-config-7.0.5.jar:7.0.5]
at org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer.init(OAuth2ResourceServerConfigurer.java:155) ~[spring-security-config-7.0.5.jar:7.0.5]
at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.init(AbstractConfiguredSecurityBuilder.java:371) ~[spring-security-config-7.0.5.jar:7.0.5]
at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:333) ~[spring-security-config-7.0.5.jar:7.0.5]
at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38) ~[spring-security-config-7.0.5.jar:7.0.5]
at cn.herodotus.cloud.authentication.autoconfigure.AuthorizationAutoConfiguration.authorizationServerSecurityFilterChain(AuthorizationAutoConfiguration.java:103) ~[authentication-spring-boot-starter-4.0.6.2.jar:4.0.6.2]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:155) ~[spring-beans-7.0.7.jar:7.0.7]
... 24 common frames omitted
This issue occurs whether it’s OAuth2 client dynamic registration or OIDC client dynamic registration.
Analyze
Because my own code includes module encapsulation and extensions, making it difficult to understand, I’ll use the following example code to illustrate:
@Bean
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
http
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.clientRegistrationEndpoint(Customizer.withDefaults())
)
.authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated()
)
.oauth2ResourceServer(configurer -> configurer.opaqueToken(Customizer.withDefaults()))
//.oauth2ResourceServer(configurer -> configurer.authenticationManagerResolver(new CustomAuthenticationManagerResolver))
return http.build();
}
After debugging the code, I found that if we follow the previous example approach—first configuring .oauth2AuthorizationServer(), followed by .oauth2ResourceServer(configurer -> configurer.opaqueToken(Customizer.withDefaults()))—then during the execution of http.build(), OAuth2AuthorizationServerConfigurer.init() will execute before OAuth2ResourceServerConfigurer.init().
Since client dynamic registration is enabled, the default JWT configuration will be set in the .oauth2AuthorizationServer() method within the OAuth2AuthorizationServerConfigurer.init() code, as shown below:
if (getConfigurer(OAuth2ClientRegistrationEndpointConfigurer.class) != null) {
httpSecurity
// Accept access tokens for Client Registration
.oauth2ResourceServer((oauth2ResourceServer) -> oauth2ResourceServer.jwt(Customizer.withDefaults()));
}
OidcConfigurer oidcConfigurer = getConfigurer(OidcConfigurer.class);
if (oidcConfigurer != null) {
if (oidcConfigurer.getConfigurer(OidcUserInfoEndpointConfigurer.class) != null
|| oidcConfigurer.getConfigurer(OidcClientRegistrationEndpointConfigurer.class) != null) {
httpSecurity
// Accept access tokens for User Info and/or Client Registration
.oauth2ResourceServer(
(oauth2ResourceServer) -> oauth2ResourceServer.jwt(Customizer.withDefaults()));
}
}
The process will then enter the OAuth2ResourceServerConfigurer.init() method, where the first step is validateConfiguration(). Since client dynamic registration is already enabled by setting .oauth2ResourceServer((oauth2ResourceServer) -> oauth2ResourceServer.jwt(Customizer.withDefaults()));, configuring .oauth2ResourceServer(configurer -> configurer.opaqueToken(Customizer.withDefaults())) as shown in the previous example or setting .oauth2ResourceServer(configurer -> configurer.authenticationManagerResolver(new CustomAuthenticationManagerResolver)) will both cause the validateConfiguration() method to fail validation.
Resolve
I found ISSUE #16406 and, following the bypass method mentioned in it, placed the configuration of .oauth2Resourserver() before .oauth2AuthorizationServer(). The example code is as follows:
@Bean
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
http
.oauth2ResourceServer(configurer -> configurer.opaqueToken(Customizer.withDefaults()))
//.oauth2ResourceServer(configurer -> configurer.authenticationManagerResolver(new CustomAuthenticationManagerResolver))
.oauth2AuthorizationServer((authorizationServer) ->
authorizationServer
.clientRegistrationEndpoint(Customizer.withDefaults())
)
.authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated()
)
return http.build();
}
This approach can indeed provide temporary solutions to problems.
Debug code found that configuring .oauth2Resourserver() before .oauth2AuthorizationServer() and executing http. build(); At this time, it will become OAuth2ResourceServerConfigurer.init() to be executed before OAuth2AuthorizationServerConfigurer.init().
Whether using OpaqueToken or a custom authenticationManagerResolver, executing OAuth2ResourceServerConfigurer.init() first will pass the validation of the validateConfiguration() method. Execute the code OAuth2AuthorizationServerConfigurer.init() again, even if JWT is configured, it has already bypassed the validation of validateConfiguration()
While this approach allows the code to run successfully, it leads to situations where either “opaque” and JWT are configured simultaneously, or JWT and a custom AuthenticationManagerResolver are configured together. In reality, this violates the constraints specified by validateConfiguration().
Thought process
OAuth2ResourceServerConfigurer.init() 中validateConfiguration() 代码如下:
private void validateConfiguration() {
if (this.authenticationManagerResolver == null) {
Assert.state(this.jwtConfigurer != null || this.opaqueTokenConfigurer != null,
"Jwt and Opaque Token are the only supported formats for bearer tokens "
+ "in Spring Security and neither was found. Make sure to configure JWT "
+ "via http.oauth2ResourceServer().jwt() or Opaque Tokens via "
+ "http.oauth2ResourceServer().opaqueToken().");
Assert.state(this.jwtConfigurer == null || this.opaqueTokenConfigurer == null,
"Spring Security only supports JWTs or Opaque Tokens, not both at the " + "same time.");
}
else {
Assert.state(this.jwtConfigurer == null && this.opaqueTokenConfigurer == null,
"If an authenticationManagerResolver() is configured, then it takes "
+ "precedence over any jwt() or opaqueToken() configuration.");
}
}
Since it is not allowed to set both jwtConfigurator and opaqueTokenConfigurator at the same time, can we consider modifying it as follows: if opaqueTokenConfigurator is configured, jwtConfigurator will be assigned a null value; if jwtConfigurator is configured, opaqueTokenConfigurator will be assigned a null value; if authenticationManagerResolver is configured, opaqueTokenConfigurator and jwtConfigurator will be assigned a null value at the same time
Environment
My environment is:Spring Security 7.0.5 和 Spring Authorization Server 7.0.5
Reproduce
Spring Authorization Server is used as the authentication server, with Opaque Tokens being used by default. Currently, since we need to support dynamic client registration, I enabled this feature by modifying the code configuration. After enabling dynamic client registration, the system throws an error when started:
Spring Security only supports JWTs or Opaque Tokens, not both at the same time..I thought it was because my code configuration wasn’t compatible with the new version of Spring Authorization Server. So I tried using a custom
authenticationManagerResolverto resolve the issue, but I still encountered errors. The error message was as follows:This issue occurs whether it’s OAuth2 client dynamic registration or OIDC client dynamic registration.
Analyze
Because my own code includes module encapsulation and extensions, making it difficult to understand, I’ll use the following example code to illustrate:
After debugging the code, I found that if we follow the previous example approach—first configuring
.oauth2AuthorizationServer(), followed by.oauth2ResourceServer(configurer -> configurer.opaqueToken(Customizer.withDefaults()))—then during the execution ofhttp.build(),OAuth2AuthorizationServerConfigurer.init()will execute beforeOAuth2ResourceServerConfigurer.init().Since client dynamic registration is enabled, the default JWT configuration will be set in the
.oauth2AuthorizationServer()method within theOAuth2AuthorizationServerConfigurer.init()code, as shown below:The process will then enter the
OAuth2ResourceServerConfigurer.init()method, where the first step isvalidateConfiguration(). Since client dynamic registration is already enabled by setting.oauth2ResourceServer((oauth2ResourceServer) -> oauth2ResourceServer.jwt(Customizer.withDefaults()));, configuring.oauth2ResourceServer(configurer -> configurer.opaqueToken(Customizer.withDefaults()))as shown in the previous example or setting.oauth2ResourceServer(configurer -> configurer.authenticationManagerResolver(new CustomAuthenticationManagerResolver))will both cause thevalidateConfiguration()method to fail validation.Resolve
I found ISSUE #16406 and, following the bypass method mentioned in it, placed the configuration of
.oauth2Resourserver()before.oauth2AuthorizationServer(). The example code is as follows:This approach can indeed provide temporary solutions to problems.
Debug code found that configuring
.oauth2Resourserver()before.oauth2AuthorizationServer()and executinghttp. build();At this time, it will becomeOAuth2ResourceServerConfigurer.init()to be executed beforeOAuth2AuthorizationServerConfigurer.init().Whether using OpaqueToken or a custom
authenticationManagerResolver, executingOAuth2ResourceServerConfigurer.init()first will pass the validation of thevalidateConfiguration()method. Execute the codeOAuth2AuthorizationServerConfigurer.init()again, even if JWT is configured, it has already bypassed the validation ofvalidateConfiguration()While this approach allows the code to run successfully, it leads to situations where either “opaque” and JWT are configured simultaneously, or JWT and a custom AuthenticationManagerResolver are configured together. In reality, this violates the constraints specified by
validateConfiguration().Thought process
OAuth2ResourceServerConfigurer.init()中validateConfiguration()代码如下:Since it is not allowed to set both
jwtConfiguratorandopaqueTokenConfiguratorat the same time, can we consider modifying it as follows: ifopaqueTokenConfiguratoris configured,jwtConfiguratorwill be assigned a null value; ifjwtConfiguratoris configured,opaqueTokenConfiguratorwill be assigned a null value; ifauthenticationManagerResolveris configured,opaqueTokenConfiguratorandjwtConfiguratorwill be assigned a null value at the same time