【问题标题】:Feign configuration not being used未使用 Feign 配置
【发布时间】:2020-08-03 10:00:05
【问题描述】:

我在 Spring Boot API 中使用 Feign,我想使用以下内容对其进行配置:

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());
        };
    }


}

问题是,如果我在服务中使用以下代码,我会得到 401,似乎没有提供身份验证数据:

        Feign.builder()
        .client(new OkHttpClient())
        .encoder(new GsonEncoder())
        .decoder(new GsonDecoder())
        .logger(new Slf4jLogger(Npa.class))
        .target(AClass.class, "https://some.website");

如果我只添加一行指定用户和密码,它就可以工作:

    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");

我更喜欢使用配置,但我不明白其中有什么问题。

【问题讨论】:

    标签: java spring-boot feign


    【解决方案1】:

    由于您已经创建了一些 bean,因此您无需在构建器步骤中再次手动实例化它们。

    您可以在其中创建 feign 客户端的伪类可能是这样的:

    @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");
        }
    }
    

    当然,有很多选择可以改进它。 F.e.,您可以创建其他 bean(用于编码器、记录器等)或使用默认(自动配置)的。

    如果您只需要一个 feign 客户端 bean,您也可以在 @Configuration 类中执行此操作。

    如果是请求拦截器 - 还有另一个构建器步骤.requestInterceptors,您可以在其中传递它们的集合。

    编辑:对于基本身份验证,您不需要单独的用户名和密码标题,而是一个标题“授权”。其值应类似于“基本用户名:密码”,其中“用户名:密码”部分应使用 Base64 (link) 进行编码。

    所以,在你的 @Configuration 类中,你可以这样写:

    @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());
        };
    }
    

    然后你可以使用这个bean来构建我上面描述的Feign客户端。

    另一种选择: 因为授权标头的创建是一项非常常见的任务,所以 Feign 引入了 BasicAuthRequestInterceptor,它也是这样做的。您可以改用它。所以现在,在@Configuration 类中你可以创建两个bean:

        @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());
            };
        }
    

    在这种情况下,您需要修改 feign 客户端构建方法以应用这两个拦截器:

    @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");
        }
    }
    

    【讨论】:

    • 感谢您的意见!可以使用属性进行依赖注入吗?我应该使用构造函数吗?
    • 我按照建议应用了更改,但错误仍然相同。
    • 哇,错过了。在您的自定义拦截器中,您尝试在两个单独的标头中传递用户名和密码。但在基本身份验证的情况下,您需要创建一个标头“Authorization”,其值类似于“Basic user:pass”,其中“user:pass”部分以 base 64 (link) 编码。如果你愿意,你可以重复here的逻辑或者自己做。
    • 关于字段注入 - 例如,我这样做了,可以通过构造函数或设置器来完成。
    • 我不确定我是否完全理解...您介意更新您的答案吗?
    猜你喜欢
    • 2020-10-08
    • 1970-01-01
    • 2018-03-13
    • 2019-06-12
    • 2018-10-07
    • 2015-05-23
    • 2015-08-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多