2

I'm using Spring Boot 2.3.4 and I need to call an external web service that needs the oauth2 authentication.

Currently I've achieved that in this way using feign

Client

@FeignClient(name = "myClient", value = "myClient", url = "${app.my.client.apiUrl}", configuration = MyClientConfiguration.class)
    public interface MyClient {
    
    @GetMapping(value = "/api/my-url", consumes = "application/json")
    String getSomeData();
}

Client Configuration

public class MyClientConfiguration {
    private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
    private final ClientRegistrationRepository clientRegistrationRepository;

        public MyClientConfiguration(OAuth2AuthorizedClientService oAuth2AuthorizedClientService, ClientRegistrationRepository clientRegistrationRepository) {
            this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
            this.clientRegistrationRepository = clientRegistrationRepository;
        }
    
        @Bean
        public RequestInterceptor requestInterceptor() {
            ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId("my-client");
            AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService);
            authorizedClientManager.setAuthorizedClientProvider(OAuth2AuthorizedClientProviderBuilder.builder().clientCredentials().build());
            return new OAuthClientCredentialsRestTemplateInterceptor(authorizedClientManager, clientRegistration);
        }
    }

OAuth Interceptor

public class OAuthClientCredentialsRestTemplateInterceptor implements RequestInterceptor {
        private static final String BEARER_HEADER_NAME = "Bearer";
        private final OAuth2AuthorizedClientManager manager;
        private final Authentication emptyPrincipal;
        private final ClientRegistration clientRegistration;
    
        public OAuthClientCredentialsRestTemplateInterceptor(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
            this.manager = manager;
            this.clientRegistration = clientRegistration;
            this.emptyPrincipal = createEmptyPrincipal();
        }
    
        @Override
        public void apply(RequestTemplate requestTemplate) {
            OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistration.getRegistrationId()).principal(emptyPrincipal).build();
            OAuth2AuthorizedClient client = manager.authorize(oAuth2AuthorizeRequest);
            if (client == null)
                throw new IllegalStateException("Cannot retrieve a valid client for registration " + clientRegistration.getRegistrationId());
            requestTemplate.header(HttpHeaders.AUTHORIZATION, BEARER_HEADER_NAME + " " + client.getAccessToken().getTokenValue());
        }
    
        private Authentication createEmptyPrincipal() {
            return new Authentication() {
                @Override
                public Collection<? extends GrantedAuthority> getAuthorities() {
                    return Collections.emptySet();
                }
    
                @Override
                public Object getCredentials() {
                    return null;
                }
    
                @Override
                public Object getDetails() {
                    return null;
                }
    
                @Override
                public Object getPrincipal() {
                    return this;
                }
    
                @Override
                public boolean isAuthenticated() {
                    return false;
                }
    
                @Override
                public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
                }
    
                @Override
                public String getName() {
                    return clientRegistration.getClientId();
                }
            };
        }
    }

properties

spring:
  security:
    oauth2:
      client:
        registration:
          microsoft:
            client-id: ******
            client-secret:  ******
            scope:  ******
            authorization-grant-type: client_credentials
            provider: my-client
        provider:
          my-client:
            token-uri: ******

app:
  my-client:
    apiUrl: https://my-url.com

feign:
  hystrix:
    enabled: false
  client:
    config:
      default:
        connect-timeout: 3000

In another project I need the same BUT it's a spring boot application without the web environment, and I've the following error

bean of type 'org.springframework.security.oauth2.client.OAuth2AuthorizedClientService' that could not be found.

How can I solve this situation?

Is it possible to use the oauth2 auto-configuration in an environment without a tomcat (or similar)?

LovaBill
  • 5,107
  • 1
  • 24
  • 32
Gavi
  • 1,300
  • 1
  • 19
  • 39

3 Answers3

0

You need to configure a separate class and tell spring IOC that this is stateless session.

@Configuration
@EnableWebSecurity
public class SecurityConfigForOauth extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().httpBasic().disable().formLogin().disable().logout().disable().sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

}
  • Hello. I don't have any web container. So @EnableWebSecurity doesn't match, and I can't set any sessionPolicy because I don't have anything that manage the sessions – Gavi Oct 05 '20 at 13:51
0

Check my response on how to get client_credentials working on https://stackoverflow.com/a/65741386/698471, there is no direct dependency on spring-boot-starter-web

Javi Vazquez
  • 517
  • 6
  • 21
0

Not the solution, only a workaround to convert the microservice into a micro-application: let the job run at configuration time during application startup by either using no batch property or setting spring.batch.job.enabled=true and then terminating the process.

@SpringBootApplication
public class MyBootApp implements CommandLineRunner {

public static void main(String[] args) {
    SpringApplication.run(MyBootApp.class, args);
}

//Runs after @Configuration classes are finished synchronously including batch jobs.
@Override
public void run(String... args) throws Exception {
    System.exit(0); 
}

}

It seems that RestTemplate and WebClient are using spring-web configuration files for autowiring stuff needed for OAuth interceptors.

LovaBill
  • 5,107
  • 1
  • 24
  • 32