Search code examples
javaspringspring-securityoauthspring-cloud-feign

Enabling OAuth2 with Feign for scheduled cross-service tasks


I'm trying to solve a puzzle with enabling OAuth2-based authentication for my Feign client that is used for cross-service communication.

In normal cases, when a user pushes a request through API, I'm able to take all authentication details needed from the Spring's SecurityContextHolder (as it normally does its job and fills all the details objects) and enhance Feign's Request as follows:

public class FeignAccessTokenRelyInterceptor implements RequestInterceptor {
    private static final Logger log = LoggerFactory.getLogger(FeignAccessTokenRelyInterceptor.class);

    @Override
    public void apply(RequestTemplate template) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null){
            String tokenValue = null;
            if (auth.getDetails() instanceof OAuth2AuthenticationDetails) {
                OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
                tokenValue = details.getTokenValue();
            }
            if (tokenValue == null) {
                log.warn("Current token value is null");
                return;
            }
            template.header("Authorization", "Bearer " + tokenValue);
        }
    }
}

However, when it comes to scheduled calls that are triggered inside the system, the SecurityContext is obviously empty. I'm filling it with UserInfoTokenServices by manually requesting the access token by client credentials flow beforehand and loading user details:

OAuth2Authentication authentication = userInfoTokenServices.loadAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);

But such construction doesn't fill OAuth2Authentication.details, on which I rely to get an access token. I tried extending OAuth2AuthenticationDetails, but the only constructor requires HttpServletRequest which is hard to get inside a scheduled task, and making a dummy instance of it feels like a bad choice.

So far, I see the only adequate option to make a separate custom implementation of details holder and pass it to OAuth2Authentication along with the access token I have. And then pick it up in FeignAccessTokenRelyInterceptor.

The question

Maybe there are some other options where I can store my access token in the security context and reliably get it from there, in order not to produce new custom classes?

Will be glad for any help.

Some related links I've studied:


Solution

  • For the history, hope that'd help someone like me struggling with that.

    I didn't find a better way than the initial one and made a custom InternalOAuth2Details to hold a token value obtained from Spring's OAuth services. Then, in the FeignAccessTokenRelyInterceptor I simply check if current details are InternalOAuth2Details and try to get a token value from it, as follows:

    @Override
    public void apply(RequestTemplate template) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    
        if (auth != null){
            String tokenValue = null;
            if (auth.getDetails() instanceof OAuth2AuthenticationDetails) {
                OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
                tokenValue = details.getTokenValue();
            } else if (auth.getDetails() instanceof InternalOAuth2Details) {
                InternalOAuth2Details details = (InternalOAuth2Details) auth.getDetails();
                tokenValue = details.getTokenValue();
            }
            if (tokenValue == null) {
                log.warn("Current token value is null");
                return;
            }
            template.header("Authorization", "Bearer " + tokenValue);
        }
    }
    

    I bet it isn't the best solution, but it seems to work quite stable as of now.