diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java index 83015ee58b..29af067631 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java @@ -21,6 +21,7 @@ import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import java.time.Duration; import java.time.Instant; import java.util.Collections; import java.util.Map; @@ -81,6 +82,15 @@ public final class OAuth2AccessTokenResponse { return new Builder(tokenValue); } + /** + * Returns a new {@link Builder}, initialized with the provided response + * @param response the response to intialize the builder with + * @return the {@link Builder} + */ + public static Builder withResponse(OAuth2AccessTokenResponse response) { + return new Builder(response); + } + /** * A builder for {@link OAuth2AccessTokenResponse}. */ @@ -92,6 +102,21 @@ public final class OAuth2AccessTokenResponse { private String refreshToken; private Map additionalParameters; + private Instant issuedAt; + private Instant expiresAt; + + private Builder(OAuth2AccessTokenResponse response) { + OAuth2AccessToken accessToken = response.getAccessToken(); + this.tokenValue = accessToken.getTokenValue(); + this.tokenType = accessToken.getTokenType(); + this.expiresAt = accessToken.getExpiresAt(); + this.issuedAt = accessToken.getIssuedAt(); + this.scopes = accessToken.getScopes(); + this.refreshToken = response.getRefreshToken() == null ? + null : response.getRefreshToken().getTokenValue(); + this.additionalParameters = response.getAdditionalParameters(); + } + private Builder(String tokenValue) { this.tokenValue = tokenValue; } @@ -157,14 +182,9 @@ public final class OAuth2AccessTokenResponse { * @return a {@link OAuth2AccessTokenResponse} */ public OAuth2AccessTokenResponse build() { - Instant issuedAt = Instant.now(); + Instant issuedAt = getIssuedAt(); - // expires_in is RECOMMENDED, as per spec https://tools.ietf.org/html/rfc6749#section-5.1 - // Therefore, expires_in may not be returned in the Access Token response which would result in the default value of 0. - // For these instances, default the expiresAt to +1 second from issuedAt time. - Instant expiresAt = this.expiresIn > 0 ? - issuedAt.plusSeconds(this.expiresIn) : - issuedAt.plusSeconds(1); + Instant expiresAt = getExpiresAt(); OAuth2AccessTokenResponse accessTokenResponse = new OAuth2AccessTokenResponse(); accessTokenResponse.accessToken = new OAuth2AccessToken( @@ -181,5 +201,28 @@ public final class OAuth2AccessTokenResponse { CollectionUtils.isEmpty(this.additionalParameters) ? Collections.emptyMap() : this.additionalParameters); return accessTokenResponse; } + + private Instant getIssuedAt() { + if (this.issuedAt == null) { + this.issuedAt = Instant.now(); + } + return this.issuedAt; + } + + /** + * expires_in is RECOMMENDED, as per spec https://tools.ietf.org/html/rfc6749#section-5.1 + * Therefore, expires_in may not be returned in the Access Token response which would result in the default value of 0. + * For these instances, default the expiresAt to +1 second from issuedAt time. + * @return + */ + private Instant getExpiresAt() { + if (this.expiresAt == null) { + Instant issuedAt = getIssuedAt(); + this.expiresAt = this.expiresIn > 0 ? + issuedAt.plusSeconds(this.expiresIn) : + issuedAt.plusSeconds(1); + } + return this.expiresAt; + } } } diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponseTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponseTests.java index cfd5a0dedf..fe2b948336 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponseTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponseTests.java @@ -102,4 +102,55 @@ public class OAuth2AccessTokenResponseTests { assertThat(tokenResponse.getRefreshToken().getTokenValue()).isEqualTo(REFRESH_TOKEN_VALUE); assertThat(tokenResponse.getAdditionalParameters()).isEqualTo(additionalParameters); } + + @Test + public void buildWhenResponseThenAllAttributesAreSet() { + Instant expiresAt = Instant.now().plusSeconds(5); + Set scopes = new LinkedHashSet<>(Arrays.asList("scope1", "scope2")); + Map additionalParameters = new HashMap<>(); + additionalParameters.put("param1", "value1"); + additionalParameters.put("param2", "value2"); + + OAuth2AccessTokenResponse tokenResponse = OAuth2AccessTokenResponse + .withToken(TOKEN_VALUE) + .tokenType(OAuth2AccessToken.TokenType.BEARER) + .expiresIn(expiresAt.toEpochMilli()) + .scopes(scopes) + .refreshToken(REFRESH_TOKEN_VALUE) + .additionalParameters(additionalParameters) + .build(); + + OAuth2AccessTokenResponse withResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse) + .build(); + + assertThat(withResponse.getAccessToken().getTokenValue()).isEqualTo(tokenResponse.getAccessToken().getTokenValue()); + assertThat(withResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); + assertThat(withResponse.getAccessToken().getIssuedAt()).isEqualTo(tokenResponse.getAccessToken().getIssuedAt()); + assertThat(withResponse.getAccessToken().getExpiresAt()).isEqualTo(tokenResponse.getAccessToken().getExpiresAt()); + assertThat(withResponse.getAccessToken().getScopes()).isEqualTo(tokenResponse.getAccessToken().getScopes()); + assertThat(withResponse.getRefreshToken().getTokenValue()).isEqualTo(tokenResponse.getRefreshToken().getTokenValue()); + assertThat(withResponse.getAdditionalParameters()).isEqualTo(tokenResponse.getAdditionalParameters()); + } + + @Test + public void buildWhenResponseAndRefreshNullThenRefreshNull() { + Instant expiresAt = Instant.now().plusSeconds(5); + Set scopes = new LinkedHashSet<>(Arrays.asList("scope1", "scope2")); + Map additionalParameters = new HashMap<>(); + additionalParameters.put("param1", "value1"); + additionalParameters.put("param2", "value2"); + + OAuth2AccessTokenResponse tokenResponse = OAuth2AccessTokenResponse + .withToken(TOKEN_VALUE) + .tokenType(OAuth2AccessToken.TokenType.BEARER) + .expiresIn(expiresAt.toEpochMilli()) + .scopes(scopes) + .additionalParameters(additionalParameters) + .build(); + + OAuth2AccessTokenResponse withResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse) + .build(); + + assertThat(withResponse.getRefreshToken()).isNull(); + } }