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 029e829a55..13eb9eefda 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 @@ -16,7 +16,6 @@ package org.springframework.security.oauth2.core.endpoint; import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import java.time.Instant; @@ -83,11 +82,18 @@ public final class OAuth2AccessTokenResponse { } public OAuth2AccessTokenResponse build() { - Assert.isTrue(this.expiresIn >= 0, "expiresIn must be a positive number"); Instant issuedAt = Instant.now(); + + // 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); + OAuth2AccessTokenResponse accessTokenResponse = new OAuth2AccessTokenResponse(); - accessTokenResponse.accessToken = new OAuth2AccessToken(this.tokenType, this.tokenValue, issuedAt, - issuedAt.plusSeconds(this.expiresIn), this.scopes); + accessTokenResponse.accessToken = new OAuth2AccessToken( + this.tokenType, this.tokenValue, issuedAt, expiresAt, this.scopes); accessTokenResponse.additionalParameters = Collections.unmodifiableMap( CollectionUtils.isEmpty(this.additionalParameters) ? Collections.emptyMap() : this.additionalParameters); return accessTokenResponse; 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 7852ec07e8..2346347aad 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 @@ -53,12 +53,26 @@ public class OAuth2AccessTokenResponseTests { .build(); } - @Test(expected = IllegalArgumentException.class) - public void buildWhenExpiresInIsNegativeThenThrowIllegalArgumentException() { - OAuth2AccessTokenResponse.withToken(TOKEN_VALUE) + @Test + public void buildWhenExpiresInIsZeroThenExpiresAtOneSecondAfterIssueAt() { + OAuth2AccessTokenResponse tokenResponse = OAuth2AccessTokenResponse + .withToken(TOKEN_VALUE) + .tokenType(OAuth2AccessToken.TokenType.BEARER) + .expiresIn(0) + .build(); + assertThat(tokenResponse.getAccessToken().getExpiresAt()).isEqualTo( + tokenResponse.getAccessToken().getIssuedAt().plusSeconds(1)); + } + + @Test + public void buildWhenExpiresInIsNegativeThenExpiresAtOneSecondAfterIssueAt() { + OAuth2AccessTokenResponse tokenResponse = OAuth2AccessTokenResponse + .withToken(TOKEN_VALUE) .tokenType(OAuth2AccessToken.TokenType.BEARER) .expiresIn(-1L) .build(); + assertThat(tokenResponse.getAccessToken().getExpiresAt()).isEqualTo( + tokenResponse.getAccessToken().getIssuedAt().plusSeconds(1)); } @Test