Last active
November 5, 2021 12:22
-
-
Save tschlegel/2c96146cdb57e57b6acad26615868ecf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.springframework.security.oauth2.client.web.reactive.function.client; | |
import java.net.URI; | |
import java.time.Clock; | |
import java.time.Duration; | |
import java.time.Instant; | |
import java.util.Optional; | |
import org.springframework.cloud.gateway.filter.GatewayFilter; | |
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; | |
import org.springframework.http.HttpHeaders; | |
import org.springframework.http.HttpMethod; | |
import org.springframework.http.MediaType; | |
import org.springframework.http.client.reactive.ClientHttpConnector; | |
import org.springframework.security.core.Authentication; | |
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; | |
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; | |
import org.springframework.security.oauth2.client.registration.ClientRegistration; | |
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; | |
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; | |
import org.springframework.security.oauth2.core.AuthorizationGrantType; | |
import org.springframework.security.oauth2.core.OAuth2AccessToken; | |
import org.springframework.security.oauth2.core.OAuth2RefreshToken; | |
import org.springframework.stereotype.Component; | |
import org.springframework.web.reactive.function.BodyInserters; | |
import org.springframework.web.reactive.function.client.ClientRequest; | |
import org.springframework.web.reactive.function.client.ExchangeFunction; | |
import org.springframework.web.reactive.function.client.ExchangeFunctions; | |
import org.springframework.web.server.ServerWebExchange; | |
import reactor.core.publisher.Mono; | |
import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse; | |
/** | |
* Token Relay Gateway Filter with automatic access token refresh. This can be removed when issue {@see https://github.com/spring-cloud/spring-cloud-security/issues/175} is closed. | |
* Implementation based on {@link ServerOAuth2AuthorizedClientExchangeFilterFunction} | |
*/ | |
@Component | |
public class TokenRelayWithRefreshGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> { | |
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository; | |
private final OAuth2AuthorizedClientResolver authorizedClientResolver; | |
private final ClientHttpConnector connector; | |
private Clock clock = Clock.systemUTC(); | |
private Duration accessTokenExpiresSkew = Duration.ofSeconds(3); | |
public TokenRelayWithRefreshGatewayFilterFactory(ServerOAuth2AuthorizedClientRepository authorizedClientRepository, ReactiveClientRegistrationRepository clientRegistrationRepository, ClientHttpConnector connector) { | |
super(Object.class); | |
this.authorizedClientRepository = authorizedClientRepository; | |
this.authorizedClientResolver = new OAuth2AuthorizedClientResolver(clientRegistrationRepository, authorizedClientRepository); | |
this.connector = connector; | |
} | |
public GatewayFilter apply() { | |
return apply((Object) null); | |
} | |
@Override | |
public GatewayFilter apply(Object config) { | |
return (exchange, chain) -> exchange.getPrincipal() | |
// .log("token-relay-filter") | |
.filter(principal -> principal instanceof OAuth2AuthenticationToken) | |
.cast(OAuth2AuthenticationToken.class) | |
.flatMap(authentication -> loadAuthorizedClient(exchange, authentication)) | |
.map(OAuth2AuthorizedClient::getAccessToken) | |
.map(token -> withBearerAuth(exchange, token)) | |
// TODO: adjustable behavior if empty | |
.defaultIfEmpty(exchange).flatMap(chain::filter); | |
} | |
private ServerWebExchange withBearerAuth(ServerWebExchange exchange, | |
OAuth2AccessToken accessToken) { | |
return exchange.mutate() | |
.request(r -> r.headers( | |
headers -> headers.setBearerAuth(accessToken.getTokenValue()))) | |
.build(); | |
} | |
private Mono<OAuth2AuthorizedClient> refreshIfNecessary(ServerWebExchange exchange, String clientRegistrationId, OAuth2AuthorizedClient authorizedClient) { | |
if (shouldRefresh(authorizedClient)) { | |
return createRequest(exchange, clientRegistrationId) | |
.flatMap(r -> refreshAuthorizedClient(authorizedClient, r)); | |
} | |
return Mono.just(authorizedClient); | |
} | |
private Mono<OAuth2AuthorizedClient> loadAuthorizedClient(ServerWebExchange exchange, OAuth2AuthenticationToken oAuth2AuthenticationToken) { | |
String clientRegistrationId = oAuth2AuthenticationToken.getAuthorizedClientRegistrationId(); | |
return createRequest(exchange, clientRegistrationId) | |
.flatMap(authorizedClientResolver::loadAuthorizedClient) | |
.flatMap(authClient -> refreshIfNecessary(exchange, clientRegistrationId, authClient)); | |
} | |
private Mono<OAuth2AuthorizedClientResolver.Request> createRequest(ServerWebExchange exchange, String clientRegistrationId) { | |
Authentication authentication = null; | |
return authorizedClientResolver.createDefaultedRequest(clientRegistrationId, authentication, exchange); | |
} | |
private boolean shouldRefresh(OAuth2AuthorizedClient authorizedClient) { | |
if (this.authorizedClientRepository == null) { | |
return false; | |
} | |
OAuth2RefreshToken refreshToken = authorizedClient.getRefreshToken(); | |
if (refreshToken == null) { | |
return false; | |
} | |
Instant now = this.clock.instant(); | |
Instant expiresAt = authorizedClient.getAccessToken().getExpiresAt(); | |
if (expiresAt != null && now.isAfter(expiresAt.minus(this.accessTokenExpiresSkew))) { | |
return true; | |
} | |
return false; | |
} | |
private Mono<OAuth2AuthorizedClient> refreshAuthorizedClient(OAuth2AuthorizedClient authorizedClient, OAuth2AuthorizedClientResolver.Request r) { | |
ServerWebExchange exchange = r.getExchange(); | |
Authentication authentication = r.getAuthentication(); | |
ClientRegistration clientRegistration = authorizedClient | |
.getClientRegistration(); | |
String tokenUri = clientRegistration | |
.getProviderDetails().getTokenUri(); | |
ClientRequest refreshRequest = ClientRequest.create(HttpMethod.POST, URI.create(tokenUri)) | |
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) | |
.headers(headers -> headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret())) | |
.body(refreshTokenBody(authorizedClient.getRefreshToken().getTokenValue())) | |
.build(); | |
ExchangeFunction next = ExchangeFunctions.create(connector) ; | |
return next.exchange(refreshRequest) | |
.flatMap(refreshResponse -> refreshResponse.body(oauth2AccessTokenResponse())) | |
.map(accessTokenResponse -> { | |
OAuth2RefreshToken refreshToken = Optional.ofNullable(accessTokenResponse.getRefreshToken()) | |
.orElse(authorizedClient.getRefreshToken()); | |
return new OAuth2AuthorizedClient(authorizedClient.getClientRegistration(), authorizedClient.getPrincipalName(), accessTokenResponse.getAccessToken(), refreshToken); | |
}) | |
.flatMap(result -> this.authorizedClientRepository.saveAuthorizedClient(result, authentication, exchange) | |
.thenReturn(result)); | |
} | |
private static BodyInserters.FormInserter<String> refreshTokenBody(String refreshToken) { | |
return BodyInserters | |
.fromFormData("grant_type", AuthorizationGrantType.REFRESH_TOKEN.getValue()) | |
.with("refresh_token", refreshToken); | |
} | |
} |
<springcloud.version>Greenwich.SR3</springcloud.version>
<springboot.version>2.1.8.RELEASE</springboot.version>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
I couldn't find OAuth2AuthorizedClientResolver.
May I see your demo
I will try to upload an example project later.
Make sure sure didn't change the package of the class. It has to be:
package org.springframework.security.oauth2.client.web.reactive.function.client;
Thank you very much. I created a package org.springframework.security.oauth2.client.web.reactive.function.client; it can work。
Thank you very much。
my english is poor。
Thank you very much。 my code can refresh token
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It should be in this spring boot starter dependency:
Or this spring security dependency: