Feign: Feign Client don't reauthentificate on expired refresh token Oauth2

Created on 24 Oct 2019  路  4Comments  路  Source: OpenFeign/feign

Hi,

I struggle with 401 errors, when refresh token expired. I would like to code a workflow to handle 401 codes and force reauthentification Oauth2 on feign client. How can I do this? This is microservice on Spring Boot 2.0.5 and Spring Cloud Security

Feign client version: _2.1.3.RELEASE_

    RequestInterceptor oauth2FeignRequestInterceptor() {
        return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resource());
    }

    private OAuth2ProtectedResourceDetails resource() {
        ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
        resourceDetails.setUsername(user);
        resourceDetails.setPassword(password);
        resourceDetails.setAccessTokenUri(accessTokenUri);
        resourceDetails.setClientId(clientId);
        resourceDetails.setClientSecret(clientSecret);
        resourceDetails.setGrantType("all");
        resourceDetails.setScope(Arrays.asList("all));
        return resourceDetails;
    }

Most helpful comment

I have just finished a hard solution. It just reset access token, when gets 401 and retry request. I hope my snippets will help somebody with similiar problem

@Slf4j
public class CustomErrorDecoder implements ErrorDecoder {
    @Autowired
    FeignRequestInterceptor feignRequestInterceptor;

    @Override
    public Exception decode(String methodKey, Response response) {
        log.debug("Catched Feign client error {}, {}", methodKey, response);
        FeignException exception = errorStatus(methodKey, response);

        switch (response.status()) {
            case 401:
                log.debug("Clearing access token and retrying last reques ");
                feignRequestInterceptor.cleanAccessToken();
                return new RetryableException(exception.getMessage(), new Date());
            case 404:
                return new NotFoundException(response.reason());
            default:
                return new exception;
        }
    }
}
@Log4j
public class FeignRequestInterceptor extends OAuth2FeignRequestInterceptor {
    private OAuth2ClientContext clientContext;

    public FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, OAuth2ProtectedResourceDetails resource) {
        super(oAuth2ClientContext, resource);
        clientContext = oAuth2ClientContext;
    }

    @Override
    public OAuth2AccessToken getToken() {
        OAuth2AccessToken accessToken = clientContext.getAccessToken();

        if (accessToken == null || accessToken.isExpired()) {
            try {
                accessToken = acquireAccessToken();
            }catch (InvalidGrantException e) {
                log.error("Catched invalid grant exception: ", e);

                cleanAccessToken();
                accessToken = acquireAccessToken();
            }catch (HttpClientErrorException e) {
                log.error("Catched oauth http exception: ", e);

                if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) {
                    log.debug("Acquiring access token...");
                    cleanAccessToken();
                    accessToken = acquireAccessToken();
                } else {
                    throw e;
                }
            } catch (UserRedirectRequiredException e) {
                clientContext.setAccessToken(null);
                String stateKey = e.getStateKey();
                if (stateKey != null) {
                    Object stateToPreserve = e.getStateToPreserve();
                    if (stateToPreserve == null) {
                        stateToPreserve = "NONE";
                    }
                    clientContext.setPreservedState(stateKey, stateToPreserve);
                }
                throw e;
            }
        }
        return accessToken;
    }

    public void cleanAccessToken() {
        log.debug("Reset access token");

        clientContext.setAccessToken(null);
    }
}
@Configuration
public class FeignClientConfiguration {

    @Value("${oauth.accessTokenUri}")
    private String accessTokenUri;
    @Value("${oauth.clientId}")
    private String clientId;
    @Value("${oauth.clientSecret}")
    private String clientSecret;
    @Value("${oauth.client.scope}")
    private String scope;
    @Value("${oauth.client.user}")
    private String user;
    @Value("${oauth.client.password}")
    private String password;
    @Value("${oauth.grantType}")
    private String grantType = "password";

    @Bean
    FeignRequestInterceptor oauth2FeignRequestInterceptor() {
        return new FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resource());
    }

    private OAuth2ProtectedResourceDetails resource() {
        ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
        resourceDetails.setUsername(user);
        resourceDetails.setPassword(password);
        resourceDetails.setAccessTokenUri(accessTokenUri);
        resourceDetails.setClientId(clientId);
        resourceDetails.setClientSecret(clientSecret);
        resourceDetails.setGrantType(grantType);
        resourceDetails.setScope(Arrays.asList(scope));
        return resourceDetails;
    }

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

    @Bean
    public ErrorDecoder errorDecoder() {
        return new CustomErrorDecoder();
    }

    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1), 2); // this will retry once
    }
}

