Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
yvasyliev opened this issue Oct 4, 2024 · 2 comments
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement

Comments

@yvasyliev
Copy link

yvasyliev commented Oct 4, 2024

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.

@yvasyliev yvasyliev added status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Oct 4, 2024
@sjohnr sjohnr self-assigned this Oct 4, 2024
@sjohnr sjohnr added in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) and removed status: waiting-for-triage An issue we've not yet triaged labels Oct 4, 2024
@sjohnr
Copy link
Member

sjohnr commented Oct 4, 2024

@yvasyliev thanks for reaching out!

I think there might be some overlapping concepts regarding Spring Boot outlined in this issue, that should be clarified before we can discuss your use case.

  1. Spring Boot is a separate project from Spring Security, and actually depends on Spring Security.
  2. Auto-configuration is a feature of Spring Boot and cannot be used in Spring Security.
  3. Spring Boot starters are also a feature of Spring Boot and not part of Spring Security.

Because of the above, some of what you ask in the issue isn't quite accurate in context. Also, I don't think this request could simply be moved to Spring Boot because much of what you're asking for here is still specific to Spring Security, but would not be implemented the way you mention above (using Spring Boot features).

Regarding your use case:

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.

When Spring Security automatically configures an OAuth2AuthorizedClientManager, this is not part of Spring Boot's auto-configuration but performed by a BeanDefinitionRegistryPostProcessor in Spring Security that registers the bean. This post-processor is registered whenever spring-security-oauth2-client is on the classpath. Note that this is a Spring Security jar, not the Spring Boot starter.

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.

The presence of spring-boot-starter-web does cause Spring Security to be configured by default. However, it's not strictly related to OAuth2 Client, though I understand why it appears that way since the interactions between various components being switched on and configured with Spring Boot seems a bit magical.

Spring Security itself is only automatically set up in Spring Boot web applications (e.g. when spring-boot-starter-web is present). As you have noticed, nothing will initialize OAuth2 Client features for a non-web application and even Spring Security itself is not set up in that case since there would be nothing to protect by default. So when you use spring-boot-starter by itself, you are responsible for setting up OAuth2 Client.

But there's too much boilerplate code.

I'm sorry you feel that it is too much boilerplate code. However, I think the bean configuration in your example is fairly reasonable and minimal given that what you're requesting isn't supported out of the box.


Considering that the above is context for how things work now, what I think this request ends up asking is whether Spring Security can provide some kind of feature for initializing a non-web application with OAuth2 Client features, specifically using client_credentials with the AuthorizedClientServiceOAuth2AuthorizedClientManager.

This is an interesting request and could be a compelling use case. For requests like this, we typically want to see how many users in the community are asking for this before deciding to tackle it. We do that by tracking upvotes on open issues over time and if quite a lot of community interest is demonstrated, we would decide to prioritize it at that point.

Make sense?

@sjohnr sjohnr added the status: waiting-for-feedback We need additional information before we can continue label Oct 4, 2024
@yvasyliev
Copy link
Author

@sjohnr thanks for such a detailed explanation!

I 100% agree. Let's see if anyone else needs this feature. 😊

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Oct 5, 2024
@sjohnr sjohnr removed the status: feedback-provided Feedback has been provided label Oct 15, 2024
@sjohnr sjohnr removed their assignment Oct 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants