OpenFeign / feign

Feign makes writing java http clients easier
Apache License 2.0
9.44k stars 1.92k forks source link

Unexpected FeignException instead of ResponseEntity when the server returns 401 #2312

Open tsypanovs opened 8 months ago

tsypanovs commented 8 months ago

Originally asked in https://stackoverflow.com/questions/77880702/unexpected-feignexception-instead-of-responseentity-when-the-server-returns-401

In our application we use spring-cloud-starter-openfeign:4.1.0 for some interservice communication. Here's one of our clients:

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

In general this code works fine, but what puzzles me is the FeignException thrown from the callee in case the server returns 401:

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

Here's the stacktrace:

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]

If the response is 200 or 500 then the ResponseEntity with corresponding status code is returned.

So my question is why do I get exception for 401 and is there a way to make the AuthClient return ResponseEntity with 401?

velo commented 8 months ago

Well, response entity is not a feign response.

I have no clue how it would work for 500s

On Fri, Jan 26, 2024, 21:36 tsypanovs @.***> wrote:

Originally asked in https://stackoverflow.com/questions/77880702/unexpected-feignexception-instead-of-responseentity-when-the-server-returns-401

In our application we use spring-cloud-starter-openfeign:4.1.0 for some interservice communication. Here's one of our clients:

@FeignClient(name = "authClient", url = "${spring.microservice.tenant-auth.host}")public interface AuthClient { @PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) ResponseEntity 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;

}

In general this code works fine, but what puzzles me is the FeignException thrown from the callee in case the server returns 401:

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

Here's the stacktrace:

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]

If the response is 200 or 500 then the ResponseEntity with corresponding status code is returned.

So my question is why do I get exception for 401 and is there a way to make the AuthClient return ResponseEntity with 401?

— Reply to this email directly, view it on GitHub https://github.com/OpenFeign/feign/issues/2312, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABBLDSADY3UB5S3IIZ3QNLYQNTJVAVCNFSM6AAAAABCLZKWD6VHI2DSMVQWIX3LMV43ASLTON2WKOZSGEYDCOBQGQ3TSNA . You are receiving this because you are subscribed to this thread.Message ID: @.***>

kdavisk6 commented 3 weeks ago

Feign treats all status codes outside of 2xx and 3xx as errors. You'll need include an ErrorDecoder, if you haven't already. This will be called when the status is outside of the acceptable range and will include the Response for further analysis and processing.