diff --git a/samples/users-resource/src/main/java/sample/authorization/DefaultTokenExchangeTokenResponseClient.java b/samples/users-resource/src/main/java/sample/authorization/DefaultTokenExchangeTokenResponseClient.java deleted file mode 100644 index 86e2b780..00000000 --- a/samples/users-resource/src/main/java/sample/authorization/DefaultTokenExchangeTokenResponseClient.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2020-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package sample.authorization; - -import java.util.Arrays; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.FormHttpMessageConverter; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.util.Assert; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; - -/** - * @author Steve Riesenberg - * @since 1.3 - */ -public final class DefaultTokenExchangeTokenResponseClient - implements OAuth2AccessTokenResponseClient { - - private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; - - private Converter> requestEntityConverter = new TokenExchangeGrantRequestEntityConverter(); - - private RestOperations restOperations; - - public DefaultTokenExchangeTokenResponseClient() { - RestTemplate restTemplate = new RestTemplate(Arrays.asList(new FormHttpMessageConverter(), - new OAuth2AccessTokenResponseHttpMessageConverter())); - restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); - this.restOperations = restTemplate; - } - - @Override - public OAuth2AccessTokenResponse getTokenResponse(TokenExchangeGrantRequest tokenExchangeGrantRequest) { - Assert.notNull(tokenExchangeGrantRequest, "tokenExchangeGrantRequest cannot be null"); - RequestEntity requestEntity = this.requestEntityConverter.convert(tokenExchangeGrantRequest); - ResponseEntity responseEntity = getResponse(requestEntity); - - return responseEntity.getBody(); - } - - private ResponseEntity getResponse(RequestEntity requestEntity) { - try { - return this.restOperations.exchange(requestEntity, OAuth2AccessTokenResponse.class); - } catch (RestClientException ex) { - OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE, - "An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " - + ex.getMessage(), null); - throw new OAuth2AuthorizationException(oauth2Error, ex); - } - } - - public void setRequestEntityConverter(Converter> requestEntityConverter) { - Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null"); - this.requestEntityConverter = requestEntityConverter; - } - - public void setRestOperations(RestOperations restOperations) { - Assert.notNull(restOperations, "restOperations cannot be null"); - this.restOperations = restOperations; - } - -} diff --git a/samples/users-resource/src/main/java/sample/authorization/TokenExchangeGrantRequest.java b/samples/users-resource/src/main/java/sample/authorization/TokenExchangeGrantRequest.java deleted file mode 100644 index fb52fb67..00000000 --- a/samples/users-resource/src/main/java/sample/authorization/TokenExchangeGrantRequest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package sample.authorization; - -import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.util.Assert; - -/** - * @author Steve Riesenberg - * @since 1.3 - */ -public final class TokenExchangeGrantRequest extends AbstractOAuth2AuthorizationGrantRequest { - - static final AuthorizationGrantType TOKEN_EXCHANGE = new AuthorizationGrantType( - "urn:ietf:params:oauth:grant-type:token-exchange"); - - private final String subjectToken; - - private final String actorToken; - - public TokenExchangeGrantRequest(ClientRegistration clientRegistration, String subjectToken, - String actorToken) { - super(TOKEN_EXCHANGE, clientRegistration); - Assert.hasText(subjectToken, "subjectToken cannot be empty"); - if (actorToken != null) { - Assert.hasText(actorToken, "actorToken cannot be empty"); - } - this.subjectToken = subjectToken; - this.actorToken = actorToken; - } - - public String getSubjectToken() { - return this.subjectToken; - } - - public String getActorToken() { - return this.actorToken; - } -} \ No newline at end of file diff --git a/samples/users-resource/src/main/java/sample/authorization/TokenExchangeGrantRequestEntityConverter.java b/samples/users-resource/src/main/java/sample/authorization/TokenExchangeGrantRequestEntityConverter.java deleted file mode 100644 index ba3fbfc5..00000000 --- a/samples/users-resource/src/main/java/sample/authorization/TokenExchangeGrantRequestEntityConverter.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2020-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package sample.authorization; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.RequestEntity; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.util.CollectionUtils; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * @author Steve Riesenberg - * @since 1.3 - */ -public class TokenExchangeGrantRequestEntityConverter implements Converter> { - - private static final String REQUESTED_TOKEN_TYPE = "requested_token_type"; - - private static final String SUBJECT_TOKEN = "subject_token"; - - private static final String SUBJECT_TOKEN_TYPE = "subject_token_type"; - - private static final String ACTOR_TOKEN = "actor_token"; - - private static final String ACTOR_TOKEN_TYPE = "actor_token_type"; - - private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; - - @Override - public RequestEntity convert(TokenExchangeGrantRequest grantRequest) { - ClientRegistration clientRegistration = grantRequest.getClientRegistration(); - - HttpHeaders headers = new HttpHeaders(); - if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) { - headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); - } - - MultiValueMap requestParameters = new LinkedMultiValueMap<>(); - requestParameters.add(OAuth2ParameterNames.GRANT_TYPE, grantRequest.getGrantType().getValue()); - requestParameters.add(REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - requestParameters.add(SUBJECT_TOKEN, grantRequest.getSubjectToken()); - requestParameters.add(SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - if (StringUtils.hasText(grantRequest.getActorToken())) { - requestParameters.add(ACTOR_TOKEN, grantRequest.getActorToken()); - requestParameters.add(ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - } - if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) { - requestParameters.add(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); - } - if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.CLIENT_SECRET_POST)) { - requestParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId()); - requestParameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret()); - } - - String tokenEndpointUri = clientRegistration.getProviderDetails().getTokenUri(); - return RequestEntity.post(tokenEndpointUri).headers(headers).body(requestParameters); - } - -} diff --git a/samples/users-resource/src/main/java/sample/authorization/TokenExchangeOAuth2AuthorizedClientProvider.java b/samples/users-resource/src/main/java/sample/authorization/TokenExchangeOAuth2AuthorizedClientProvider.java deleted file mode 100644 index 4bfd94da..00000000 --- a/samples/users-resource/src/main/java/sample/authorization/TokenExchangeOAuth2AuthorizedClientProvider.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2020-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package sample.authorization; - -import java.time.Clock; -import java.time.Duration; -import java.util.function.Function; - -import org.springframework.security.oauth2.client.ClientAuthorizationException; -import org.springframework.security.oauth2.client.OAuth2AuthorizationContext; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.util.Assert; - -/** - * @author Steve Riesenberg - * @since 1.3 - */ -public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider { - - private OAuth2AccessTokenResponseClient accessTokenResponseClient = new DefaultTokenExchangeTokenResponseClient(); - - private Function subjectTokenResolver = this::resolveSubjectToken; - - private Function actorTokenResolver = (context) -> null; - - private Duration clockSkew = Duration.ofSeconds(60); - - private Clock clock = Clock.systemUTC(); - - @Override - public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) { - Assert.notNull(context, "context cannot be null"); - ClientRegistration clientRegistration = context.getClientRegistration(); - if (!TokenExchangeGrantRequest.TOKEN_EXCHANGE.equals(clientRegistration.getAuthorizationGrantType())) { - return null; - } - OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient(); - if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) { - // If client is already authorized but access token is NOT expired than no - // need for re-authorization - return null; - } - if (authorizedClient != null && authorizedClient.getRefreshToken() != null) { - // If client is already authorized but access token is expired and a - // refresh token is available, delegate to refresh_token. - return null; - } - - TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, - this.subjectTokenResolver.apply(context), this.actorTokenResolver.apply(context)); - OAuth2AccessTokenResponse tokenResponse = getTokenResponse(clientRegistration, grantRequest); - - return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(), - tokenResponse.getAccessToken(), tokenResponse.getRefreshToken()); - } - - private OAuth2AccessTokenResponse getTokenResponse(ClientRegistration clientRegistration, - TokenExchangeGrantRequest grantRequest) { - try { - return this.accessTokenResponseClient.getTokenResponse(grantRequest); - } catch (OAuth2AuthorizationException ex) { - throw new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(), ex); - } - } - - private boolean hasTokenExpired(OAuth2Token token) { - return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew)); - } - - private String resolveSubjectToken(OAuth2AuthorizationContext context) { - if (context.getPrincipal().getPrincipal() instanceof OAuth2Token accessToken) { - return accessToken.getTokenValue(); - } - return null; - } - - public void setAccessTokenResponseClient(OAuth2AccessTokenResponseClient accessTokenResponseClient) { - Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null"); - this.accessTokenResponseClient = accessTokenResponseClient; - } - - public void setSubjectTokenResolver(Function subjectTokenResolver) { - Assert.notNull(subjectTokenResolver, "subjectTokenResolver cannot be null"); - this.subjectTokenResolver = subjectTokenResolver; - } - - public void setActorTokenResolver(Function actorTokenResolver) { - Assert.notNull(actorTokenResolver, "actorTokenResolver cannot be null"); - this.actorTokenResolver = actorTokenResolver; - } - - public void setClockSkew(Duration clockSkew) { - Assert.notNull(clockSkew, "clockSkew cannot be null"); - this.clockSkew = clockSkew; - } - - public void setClock(Clock clock) { - Assert.notNull(clock, "clock cannot be null"); - this.clock = clock; - } - -} diff --git a/samples/users-resource/src/main/java/sample/config/TokenExchangeConfig.java b/samples/users-resource/src/main/java/sample/config/TokenExchangeConfig.java index c454df0d..48ea043a 100644 --- a/samples/users-resource/src/main/java/sample/config/TokenExchangeConfig.java +++ b/samples/users-resource/src/main/java/sample/config/TokenExchangeConfig.java @@ -17,8 +17,6 @@ package sample.config; import java.util.function.Function; -import sample.authorization.TokenExchangeOAuth2AuthorizedClientProvider; - import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager; @@ -29,7 +27,9 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.util.Assert; /** @@ -48,8 +48,8 @@ public class TokenExchangeConfig { OAuth2AuthorizedClientManager authorizedClientManager = tokenExchangeAuthorizedClientManager( clientRegistrationRepository, authorizedClientService); - Function actorTokenResolver = createTokenResolver(authorizedClientManager, - ACTOR_TOKEN_CLIENT_REGISTRATION_ID); + Function actorTokenResolver = createTokenResolver( + authorizedClientManager, ACTOR_TOKEN_CLIENT_REGISTRATION_ID); TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider(); @@ -83,7 +83,7 @@ public class TokenExchangeConfig { /** * Create a {@code Function} to resolve a token from the current principal. */ - private static Function createTokenResolver( + private static Function createTokenResolver( OAuth2AuthorizedClientManager authorizedClientManager, String clientRegistrationId) { return (context) -> { @@ -97,7 +97,7 @@ public class TokenExchangeConfig { OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(authorizeRequest); Assert.notNull(authorizedClient, "authorizedClient cannot be null"); - return authorizedClient.getAccessToken().getTokenValue(); + return authorizedClient.getAccessToken(); }; }