All 4 comments

I have just finished a hard solution. It just reset access token, when gets 401 and retry request. I hope my snippets will help somebody with similiar problem

@Slf4j
public class CustomErrorDecoder implements ErrorDecoder {
    @Autowired
    FeignRequestInterceptor feignRequestInterceptor;

    @Override
    public Exception decode(String methodKey, Response response) {
        log.debug("Catched Feign client error {}, {}", methodKey, response);
        FeignException exception = errorStatus(methodKey, response);

        switch (response.status()) {
            case 401:
                log.debug("Clearing access token and retrying last reques ");
                feignRequestInterceptor.cleanAccessToken();
                return new RetryableException(exception.getMessage(), new Date());
            case 404:
                return new NotFoundException(response.reason());
            default:
                return new exception;
        }
    }
}
@Log4j
public class FeignRequestInterceptor extends OAuth2FeignRequestInterceptor {
    private OAuth2ClientContext clientContext;

    public FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, OAuth2ProtectedResourceDetails resource) {
        super(oAuth2ClientContext, resource);
        clientContext = oAuth2ClientContext;
    }

    @Override
    public OAuth2AccessToken getToken() {
        OAuth2AccessToken accessToken = clientContext.getAccessToken();

        if (accessToken == null || accessToken.isExpired()) {
            try {
                accessToken = acquireAccessToken();
            }catch (InvalidGrantException e) {
                log.error("Catched invalid grant exception: ", e);

                cleanAccessToken();
                accessToken = acquireAccessToken();
            }catch (HttpClientErrorException e) {
                log.error("Catched oauth http exception: ", e);

                if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) {
                    log.debug("Acquiring access token...");
                    cleanAccessToken();
                    accessToken = acquireAccessToken();
                } else {
                    throw e;
                }
            } catch (UserRedirectRequiredException e) {
                clientContext.setAccessToken(null);
                String stateKey = e.getStateKey();
                if (stateKey != null) {
                    Object stateToPreserve = e.getStateToPreserve();
                    if (stateToPreserve == null) {
                        stateToPreserve = "NONE";
                    }
                    clientContext.setPreservedState(stateKey, stateToPreserve);
                }
                throw e;
            }
        }
        return accessToken;
    }

    public void cleanAccessToken() {
        log.debug("Reset access token");

        clientContext.setAccessToken(null);
    }
}
@Configuration
public class FeignClientConfiguration {

    @Value("${oauth.accessTokenUri}")
    private String accessTokenUri;
    @Value("${oauth.clientId}")
    private String clientId;
    @Value("${oauth.clientSecret}")
    private String clientSecret;
    @Value("${oauth.client.scope}")
    private String scope;
    @Value("${oauth.client.user}")
    private String user;
    @Value("${oauth.client.password}")
    private String password;
    @Value("${oauth.grantType}")
    private String grantType = "password";

    @Bean
    FeignRequestInterceptor oauth2FeignRequestInterceptor() {
        return new FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resource());
    }

    private OAuth2ProtectedResourceDetails resource() {
        ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
        resourceDetails.setUsername(user);
        resourceDetails.setPassword(password);
        resourceDetails.setAccessTokenUri(accessTokenUri);
        resourceDetails.setClientId(clientId);
        resourceDetails.setClientSecret(clientSecret);
        resourceDetails.setGrantType(grantType);
        resourceDetails.setScope(Arrays.asList(scope));
        return resourceDetails;
    }

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

    @Bean
    public ErrorDecoder errorDecoder() {
        return new CustomErrorDecoder();
    }

    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1), 2); // this will retry once
    }
}

@michmzr Feign is just REST client nothing else; it cannot be responsible for re-authenticate/refresh under default settings as that is spring oauth2 provider/integration I've done successfully your case, without using any hardcoding or any feign-specific customizations

OAuth2FeignRequestInterceptor is a part of the spring-cloud-security library. Consider heading over there to submit your issue.

https://github.com/spring-cloud/spring-cloud-security/blob/master/spring-cloud-security/src/main/java/org/springframework/cloud/security/oauth2/client/feign/OAuth2FeignRequestInterceptor.java

I agree with navaare and WhiteBoardDev.

Please file a ticket with spring cloud.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

JerryChaox picture JerryChaox  路  3Comments

why168 picture why168  路  5Comments

hellboy81 picture hellboy81  路  4Comments

shivamsingh picture shivamsingh  路  4Comments

firebook picture firebook  路  5Comments