Skip to content

Add OAuth2AuthorizedClientManager autoconfiguration without spring-boot-starter-web dependency #15877

Open
@yvasyliev

Description

@yvasyliev

Expected Behavior

I would like org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager to be an autoconfigured bean based on application.yml properties, and without having spring-boot-starter-web dependency.

My desirable state would be the following:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.0-M3</version>
    </parent>

    <groupId>io.github.yvasyliev.oauth2</groupId>
    <artifactId>springboot-oauth2-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-json</artifactId>
        </dependency>
    </dependencies>
</project>

application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          auth-1:
            client-id: client-id
            client-secret: client-secret
            authorization-grant-type: client_credentials
        provider:
          auth-1:
            token-uri: https://auth-1/api/v1/token

MyServiceConfig.java

@Configuration
public class MyServiceConfig {
    @Bean
    public MyService myService(OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
        var oAuth2ClientHttpRequestInterceptor = new OAuth2ClientHttpRequestInterceptor(
                authorizedClientManager,
                request -> "auth-1"
        );
        var restClient = RestClient.builder()
                .baseUrl("https://api.service-1.com")
                .requestInterceptor(oAuth2ClientHttpRequestInterceptor)
                .build();
        var restClientAdapter = RestClientAdapter.create(restClient);
        var httpServiceProxyFactory = HttpServiceProxyFactory.builderFor(restClientAdapter).build();
        return httpServiceProxyFactory.createClient(MyService.class);
    }
}

I would expect oAuth2AuthorizedClientManager to be an instance of org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager, because it exists outside the servlet context.

Current Behavior

The application above fails to start:

Console output

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of method myService in io.github.yvasyliev.oauth2.config.MyServiceConfig required a bean of type 'org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager' that could not be found.


Action:

Consider defining a bean of type 'org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager' in your configuration.


Process finished with exit code 1

If I add spring-boot-starter-web dependency to the project, the oAuth2AuthorizedClientManager bean will be automatically created:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.0-M3</version>
    </parent>

    <groupId>io.github.yvasyliev.oauth2</groupId>
    <artifactId>springboot-oauth2-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

But at the same time I'm having:

  1. A web server up and running.
  2. oAuth2AuthorizedClientManager is instance of org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.
  3. spring.main.web-application=none property will disable server startup and OAuth2AuthorizedClientManager autoconfiguration.

Context

I'm building a Spring Boot (not web!) application that communicates with external REST services. I want to utilize HTTP Interface based on RestClient with OAuth interceptor. And I don't really want to add spring-boot-starter-web to my project, because it includes HTTP server that I won't use.

It would be awesome if OAuth2AuthorizedClientManager bean was automatically created in case of spring.security.oauth2.client.* properties existence in application.yml just like spring-boot-starter-web does.

I can achieve the desired outcome by manual OAuth2AuthorizedClientManager configuration:

MyServiceConfig.java

@Configuration
public class MyServiceConfig {
    @Bean
    public MyService myService(
            @Value("${spring.security.oauth2.client.registration.auth-1.client-id}") String clientId,
            @Value("${spring.security.oauth2.client.registration.auth-1.client-secret}") String clientSecret,
            @Value("${spring.security.oauth2.client.registration.auth-1.authorization-grant-type}") AuthorizationGrantType authorizationGrantType,
            @Value("${spring.security.oauth2.client.provider.auth-1.token-uri}") String tokenUri) {
        var clientRegistration = ClientRegistration.withRegistrationId("auth-1")
                .clientId(clientId)
                .clientSecret(clientSecret)
                .authorizationGrantType(authorizationGrantType)
                .tokenUri(tokenUri)
                .build();
        var clientRegistrationRepository = new InMemoryClientRegistrationRepository(clientRegistration);
        var authorizedClientService = new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
        var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
                clientRegistrationRepository,
                authorizedClientService
        );
        var oAuth2ClientHttpRequestInterceptor = new OAuth2ClientHttpRequestInterceptor(
                authorizedClientManager,
                request -> "auth-1"
        );
        var restClient = RestClient.builder()
                .baseUrl("https://api.service-1.com")
                .requestInterceptor(oAuth2ClientHttpRequestInterceptor)
                .build();
        var restClientAdapter = RestClientAdapter.create(restClient);
        var httpServiceProxyFactory = HttpServiceProxyFactory.builderFor(restClientAdapter).build();
        return httpServiceProxyFactory.createClient(MyService.class);
    }
}

But there's too much boilerplate code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    in: oauth2An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose)type: enhancementA general enhancement

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions