You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

服务端返回401时Feign返回FeignException而非ResponseEntity的问题

问题描述

我们的应用使用spring-cloud-starter-openfeign:4.1.0实现服务间通信,客户端代码如下:

@FeignClient(name = "authClient", url = "${spring.microservice.tenant-auth.host}")
public interface AuthClient {
    @PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    ResponseEntity<AuthenticationResponse> getAuthToken(AuthFormData formData);
}

public class AuthFormData {

    private String grant_type;
    private String client_id;
    private String client_secret;

    public void setGrantType(String grantType) {
        this.grant_type = grantType;
    }

    public void setClientId(String clientId) {
        this.client_id = clientId;
    }

    public void setClientSecret(String clientSecret) {
        this.client_secret = clientSecret;
    }
}

public class AuthenticationResponse {

    @JsonProperty("access_token")
    private String accessToken;
    @JsonProperty("token_type")
    private String tokenType;
    @JsonProperty("expires_in")
    private int expiresIn;
    @JsonProperty("scope")
    private String scope;
    @JsonProperty("refresh_token")
    private String refreshToken;
    @JsonProperty("id_token")
    private String idToken;
}

代码运行正常,但存在一个问题:当服务端返回401时,调用方会抛出FeignException,调用代码如下:

AuthenticationResponse getToken(AuthFormData authFormData) {
    var authResponse = authClient.getAuthToken(authFormData);
    var body = authResponse.getBody();
    if (!authResponse.getStatusCode().is2xxSuccessful() || body == null) {
        throw new IllegalStateException("Failed to get auth token " + authResponse);
    }
    return body;
}

对应的堆栈信息:

feign.FeignException$Unauthorized: [401 Unauthorized] during [POST] to [https://somethin.com/oauth/token] [AuthClient#getAuthToken(AuthFormData)]: []
    at feign.FeignException.clientErrorStatus(FeignException.java:224) ~[feign-core-13.1.jar:na]
    at feign.FeignException.errorStatus(FeignException.java:203) ~[feign-core-13.1.jar:na]
    at feign.FeignException.errorStatus(FeignException.java:194) ~[feign-core-13.1.jar:na]
    at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:103) ~[feign-core-13.1.jar:na]
    at feign.InvocationContext.decodeError(InvocationContext.java:126) ~[feign-core-13.1.jar:na]
    at feign.InvocationContext.proceed(InvocationContext.java:72) ~[feign-core-13.1.jar:na]
    at feign.ResponseHandler.handleResponse(ResponseHandler.java:63) ~[feign-core-13.1.jar:na]
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:114) ~[feign-core-13.1.jar:na]
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:70) ~[feign-core-13.1.jar:na]
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:99) ~[feign-core-13.1.jar:na]
    at jdk.proxy2/jdk.proxy2.$Proxy113.getAuthToken(Unknown Source) ~[na:na]
    at c.c.o.p.m.AuthenticationTokenProvider.getToken(AuthenticationTokenProvider.java:34) ~[classes/:0.0.1-SNAPSHOT]

奇怪的是,服务端返回200或500时,能正常返回对应状态码的ResponseEntity。请问为何401会抛出异常,如何让AuthClient返回带401状态的ResponseEntity?

原因分析

Feign的默认ErrorDecoder会将**4xx客户端错误(除404外)**转换成FeignException抛出,而5xx服务端错误则会被直接包装进ResponseEntity返回。这就是401会触发异常,而500能正常返回ResponseEntity的核心原因。

解决方案

要让Feign返回401状态的ResponseEntity,需要自定义Feign的错误处理逻辑,跳过对401的异常转换。

方法一:自定义ErrorDecoder放行401状态

创建自定义ErrorDecoder实现,对401状态不抛出异常,交给后续代码处理:

import feign.Response;
import feign.codec.ErrorDecoder;
import org.springframework.cloud.openfeign.FeignClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {

    @Bean
    public ErrorDecoder errorDecoder(FeignClientProperties properties) {
        return new ErrorDecoder.Default() {
            @Override
            public Exception decode(String methodKey, Response response) {
                // 对401状态不抛出异常,直接返回null让Feign继续处理成ResponseEntity
                if (response.status() == 401) {
                    return null;
                }
                return super.decode(methodKey, response);
            }
        };
    }
}

在@FeignClient注解中指定该配置类:

@FeignClient(name = "authClient", url = "${spring.microservice.tenant-auth.host}", configuration = FeignConfig.class)
public interface AuthClient {
    // 原有方法保持不变
}

方法二:配置统一Decoder处理所有状态码

通过自定义SpringDecoder,让Feign将所有状态码的响应都包装成ResponseEntity返回,包括4xx:

import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.util.List;

@Configuration
public class FeignConfig {

    @Bean
    public SpringDecoder springDecoder() {
        List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
        return new SpringDecoder(() -> converters);
    }
}

这种方式下,所有状态码的响应都会被封装为ResponseEntity,不会触发Feign异常。

验证效果

修改完成后,调用authClient.getAuthToken(authFormData)时,服务端返回401的情况下,会得到一个状态码为401的ResponseEntity,可以在调用方法中正常判断状态码并进行后续处理。

内容的提问来源于stack exchange,提问作者Sergey Tsypanov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 17:02:02