Spring Boot Using Multiple OpenFeign Clients To Authenticate REST API Calls
By: Zdenek Papez
Tags: Spring Boot, Authentication, Rest Api, Openfeign, Integration Testing

Spring Boot Application

Start with Spring Initializr and add the necessary dependencies:

Spring Web — our application itself is a REST API with one endpoint

OpenFeign — to call Jira and Slack APIs

Lombok — for some handy getters and setters in DTO classes

Spring Initializr Dependencies

Configure OpenFeign Client for Jira API

In this section, we connect to Zephyr part of the Jira API. Let’s use Get list of executions endpoint to showcase the functionality.

Define your zephyr.api.baseUrl in your application.yml like:

Zephyr API uses basic authentication. To make your Feign Client provide the correct authorization header, use a configuration class where you define a bean of BasicAuthRequestInterceptor .

Notice — do not mark the whole class as @Configuration . This would make the BasicAuthRequestInterceptor bean available in the whole Spring context and therefore it would be picked up by other Feign Clients. That would interact with the Slack Feign Client that we define next.

In this article’s sample GitHub project, you can use JIRA_API_USERNAME and JIRA_API_PASSWORD environment variables to provide the credentials. (Notice — your Jira username is usually not your email.)

Configure OpenFeign Client for Slack API

As a second OpenFeign client let’s configure a client that is sending messages to Slack API.

The Slack API base URL is https://slack.com/api and it uses token authentication. You can find your token in a Slack application that you need to create to start using the API. Token is visible in the configuration page of your application at https://api.slack.com/apps/YOUAR_APP_ID .

Use configuration to define a RequestInterceptor that adds Authorization header with Bearer token to all Slack requests.

In this article’s sample GitHub project, you can use SLACK_AUTH_TOKEN environment variable to provide the token.

Controller and Service

Let’s just create a simple controller with GET endpoint at /api/v1/resource that will trigger a service that calls both Jira and Slack APIs.

If all environment variables are correctly set up and correct credentials are provided, this should work — but let’s test it also in an integration test.

WireMock Integration Test verifying Authorization Headers

When creating 3rd party API calls in our application, we can verify that proper endpoints with a proper request structure were called using WireMock. Of course, we also mock responses from those APIs to verify our code (and business process) reacts accordingly.

Let’s create a JUnit 5 integration test using @SprinBootTest — see the complete integration test on GitHub.

Add WireMock dependecy in build.gradle

testImplementation ‘com.github.tomakehurst:wiremock:2.27.2’ With JUnit 5 we cannot use WireMock through @Rule , but we have to use an extension.

This enables us to create multiple WireMock servers on random ports during the @BeforeAll .

@BeforeAll

public static void beforeAll(@WireMockInstance WireMockServer server1, @WireMockInstance WireMockServer server2) {

zephyrWiremockServer = server1;

slackWiremockServer = server2;

}

3. Because WireMock servers start on random ports, we need to set up our Jira and Slack API base URLs on the fly. This is done using initializers in @ContextConfiguration .

4. Define fake credentials used in your tests.

@SpringBootTest(

webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,

properties = {

"zephyr.api.username=zephyrTestUser",

"zephyr.api.password=zephyrTestPassword",

"slack.app.oauth.accessToken=testFakeToken"

})

5. Verify that all requests to Jira and Slack APIs had the proper Authorization headers in @AfterEach .

zephyrWiremockServer.verify(anyRequestedFor(anyUrl())

.withBasicAuth(new BasicCredentials("zephyrTestUser", "zephyrTestPassword"))); slackWiremockServer.verify(anyRequestedFor(anyUrl())

.withHeader("Authorization", equalTo("Bearer testFakeToken")));

6. You should also verify that there was exactly one Authozitation header in your requests. There could be potentially multiple Authozitation headers and WireMock would match any of them. To assert that there is only one header that you expect is a bit tricky with WireMock, but can be achieved using addMockServiceRequestListener in @BeforeAll and later asserting it in @AfterEach .

7. Finally, create a test method where you call the controller, mock the Jira, and Slack API responses, and verify the response of your controller.

https://github.com/zpapez/spring-feign-clients/blob/707dd17fd5cc3a93a7ffa031a8fa1de405e19c8c/src/test/java/cz/zpapez/springfeignclients/IntegrationTest.java#L102

Troubleshooting

Turn on full logging of OpenFeign clients

@Configuration

public class MyConfiguration {

@Bean

Logger.Level feignLoggerLevel() {

return Logger.Level.FULL;

}

}

and in properties

logging.level.cz.zpapez.springfeignclients.zephyr.ZephyrFeignClient: DEBUG

logging.level.cz.zpapez.springfeignclients.slack.SlackFeignClient: DEBUG

This setup prints all requests including headers and also responses that are being sent. You can verify proper values in proper authorization headers by reading these logs.

401 Unauthorized

Double-check that you provide the correct credentials (like Jira username, not email) in the environment variables. Maybe also try to make the same request work using curl .

No default constructor

No default constructor found when running the @SpringBootTest with context configuration initializers @ContextConfiguration(initializers = AppIntegrationTestPropertyInitializer.class) .

Caused by: org.springframework.beans.BeanInstantiationException:

Failed to instantiate [cz.zpapez.springfeignclients.IntegrationTest$AppIntegrationTestPropertyInitializer]: No default constructor found; nested exception is java.lang.NoSuchMethodException:

cz.zpapez.springfeignclients.IntegrationTest$AppIntegrationTestPropertyInitializer.<init>()