The problem
We currently facing a very special problem using Spring Security OAuth in conjunction with Keycloak in a container cluster (OKD) in a high regulated and networking constraint environment.
Essentially the problem boils down to the fact, that if an issuer-uri is set for a Spring Security oauth-client, the framework attempts to validate the ISS claim and therefore uses the specified URI as connection base for getting the /.well-known/openid-configuration stuff.
As this is hard to describe I have put together a simple schema to showcase the problem:
Considering the following configuration:
spring:
security:
oauth2:
client:
provider:
internal:
issuer-uri: https://the.platform.io/auth/realms/the-realm
authorization-uri: https://keycloak.cluster.local/auth/realms/the-realm/protocol/openid-connect/auth
token-uri: https://keycloak.cluster.local/auth/realms/the-realm/protocol/openid-connect/token
jwk-set-uri: https://keycloak.cluster.local/auth/realms/the-realm/protocol/openid-connect/certs
user-info-uri: https://keycloak.cluster.local/auth/realms/the-realm/protocol/openid-connect/userinfo
In the given example configuration the framework will attempt to get the OIDC configuration via https://the.platform.io/auth/realms/the-realm which is the outside URI of Keycloak that in our case is not reachable from inside the cluster due to network restrictions.
Yes I might be possible to setup some kind of split horizon DNS, but that makes network setup even more complicated and not well understandable.
The other issue is, that you do not want to do a full HTTP roundtrip to the outside world, if the targeted service (Keycloak) sits nearby in the same subnet.
A lot of tickets out there try to tackle this by having multiple issuer URIs, but all of the stuff I found regarding this solution has been denied or rejected as token validation would get too complicated.
Accidentally, a colleague of mine stumbled upon the following solution: Instead of having multiple issuer URIs, some folks just use some kind of hash value (instead of a URI) to configure within the ISS claim of the oauth provider and the oauth clients, but unfortunately we cannot do this either with Spring Security nor Keycloak.
We also found the following in Quarkus: https://quarkus.io/guides/security-openid-connect-multitenancy#quarkus-oidc_quarkus.oidc.token.issuer
Advice appreciated
At the moment we could work around this by leaving away the issuer-uri setting completely but are unsure about this and an advice from Spring Security team if that should be regarded as problematic would be very cool.
Possible solutions
- A: Provide an separate config property to use for the connection for getting the OIDC configuration
- B: Introduce a new property like
iss-claimthat is just a value to match, and still useissuer-urifor connection - C: You name it, I guess there might be better options 😎
Feedback very appreciated
✌️
Comment From: holgerstolzenberg
Add-On: I forgot to mention that we need to advertise the outside URI in the ISS claim in order for the frontend UI to work running in the client browser.
Comment From: olivierboudet
Hello, it's funny because I had come here to open the exact same issue but you just did it only 3 hours before :laughing:
In my case, I am in the process of removing deprecated Keycloak Adapter by Spring Security OAuth and having same kind of issues. I am using Keycloak with a Frontend URL configured. My frontend uses a public Keycloak URI (https://mysite.com), the token issuer is my frontend URL. But my backend should access to Keycloak with an internal service URI in k8s (http://keycloak:8080).
As @holgerstolzenberg described, token validation is failing on "invalid issuer" error.
My configuration :
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://keycloak:8080/auth/realms/myrealm
client:
registration:
myclient:
client-id: myclient
client-secret: mysecret
authorization-grant-type: client_credentials
provider: myprovider
provider:
myprovider:
issuer-uri: http://keycloak:8080/auth/realms/myrealm
This is the full exception I get :
2022-07-15 11:51:23.953 - - - TRACE 1 --- [nio-8080-exec-5] .o.s.r.w.BearerTokenAuthenticationFilter : Failed to process authentication request
org.springframework.security.oauth2.server.resource.InvalidBearerTokenException: Invalid issuer
at org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver$ResolvingAuthenticationManager.authenticate(JwtIssuerAuthenticationManagerResolver.java:146)
at org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter.doFilterInternal(BearerTokenAuthenticationFilter.java:130)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:769)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)
As Keycloak returns URIs in openid-configuration with the Frontend URL, the desired behavior in my case might be to configure the token validation with the issuer-uri returned by keycloak openid-configuration endpoint instead of the one directly set in yaml configuration.
PS : I am using spring-security 5.6.1 with spring-boot 2.6.2.
Comment From: jgrandja
@holgerstolzenberg Can you provide more details on the specific issue you are having? For example, please provide the stacktrace so I can determine the point in code that is resulting in the error condition.
Comment From: jgrandja
@olivierboudet The stacktrace you provided is coming from JwtIssuerAuthenticationManagerResolver so I suspect this might be a misconfiguration? Please review the reference documentation for configuring JwtIssuerAuthenticationManagerResolver and confirm that you have setup the correct issuers. Can you also provide a sample of your configuration?
Comment From: olivierboudet
Hello @jgrandja, indeed I am using JwtIssuerAuthenticationManagerResolver to manage multi-tenancy.
The issue is exactly as I can't set all issuers in configuration because the FrontendUrl of Keycloak is not reachable from inside the k8s cluster.
Perhaps it would be more clear if I show you the workaround I am using to resolve this issue.
I have a @Configuration class extending WebSecurityConfigurerAdapter with :
protected final KeycloakProperties keycloakProperties;
@Override
protected void configure(HttpSecurity http) throws Exception {
Map<String, AuthenticationManager> authenticationManagers = new HashMap<>();
keycloakProperties.getIssuers().values().forEach(entry -> {
JwtAuthenticationProvider authenticationProvider = new JwtAuthenticationProvider(JwtDecoders.fromOidcIssuerLocation(entry.get("internal-url"), entry.get("frontend-url")));
authenticationProvider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
authenticationManagers.put(entry.get("frontend-url"), authenticationProvider::authenticate);
});
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver(
authenticationManagers::get);
http.authorizeRequests()
.antMatchers("/v3/api-docs").permitAll()
.antMatchers("**").hasAnyRole(role)
.and()
.csrf().disable()
.exceptionHandling()
.accessDeniedHandler(new KeycloakAccessDeniedHandler(objectMapper))
;
http.oauth2ResourceServer(oauth2 ->
oauth2.
authenticationManagerResolver(authenticationManagerResolver)
);
}
KeycloakProperties loads application.yaml properties as following :
keycloak:
issuers:
mysite:
frontend-url: https://localhost/auth/realms/myRealm
internal-url: http://keycloak-service/auth/realms/myRealm
And as JwtDecorders and JwtDecoderProviderConfigurationUtils are private, I must duplicate them to allow to build a decoder which is not using the internal url of keycloak but its frontend url instead :
public final class JwtDecoders {
private JwtDecoders() {
}
@SuppressWarnings("unchecked")
public static <T extends JwtDecoder> T fromOidcIssuerLocation(String oidcIssuerLocation, String frontendUrl) {
Assert.hasText(oidcIssuerLocation, "oidcIssuerLocation cannot be empty");
Map<String, Object> configuration = JwtDecoderProviderConfigurationUtils
.getConfigurationForOidcIssuerLocation(oidcIssuerLocation);
return (T) withProviderConfiguration(configuration, oidcIssuerLocation, frontendUrl);
}
private static JwtDecoder withProviderConfiguration(Map<String, Object> configuration, String issuer, String frontendUrl) {
//JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer);
OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefaultWithIssuer(frontendUrl);
String jwkSetUri = configuration.get("jwks_uri").toString();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
.jwtProcessorCustomizer(JwtDecoderProviderConfigurationUtils::addJWSAlgorithms).build();
jwtDecoder.setJwtValidator(jwtValidator);
return jwtDecoder;
}
}
I did not change anything in JwtDecoderProviderConfigurationUtils. In JwtDecoders I changed two things :
- removed JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer); because I know it will be different (issuer in configuration is ̀http://keycloak-service:8080/auth ̀ but issuer in ̀openid-configuration ̀ is ̀https://localhost/auth ̀ as set in the Keycloak's FrontendUrl)
- I am creating the JwtDecoder with FrontendUrl explicitly in JwtDecoders.withProviderConfiguration : OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefaultWithIssuer(frontendUrl);
These changes allow me to workaround the initial issue, but I think this would not be necessary if Spring Security allows to build the decoder with issuers found in the response of request to .well-known/openid-configuration in addition to those found in configuration.
Comment From: xsreality
We are facing the exact same problem in our setup and interesting to see the issue raised so recently :)
As @olivierboudet mentioned, the problem is with the fromIssuerLocation/fromOidcIssuerLocation method in JwtDecoders class. It takes the issuer input and uses it for both
- Calling the Discovery document (
getConfigurationForIssuerLocation()) and - Matching the same input with the response of the Discovery document (
JwtDecoderProviderConfigurationUtils.validateIssuer()).
This tightly couples the two behaviours on the same input.
Note that the Discovery document returns the same Issuer irrespective of which URI it is called on.
Comment From: jgrandja
@holgerstolzenberg, @olivierboudet, @xsreality
In case you are not aware, I'd like to refer you to 4.3. OpenID Provider Configuration Validation:
The
issuervalue returned MUST be identical to the Issuer URL that was directly used to retrieve the configuration information.
Hence, the reason for JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer) in JwtDecoders as this is implemented to spec.
There are limitations to customizing the flow when using JwtDecoders so we're looking at adding gh-10309, which will allow customizing the underlying RestOperations used when requesting the provider configuration, as well, a custom OAuth2TokenValidator can be provided to overcome the issuer validation issues you all are having.
I'm leaning towards closing this issue as a duplicate as I feel gh-10309 will provide the flexibility you all are looking for.
@holgerstolzenberg, @olivierboudet, @xsreality Can you confirm if my assumptions are correct? And if so, can you please provide further comments in the other issue so we capture all the requirements you need to make things work?
Comment From: holgerstolzenberg
@jgrandja If I understand the mechanics of the referenced issue correctly (without seeing code changes) you are going to introduce a new configuration property that enables us to configure/overwrite the issuer location to be used for ISS claim validation. I guess that might fix our problem.
To be clear, what we need to able to do is to configure a oauth provider whose configured issuer-uri is not necessarily the value provided by the ISS claim.
Comment From: ruckc
We are running into this same issue. Our main issue is while we can do the insane loopback through cloudflare/routing/ingress controllers to a pod sitting in the same local subnet... our development staff, use "localhost" and our spring-boot service can't access keycloak via "localhost"... so we give the issuer-uri as "https://keycloak:8443/auth/realms/whatever", which then means we get "the iss claim is invalid".
I don't really care how we can solve it, but i'm about to have to replace whatever @Bean configures the JWT validation, and copy+paste into our code base whatever all the way down the stacktrace... just so I can set a Host header on the request to pull the ${issuer-uri}/.well-known/openid-configuration.
I get being truthful to the OpenID Provider spec, which is why i'm about to go file an issue wherever that is maintained.
Comment From: jgrandja
@holgerstolzenberg
you are going to introduce a new configuration property that enables us to configure/overwrite the issuer location to be used for ISS claim validation
No, we will not be introducing a new configuration property.
But before I explain further, I re-read your original issue and my previous comment does not apply to your situation. Sorry for the confusion. My previous comment refers to issuer validation during the initialization of a JwtDecoder component. However, your issue occurs when a ClientRegistration is auto-configured via provider configuration lookup.
Regarding your work around:
At the moment we could work around this by leaving away the
issuer-urisetting completely but are unsure about this and an advice from Spring Security team if that should be regarded as problematic would be very cool.
Yes, this is exactly what you would do to bypass issuer validation. This is documented in ClientRegistrations. FYI, the ClientRegistrations utility class has some limitations and we're considering adding a component that provides better flexibility.
Does this answer your question?
My previous comment was referring to issuer validation during the initialization of a JwtDecoder component.
Specifically the configuration:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://keycloak:8080/auth/realms/myrealm
After gh-10309 is implemented, you could provide the following configuration:
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder
.withIssuerLocation("https://the.platform.io/auth/realms/the-realm")
.build();
jwtDecoder.setJwtValidator(
JwtValidators.createDefaultWithIssuer("https://keycloak.cluster.local/auth/realms/the-realm")
);
return jwtDecoder;
}
With the above configuration, the issuer validation is overridden using JwtValidators.createDefaultWithIssuer("https://keycloak.cluster.local/auth/realms/the-realm"), which will give you totally control on how the issuer is validated.
Comment From: holgerstolzenberg
Yes - that looks good to me. The suggested overridden JwtDecoder looks promising.
We can give that a try.
Comment From: olivierboudet
It looks good also for me :+1:
Comment From: sutr90
@olivierboudet I have come up with a bit simpler workaround. We are in the same boat, backend and frontend URLs, not possible to go around the proxies, etc.
Our solution is following:
public class JwtMultiIssuerDecoder implements JwtDecoder {
private final NimbusJwtDecoder internalDecoder;
private final NimbusJwtDecoder publicDecoder;
public JwtMultiIssuerDecoder(String internalUri, String publicUri){
internalDecoder = JwtDecoders.fromIssuerLocation(internalUri);
// This is not an error!!! We need to init the public decoder from the internal URI - the publicUri might not be reachable by this server!
publicDecoder = JwtDecoders.fromIssuerLocation(internalUri);
publicDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(publicUri));
}
@Override
public Jwt decode(String token) throws JwtException {
try {
return internalDecoder.decode(token);
} catch (JwtValidationException e){
return publicDecoder.decode(token);
}
}
}
And then just use it like this in your WebSecurityConfig:
@Bean
public JwtDecoder jwtDecoder() {
return new JwtMultiIssuerDecoder("internal URI", "public URI");
}
This way you do not have to sacrifice the issuer validation, with the cost of validating twice, for the "public" tokens.
Comment From: jgrandja
@holgerstolzenberg, @olivierboudet, @xsreality Closing this in favour of gh-10309
Comment From: xsreality
@jgrandja It is still not clear to me how https://github.com/spring-projects/spring-security/issues/10309 solves the issue.
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder
.withIssuerLocation("https://the.platform.io/auth/realms/the-realm")
.build();
jwtDecoder.setJwtValidator(
JwtValidators.createDefaultWithIssuer("https://keycloak.cluster.local/auth/realms/the-realm")
);
return jwtDecoder;
}
The NimbusJwtDecoder.withIssuerLocation() still uses a function that validates the issuer with this line JwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer); So above code will end up calling the Issuer on the public URL to fetch the configuration and try to validate the issuer which will succeed since the public URL is being used. Later the validation will happen again with the internal URL which will fail as it doesn't match the issuer claim.
I tried above code with 6.1.0 and can confirm above described behaviour. Here's my code:
SecurityConfigurer:
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange().anyExchange().authenticated()
.and()
.oauth2ResourceServer(spec ->
spec.authenticationManagerResolver(authenticationManagerResolver))
.build();
}
Custom AuthenticationManagerResolver:
@Override
public Mono<ReactiveAuthenticationManager> resolve(ServerWebExchange exchange) {
return Mono.just(globalAuthenticationManager());
}
ReactiveAuthenticationManager globalAuthenticationManager() {
return new JwtReactiveAuthenticationManager(globalAuthJwtDecoder());
}
ReactiveJwtDecoder globalAuthJwtDecoder() {
List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
validators.add(JwtValidators.createDefaultWithIssuer(globalAuthConfig.getIssuerUrlForValidation()));
validators.add(new JwtClaimValidator<List<String>>(AUD, s -> s.contains(globalAuthConfig.getAudience())));
validators.add(new JwtClaimValidator<String>(EMAIL, StringUtils::hasText));
validators.add(new JwtClaimValidator<String>("tenant_short_name", StringUtils::hasText));
OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>(validators);
// we use separate issuerUrl to discover configuration to allow the possibility of
// keeping the traffic from API Gw -> Authorization server in the internal network
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder
.withIssuerLocation(globalAuthConfig.getIssuerUrlForDiscovery())
.build();
jwtDecoder.setJwtValidator(validator);
return new SupplierReactiveJwtDecoder(() -> jwtDecoder);
}
Comment From: Cherrywoood
@ruckc Hello, I have such a problem. Do you know how to solve it? I have a spring app and keycloak running in docker. It is logical that the spring cannot be formed through localhost to keycloak in order to validate the token, however keycloak returns iss with localhost and an external port. I don't know how to solve this problem.
Comment From: heruan
Sorry to bump this closed issue, but it's not clear to me which is the solution. Same situation:
- client-app and Keycloak running in the same cluster, browser outside
- client-app uses internal hostname as issuer, but validation fails since Keycloak returns the external hostname for the issuer in metadata, see:
https://github.com/spring-projects/spring-security/blob/9c6b5f90f7787404922450d9ec559415c55ec4c3/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java#L246-L248
I need to fetch metadata from the issuer since some endpoints are read only from metadata, e.g the end_session_endpoint:
https://github.com/spring-projects/spring-security/blob/9c6b5f90f7787404922450d9ec559415c55ec4c3/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandler.java#L79-L80
But also other back-channel endpoints without configuration properties in application.yaml.
Since I know both internal and external hostnames, how can I make Spring Security fetch metadata from the internal issuer hostname and accept the external hostname in metadata?
Update I realized this is a different scenario, so I opened a new issue: https://github.com/spring-projects/spring-security/issues/14633