-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Token Exchange grant to demo-client sample
Issue gh-60
- Loading branch information
Showing
15 changed files
with
693 additions
and
2 deletions.
There are no files selected for viewing
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
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
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
plugins { | ||
id "org.springframework.boot" version "3.2.2" | ||
id "io.spring.dependency-management" version "1.1.0" | ||
id "java" | ||
} | ||
|
||
group = project.rootProject.group | ||
version = project.rootProject.version | ||
sourceCompatibility = "17" | ||
|
||
repositories { | ||
mavenCentral() | ||
maven { url "https://repo.spring.io/milestone" } | ||
} | ||
|
||
dependencies { | ||
implementation "org.springframework.boot:spring-boot-starter-web" | ||
implementation "org.springframework.boot:spring-boot-starter-security" | ||
implementation "org.springframework.boot:spring-boot-starter-oauth2-resource-server" | ||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client" | ||
} |
32 changes: 32 additions & 0 deletions
32
samples/users-resource/src/main/java/sample/UsersResourceApplication.java
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* 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; | ||
|
||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
|
||
/** | ||
* @author Steve Riesenberg | ||
* @since 1.3 | ||
*/ | ||
@SpringBootApplication | ||
public class UsersResourceApplication { | ||
|
||
public static void main(String[] args) { | ||
SpringApplication.run(UsersResourceApplication.class, args); | ||
} | ||
|
||
} |
85 changes: 85 additions & 0 deletions
85
...-resource/src/main/java/sample/authorization/DefaultTokenExchangeTokenResponseClient.java
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/* | ||
* 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<TokenExchangeGrantRequest> { | ||
|
||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; | ||
|
||
private Converter<TokenExchangeGrantRequest, RequestEntity<?>> 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<OAuth2AccessTokenResponse> responseEntity = getResponse(requestEntity); | ||
|
||
return responseEntity.getBody(); | ||
} | ||
|
||
private ResponseEntity<OAuth2AccessTokenResponse> 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<TokenExchangeGrantRequest, RequestEntity<?>> 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; | ||
} | ||
|
||
} |
54 changes: 54 additions & 0 deletions
54
samples/users-resource/src/main/java/sample/authorization/TokenExchangeGrantRequest.java
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* 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; | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
...resource/src/main/java/sample/authorization/TokenExchangeGrantRequestEntityConverter.java
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* 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<TokenExchangeGrantRequest, RequestEntity<?>> { | ||
|
||
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<String, Object> 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); | ||
} | ||
|
||
} |
Oops, something went wrong.