Malvin
Malvin

Reputation: 1

OidcUserInfoAuthenticationProvider doesn't support for opaque token bearer authorization

Continue for this problem, I still don't get any solution for handling my problem, issue 1865. I want to have spring authorization server with opaque token and oidc enabled.

Once oidc enabled, it allows client to call /userinfo endpoint. It's throw an error because of opaque bearer token can't handle by any provider.

When I try to configure custom authenticationManagerResolver,

.oauth2ResourceServer(resourceServer ->
    resourceServer      .authenticationManagerResolver(authenticationManagerResolver(resourceServerProperties)));

It throws another error because of OAuth2AuthorizationServerConfigurer that config jwt when oidc is enabled

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()));

            }
        }

Error stack

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:79) ~[spring-core-6.2.0.jar:6.2.0]
    at org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer.validateConfiguration(OAuth2ResourceServerConfigurer.java:299) ~[spring-security-config-6.4.1.jar:6.4.1]
    at org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer.init(OAuth2ResourceServerConfigurer.java:260) ~[spring-security-config-6.4.1.jar:6.4.1]
    at org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer.init(OAuth2ResourceServerConfigurer.java:147) ~[spring-security-config-6.4.1.jar:6.4.1]
    at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.init(AbstractConfiguredSecurityBuilder.java:366) ~[spring-security-config-6.4.1.jar:6.4.1]
    at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:328) ~[spring-security-config-6.4.1.jar:6.4.1]
    at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38) ~[spring-security-config-6.4.1.jar:6.4.1]
    at app.chameleon.authorization.server.config.AuthorizationServerConfig.authorizationServerSecurityFilterChain(AuthorizationServerConfig.java:143) ~[classes/:na]
    at app.chameleon.authorization.server.config.AuthorizationServerConfig$$SpringCGLIB$$0.CGLIB$authorizationServerSecurityFilterChain$9(<generated>) ~[classes/:na]
    at app.chameleon.authorization.server.config.AuthorizationServerConfig$$SpringCGLIB$$FastClass$$1.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[spring-core-6.2.0.jar:6.2.0]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:348) ~[spring-context-6.2.0.jar:6.2.0]
    at app.chameleon.authorization.server.config.AuthorizationServerConfig$$SpringCGLIB$$0.authorizationServerSecurityFilterChain(<generated>) ~[classes/:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.2.0.jar:6.2.0]
    ... 42 common frames omitted

How to overcome this problem ?

Upvotes: 0

Views: 158

Answers (1)

Roar S.
Roar S.

Reputation: 11134

While looking into this issue, I discovered that moving

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

above

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

app starts, and

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

returns successfully.

Complete authorizationServerSecurityFilterChain

    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(
            HttpSecurity http,
            AuthenticationManagerResolver<HttpServletRequest> tokenAuthenticationManagerResolver) throws Exception {
        var authorizationServerConfigurer =
                OAuth2AuthorizationServerConfigurer.authorizationServer();

        http
                .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
                .oauth2ResourceServer(oauth2 ->
                        oauth2.opaqueToken(Customizer.withDefaults())
                )
                .with(authorizationServerConfigurer, authorizationServer ->
                        authorizationServer.oidc(Customizer.withDefaults())
                )
                .authorizeHttpRequests(authorize ->
                        authorize.anyRequest().authenticated()
                )
                // Redirect to the login page when not authenticated from the
                // authorization endpoint
                .exceptionHandling(exceptions -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                );

        return http.build();
    }

OpaqueTokenIntrospector (application.yml)

spring:
  security:
    oauth2:
      resourceserver:
        opaque-token:
          introspection-uri: http://localhost:9000/oauth2/introspect
          client-id: my-client
          client-secret: secret

You will most likely have values for client-id and client-secret somewhere else in your application.yml, assuming that section is oauth:

          client-id: ${oauth.client-id}
          client-secret: ${oauth.client-secret}

Let's test the /userinfo-endpoint with this example JwtCustomizerConfig. In your app you should get values from context.getPrincipal(); I'm using hard-coded values for simplicity here. Why JwtEncodingContext instead of OAuth2TokenClaimsContext which is targeting opaque tokens? That relates to the original issue where OIDC-endpoints getting configured with JWT.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;

@Configuration
public class JwtCustomizerConfig {

    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> exampleJwtCustomizer() {
        return context -> context.getClaims()
                .claim("name", "Malvin Patrick")
                .claim("given_name", "Malvin")
                .claim("family_name", "Patrick")
                .claim("picture", "https://example.com/image.jpg")
                .claim("email", "[email protected]")
                .claim("email_verified", true)
                .claim("locale", "en");
    }
}

With scope openid, assuming username is user1:

{"sub":"user1"}

With scopes openid, email

{
  "sub": "user1",
  "email_verified": true,
  "email": "[email protected]"
}

With scopes openid, email, profile

{
  "sub": "user1",
  "email_verified": true,
  "given_name": "Malvin",
  "locale": "en",
  "picture": "https://example.com/image.jpg",
  "name": "Malvin Patrick",
  "family_name": "Patrick",
  "email": "[email protected]"
}

You can also test the introspect-endpoint. Replace my-client with client-id and secret with client-secret from application.yml.

curl -X POST \
  http://localhost:9000/oauth2/introspect \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "my-client:secret" \
  -d "token=<OPAQUE_TOKEN>"

Please note that this solution is WIP regarding discovering any unwanted side-effects.

Upvotes: 0

Related Questions