Description
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:
- A web server up and running.
oAuth2AuthorizedClientManager
is instance oforg.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager
.spring.main.web-application=none
property will disable server startup andOAuth2AuthorizedClientManager
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.