Given the following configuration:

@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
        throws Exception {

    OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
            OAuth2AuthorizationServerConfigurer.authorizationServer();

    http
        .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
        .with(authorizationServerConfigurer, (authorizationServer) ->
            authorizationServer
                .oidc(Customizer.withDefaults())    // Enable OpenID Connect 1.0
        )
        .authorizeHttpRequests((authorize) ->
            authorize
                .anyRequest().authenticated()
        )
        .exceptionHandling((exceptions) -> exceptions
            .defaultAuthenticationEntryPointFor(
                new LoginUrlAuthenticationEntryPoint("/login"),
                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
            )
        )
        .oauth2ResourceServer(resourceServer ->
            resourceServer
                .authenticationManagerResolver((context) -> customAuthenticationManager)
        );

    return http.build();
}

The application context will fail to build with the error message:

Caused by: java.lang.IllegalStateException: If an authenticationManagerResolver() is configured, then it takes precedence over any jwt() or opaqueToken() configuration.

The reason is because OAuth2AuthorizationServerConfigurer will default to resourceServer.jwt() if the OIDC UserInfo endpoint or OIDC Client Registration endpoint is enabled. However, if an application configures a client to use opaque tokens for an OpenID Connect flow, then configuring the authenticationManagerResolver() should be possible if support for both JWT and Opaque access tokens is required. As of now, it's not possible since resourceServer.jwt() was previously configured as the default by OAuth2AuthorizationServerConfigurer.

A similar error condition occurs with the following configuration:

@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
        throws Exception {

    OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
            OAuth2AuthorizationServerConfigurer.authorizationServer();

    http
        .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
        .with(authorizationServerConfigurer, (authorizationServer) ->
            authorizationServer
                .oidc(Customizer.withDefaults())    // Enable OpenID Connect 1.0
        )
        .authorizeHttpRequests((authorize) ->
            authorize
                .anyRequest().authenticated()
        )
        .exceptionHandling((exceptions) -> exceptions
            .defaultAuthenticationEntryPointFor(
                new LoginUrlAuthenticationEntryPoint("/login"),
                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
            )
        )
        .oauth2ResourceServer(resourceServer ->
            resourceServer
                .opaqueToken(Customizer.withDefaults())
        );

    return http.build();
}

The error message is:

Caused by: java.lang.IllegalStateException: Spring Security only supports JWTs or Opaque Tokens, not both at the same time.

The application is not able to override the default resourceServer.jwt() configured by OAuth2AuthorizationServerConfigurer to configure support for resourceServer.opaqueToken() instead.

Comment From: jzheaux

Thanks, @jgrandja, I'll take a look. It's quite common in Spring Security for the last-configured component to take precedence, so I imagine this is a reasonable improvement.

The only thing I hesitate on is that disallowing this seems to have been intentional in the code.

Either way, I've marked this as ideal-for-contribution and would be happy to work with someone on a PR.

Comment From: roar-skinderviken

One possible solution might be to just remove the code block starting on line 356 and let users of the code be responsible for configuring this.

As I see this, there are 3 possible configurations - oauth2.authenticationManagerResolver - oauth2.jwt - oauth2.opaqueToken

and just one of them can be applied.

This will be a breaking change, therefore some code for fail-fast if oauth is not configured properly, will be required.

Comment From: jgrandja

@roar-skinderviken

One possible solution might be to just remove the code block starting on line 356 and let users of the code be responsible for configuring this.

That block of code was added in spring-authorization-server#707 to simplify configuring Authorization Server. So I don't think we want to remove that block and force the user to explicitly configure resourceServer(). Defaulting to resourceServer().jwt() is a sensible default.

Comment From: jgrandja

@jzheaux

It's quite common in Spring Security for the last-configured component to take precedence, so I imagine this is a reasonable improvement.

I agree.

As of now, if a default is set (resourceServer().jwt()) by the framework (Authorization Server) then it still should be possible for the application to override the default. However, this is not possible for this specific configuration scenario so it is a blocker.

Comment From: roar-skinderviken

It seems like putting

                .oauth2ResourceServer(oauth2 ->
                        oauth2.opaqueToken(Customizer.withDefaults())
                )

before

                .with(authorizationServerConfigurer, authorizationServer ->
                        authorizationServer.oidc(Customizer.withDefaults())
                )

fixes the issue, but this should be verified by @malvinpatrick as well. I need some more Spring Security debugging-time to explain why and to discover any gotchas with this solution, unless one of you guys know the answers.

curl -X GET "http://localhost:9000/userinfo" -H "Authorization: Bearer <OPAQUE TOKEN>"

works as expected.

Comment From: malvinpatrick

Woahh thankyou @roar-skinderviken, in my case it solved the problem, but when you add oauth2.opaqueToken, how can you config OpaqueTokenIntrospector ?

I've tried your solution on this config, but I'm not pretty sure my code is a good way.

Based on this documentation, default OpaqueTokenIntrospector needs information about clientId and clientSecret which weird things in my case, an authorization server needs clientId and clientSecret for himself to handle opaqueToken, which Authorization Server already have repository to query token data on database.

So I create CustomOpaqueTokenIntrospector

Can you guys review my code ? If it good, maybe this can be a default OpaqueTokenIntrospector for Spring Authorization Server Project.

Comment From: roar-skinderviken

@malvinpatrick Good to hear that it solved your problem. Please see my answer to your question on StackOverflow https://stackoverflow.com/a/79339116/14072498.