I am using Feign in a Spring Boot API and I would like to configure it using the following:
import feign.RequestInterceptor;
import feign.auth.BasicAuthRequestInterceptor;
import feign.codec.ErrorDecoder;
import feign.okhttp.OkHttpClient;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignClientConfiguration {
@Bean
public ErrorDecoder errorDecoder() {
return new ErrorDecoder.Default();
}
@Bean
public OkHttpClient client() {
return new OkHttpClient();
}
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
requestTemplate.header("user", "some user");
requestTemplate.header("password", "some password");
requestTemplate.header("Accept", ContentType.APPLICATION_JSON.getMimeType());
};
}
}
Problem is that if I use the following code in a service I get a 401, seems like no authentication data was provided:
Feign.builder()
.client(new OkHttpClient())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.logger(new Slf4jLogger(Npa.class))
.target(AClass.class, "https://some.website");
If I just add one line specifying user and password it works:
Feign.builder()
.client(new OkHttpClient())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.logger(new Slf4jLogger(Npa.class))
.requestInterceptor(new BasicAuthRequestInterceptor("some user", "some password"))
.target(AClass.class, "https://some.website");
I would prefer to use the configuration but I cannot understand what is wrong in it.
Since you've created some beans then you don't need to instantiate them again manually in builder steps.
The pseudo-class where you can create your feign client could be like this:
@Component
public class FeignClientFactory {
@Autowired
private ErrorDecoder errorDecoder;
@Autowired
private OkHttpClient client;
@Autowired
private RequestInterceptor requestInterceptor;
public AClass getFeignClient() {
return Feign.builder()
.client(client)
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.logger(new Slf4jLogger(Npa.class))
.requestInterceptor(requestInterceptor)
.target(AClass.class, "https://some.website");
}
}
Of course, there is a lot of options to improve it. F.e., you can create other beans (for encoders, loggers and so on) or use the default (autoconfigured) ones.
Or you can do it in @Configuration
class if you need only one feign client bean.
In case of request interceptors - there is another builder step .requestInterceptors
, where you can pass a collection of them.
EDIT: For basic authentication you don't need to have separate username and password headers but one header "Authorization" instead. Its value should look like "Basic username:password" where "username:password" part should be encoded with Base64 (link).
So, in your @Configuration
class you can write smth like this:
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
String encodedCredentials = Base64.getEncoder()
.encodeToString("some user:some password".getBytes());
requestTemplate.header("Authorization", "Basic " + encodedCredentials);
requestTemplate.header("Accept", ContentType.APPLICATION_JSON.getMimeType());
};
}
Then you can use this bean for building Feign client as I described above.
Another option: Because the creation of authorization header is a pretty common task, the BasicAuthRequestInterceptor
was introduced by Feign which does the same. You can use it instead. So now, in @Configuration
class you can create two beans:
@Bean
@Qualifier("basicAuthRequestInterceptor")
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("some user","some password");
}
@Bean
@Qualifier("customRequestInterceptor")
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
requestTemplate.header("Accept", ContentType.APPLICATION_JSON.getMimeType());
};
}
In this case, you need to modify the feign client building method in order to apply both these interceptors:
@Component
public class FeignClientFactory {
@Autowired
private ErrorDecoder errorDecoder;
@Autowired
private OkHttpClient client;
@Autowired
@Qualifier("basicAuthRequestInterceptor")
private RequestInterceptor basicAuthRequestInterceptor;
@Autowired
@Qualifier("customRequestInterceptor")
private RequestInterceptor requestInterceptor;
public AClass getFeignClient() {
List<RequestInterceptor> requestInterceptors = new ArrayList<>();
requestInterceptors.add(basicAuthRequestInterceptor);
requestInterceptors.add(requestInterceptor);
return Feign.builder()
.client(client)
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.logger(new Slf4jLogger(Npa.class))
.requestInterceptors(requestInterceptors)
.target(AClass.class, "https://some.website");
}
}