Skip to content

Instantly share code, notes, and snippets.

@loesak
Last active June 26, 2020 01:51
Show Gist options
  • Save loesak/ec97dfb891381932187e655153e741ae to your computer and use it in GitHub Desktop.
Save loesak/ec97dfb891381932187e655153e741ae to your computer and use it in GitHub Desktop.
package com.loesak.feign.oauth2;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
@Slf4j
@RequiredArgsConstructor
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
private final ClientRegistrationRepository clientRegistrationRepository;
private final OAuth2AuthorizedClientService authorizedClientService;
private final String clientRegistrationId;
private final Clock clock = Clock.systemUTC();
private final Duration accessTokenExpiresSkew = Duration.ofMinutes(1);
private OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient = new DefaultClientCredentialsTokenResponseClient();
@Override
public void apply(RequestTemplate template) {
OAuth2AuthorizedClient oauthClient = this.authorizedClientService.loadAuthorizedClient(
this.clientRegistrationId,
this.clientRegistrationId);
if (oauthClient != null) {
ClientRegistration clientRegistration = oauthClient.getClientRegistration();
if (!this.isClientCredentialsGrantType(clientRegistration)) {
throw new UnsupportedOperationException("Only client_credentials grant type is supported");
}
if (this.hasTokenExpired(oauthClient)) {
oauthClient = this.getAuthorizedClient(clientRegistration);
}
} else {
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(this.clientRegistrationId);
if (clientRegistration == null) {
throw new IllegalArgumentException("Could not find ClientRegistration with id " + this.clientRegistrationId);
}
if (!this.isClientCredentialsGrantType(clientRegistration)) {
throw new UnsupportedOperationException("Only client_credentials grant type is supported");
}
oauthClient = this.getAuthorizedClient(clientRegistration);
}
template.header(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", oauthClient.getAccessToken().getTokenValue()));
}
private boolean isClientCredentialsGrantType(ClientRegistration clientRegistration) {
return AuthorizationGrantType.CLIENT_CREDENTIALS.equals(clientRegistration.getAuthorizationGrantType());
}
private OAuth2AuthorizedClient getAuthorizedClient(ClientRegistration clientRegistration) {
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest(clientRegistration);
OAuth2AccessTokenResponse tokenResponse = this.clientCredentialsTokenResponseClient.getTokenResponse(clientCredentialsGrantRequest);
Authentication authentication = new Authentication() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
throw unsupported();
}
@Override
public Object getCredentials() {
throw unsupported();
}
@Override
public Object getDetails() {
throw unsupported();
}
@Override
public Object getPrincipal() {
throw unsupported();
}
@Override
public boolean isAuthenticated() {
throw unsupported();
}
@Override
public void setAuthenticated(boolean isAuthenticated)
throws IllegalArgumentException {
throw unsupported();
}
@Override
public String getName() {
return OAuth2FeignRequestInterceptor.this.clientRegistrationId;
}
private UnsupportedOperationException unsupported() {
return new UnsupportedOperationException("Not Supported");
}
};
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
clientRegistration,
authentication.getName(),
tokenResponse.getAccessToken());
this.authorizedClientService.saveAuthorizedClient(
authorizedClient,
authentication);
return authorizedClient;
}
private boolean hasTokenExpired(OAuth2AuthorizedClient authorizedClient) {
Instant now = this.clock.instant();
Instant expiresAt = authorizedClient.getAccessToken().getExpiresAt();
if (now.isAfter(expiresAt.minus(this.accessTokenExpiresSkew))) {
return true;
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment