Explorar o código

Polish gh-128

Joe Grandja %!s(int64=5) %!d(string=hai) anos
pai
achega
a2167a5091
Modificáronse 14 ficheiros con 154 adicións e 143 borrados
  1. 6 4
      oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java
  2. 3 7
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java
  3. 9 9
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationToken.java
  4. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java
  5. 36 31
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java
  6. 25 20
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java
  7. 13 14
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIssuerUtil.java
  8. 25 24
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/TokenSettings.java
  9. 20 18
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java
  10. 4 4
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java
  11. 4 4
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationTokenTests.java
  12. 4 4
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/TokenSettingsTests.java
  13. 3 2
      samples/boot/oauth2-integration/authorizationserver/spring-security-samples-boot-oauth2-integrated-authorizationserver.gradle
  14. 1 1
      samples/boot/oauth2-integration/client/src/main/java/sample/config/WebClientConfig.java

+ 6 - 4
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java

@@ -144,16 +144,18 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 						jwtEncoder);
 		builder.authenticationProvider(postProcess(authorizationCodeAuthenticationProvider));
 
+		OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =
+				new OAuth2RefreshTokenAuthenticationProvider(
+						getAuthorizationService(builder),
+						jwtEncoder);
+		builder.authenticationProvider(postProcess(refreshTokenAuthenticationProvider));
+
 		OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
 				new OAuth2ClientCredentialsAuthenticationProvider(
 						getAuthorizationService(builder),
 						jwtEncoder);
 		builder.authenticationProvider(postProcess(clientCredentialsAuthenticationProvider));
 
-		OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =
-				new OAuth2RefreshTokenAuthenticationProvider(getAuthorizationService(builder), jwtEncoder);
-		builder.authenticationProvider(postProcess(refreshTokenAuthenticationProvider));
-
 		OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider =
 				new OAuth2TokenRevocationAuthenticationProvider(
 						getAuthorizationService(builder));

+ 3 - 7
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java

@@ -66,16 +66,12 @@ public final class InMemoryOAuth2AuthorizationService implements OAuth2Authoriza
 		} else if (TokenType.AUTHORIZATION_CODE.equals(tokenType)) {
 			OAuth2AuthorizationCode authorizationCode = authorization.getTokens().getToken(OAuth2AuthorizationCode.class);
 			return authorizationCode != null && authorizationCode.getTokenValue().equals(token);
-		}
-
-		if (TokenType.ACCESS_TOKEN.equals(tokenType)) {
+		} else if (TokenType.ACCESS_TOKEN.equals(tokenType)) {
 			return authorization.getTokens().getAccessToken() != null &&
 					authorization.getTokens().getAccessToken().getTokenValue().equals(token);
-		}
-
-		if (TokenType.REFRESH_TOKEN.equals(tokenType)) {
+		} else if (TokenType.REFRESH_TOKEN.equals(tokenType)) {
 			return authorization.getTokens().getRefreshToken() != null &&
-						authorization.getTokens().getRefreshToken().getTokenValue().equals(token);
+					authorization.getTokens().getRefreshToken().getTokenValue().equals(token);
 		}
 
 		return false;

+ 9 - 9
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationToken.java

@@ -18,24 +18,25 @@ package org.springframework.security.oauth2.server.authorization.authentication;
 import org.springframework.lang.Nullable;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.core.Authentication;
-import org.springframework.security.oauth2.server.authorization.Version;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
+import org.springframework.security.oauth2.server.authorization.Version;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.util.Assert;
 
 import java.util.Collections;
 
 /**
- * An {@link Authentication} implementation used when issuing an OAuth 2.0 Access Token.
+ * An {@link Authentication} implementation used when issuing an
+ * OAuth 2.0 Access Token and (optional) Refresh Token.
  *
  * @author Joe Grandja
  * @author Madhu Bhat
  * @since 0.0.1
  * @see AbstractAuthenticationToken
- * @see OAuth2AuthorizationCodeAuthenticationProvider
  * @see RegisteredClient
  * @see OAuth2AccessToken
+ * @see OAuth2RefreshToken
  * @see OAuth2ClientAuthenticationToken
  */
 public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthenticationToken {
@@ -65,8 +66,8 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
 	 * @param accessToken the access token
 	 * @param refreshToken the refresh token
 	 */
-	public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient,
-			Authentication clientPrincipal, OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
+	public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient, Authentication clientPrincipal,
+			OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
 		super(Collections.emptyList());
 		Assert.notNull(registeredClient, "registeredClient cannot be null");
 		Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
@@ -105,14 +106,13 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
 		return this.accessToken;
 	}
 
-
 	/**
-	 * Returns the {@link OAuth2RefreshToken} if provided
+	 * Returns the {@link OAuth2RefreshToken refresh token}.
 	 *
-	 * @return the {@link OAuth2RefreshToken}
+	 * @return the {@link OAuth2RefreshToken} or {@code null} if not available
 	 */
 	@Nullable
 	public OAuth2RefreshToken getRefreshToken() {
-		return refreshToken;
+		return this.refreshToken;
 	}
 }

+ 1 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java

@@ -97,9 +97,9 @@ public class OAuth2ClientCredentialsAuthenticationProvider implements Authentica
 				jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), scopes);
 
 		OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
-				.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
 				.principalName(clientPrincipal.getName())
 				.tokens(OAuth2Tokens.builder().accessToken(accessToken).build())
+				.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
 				.build();
 		this.authorizationService.save(authorization);
 

+ 36 - 31
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java

@@ -13,12 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.springframework.security.oauth2.server.authorization.authentication;
 
-import java.time.Instant;
-import java.util.Set;
-
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
@@ -38,6 +34,9 @@ import org.springframework.security.oauth2.server.authorization.config.TokenSett
 import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
 import org.springframework.util.Assert;
 
+import java.time.Instant;
+import java.util.Set;
+
 /**
  * An {@link AuthenticationProvider} implementation for the OAuth 2.0 Refresh Token Grant.
  *
@@ -47,19 +46,23 @@ import org.springframework.util.Assert;
  * @see OAuth2AccessTokenAuthenticationToken
  * @see OAuth2AuthorizationService
  * @see JwtEncoder
- * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.5">Section 1.5 Refresh Token</a>
+ * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.5">Section 1.5 Refresh Token Grant</a>
  * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-6">Section 6 Refreshing an Access Token</a>
  */
-
 public class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationProvider {
-
 	private final OAuth2AuthorizationService authorizationService;
 	private final JwtEncoder jwtEncoder;
 
-	public OAuth2RefreshTokenAuthenticationProvider(OAuth2AuthorizationService authorizationService, JwtEncoder jwtEncoder) {
+	/**
+	 * Constructs an {@code OAuth2RefreshTokenAuthenticationProvider} using the provided parameters.
+	 *
+	 * @param authorizationService the authorization service
+	 * @param jwtEncoder the jwt encoder
+	 */
+	public OAuth2RefreshTokenAuthenticationProvider(OAuth2AuthorizationService authorizationService,
+			JwtEncoder jwtEncoder) {
 		Assert.notNull(authorizationService, "authorizationService cannot be null");
 		Assert.notNull(jwtEncoder, "jwtEncoder cannot be null");
-
 		this.authorizationService = authorizationService;
 		this.jwtEncoder = jwtEncoder;
 	}
@@ -73,43 +76,45 @@ public class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationP
 		if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(refreshTokenAuthentication.getPrincipal().getClass())) {
 			clientPrincipal = (OAuth2ClientAuthenticationToken) refreshTokenAuthentication.getPrincipal();
 		}
-
 		if (clientPrincipal == null || !clientPrincipal.isAuthenticated()) {
 			throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
 		}
+		RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
 
-		OAuth2Authorization authorization = this.authorizationService.findByToken(refreshTokenAuthentication.getRefreshToken(), TokenType.REFRESH_TOKEN);
+		OAuth2Authorization authorization = this.authorizationService.findByToken(
+				refreshTokenAuthentication.getRefreshToken(), TokenType.REFRESH_TOKEN);
 		if (authorization == null) {
 			throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
 		}
 
-		Instant refreshTokenExpiration = authorization.getTokens().getRefreshToken().getExpiresAt();
-		if (refreshTokenExpiration.isBefore(Instant.now())) {
-			// as per https://tools.ietf.org/html/rfc6749#section-5.2
-			// invalid_grant: The provided authorization grant (e.g., authorization
-			// code, resource owner credentials) or refresh token is invalid, expired, revoked [...].
-			throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
+		if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) {
+			throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
 		}
 
-		RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
+		Instant refreshTokenExpiresAt = authorization.getTokens().getRefreshToken().getExpiresAt();
+		if (refreshTokenExpiresAt.isBefore(Instant.now())) {
+			// As per https://tools.ietf.org/html/rfc6749#section-5.2
+			// invalid_grant: The provided authorization grant (e.g., authorization code,
+			// resource owner credentials) or refresh token is invalid, expired, revoked [...].
+			throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
+		}
 
-		// https://tools.ietf.org/html/rfc6749#section-6
+		// As per https://tools.ietf.org/html/rfc6749#section-6
 		// The requested scope MUST NOT include any scope not originally granted by the resource owner,
 		// and if omitted is treated as equal to the scope originally granted by the resource owner.
-		Set<String> refreshTokenScopes = refreshTokenAuthentication.getScopes();
+		Set<String> scopes = refreshTokenAuthentication.getScopes();
 		Set<String> authorizedScopes = authorization.getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES);
-		if (!authorizedScopes.containsAll(refreshTokenScopes)) {
+		if (!authorizedScopes.containsAll(scopes)) {
 			throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE));
 		}
-
-		if (refreshTokenScopes.isEmpty()) {
-			refreshTokenScopes = authorizedScopes;
+		if (scopes.isEmpty()) {
+			scopes = authorizedScopes;
 		}
 
 		Jwt jwt = OAuth2TokenIssuerUtil
-			.issueJwtAccessToken(this.jwtEncoder, authorization.getPrincipalName(), registeredClient.getClientId(), refreshTokenScopes);
+			.issueJwtAccessToken(this.jwtEncoder, authorization.getPrincipalName(), registeredClient.getClientId(), scopes);
 		OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-			jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), refreshTokenScopes);
+			jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), scopes);
 
 		TokenSettings tokenSettings = registeredClient.getTokenSettings();
 		OAuth2RefreshToken refreshToken;
@@ -120,13 +125,13 @@ public class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationP
 		}
 
 		authorization = OAuth2Authorization.from(authorization)
-								.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
-								.tokens(OAuth2Tokens.builder().accessToken(accessToken).refreshToken(refreshToken).build())
-								.build();
-
+				.tokens(OAuth2Tokens.builder().accessToken(accessToken).refreshToken(refreshToken).build())
+				.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
+				.build();
 		this.authorizationService.save(authorization);
 
-		return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken);
+		return new OAuth2AccessTokenAuthenticationToken(
+				registeredClient, clientPrincipal, accessToken, refreshToken);
 	}
 
 	@Override

+ 25 - 20
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java

@@ -13,16 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.springframework.security.oauth2.server.authorization.authentication;
 
-import java.util.Collections;
-import java.util.Set;
-
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.server.authorization.Version;
 import org.springframework.util.Assert;
 
+import java.util.Collections;
+import java.util.Set;
+
 /**
  * An {@link Authentication} implementation used for the OAuth 2.0 Refresh Token Grant.
  *
@@ -33,49 +33,54 @@ import org.springframework.util.Assert;
  * @see OAuth2ClientAuthenticationToken
  */
 public class OAuth2RefreshTokenAuthenticationToken extends AbstractAuthenticationToken {
-
-	private final Authentication clientPrincipal;
+	private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
 	private final String refreshToken;
+	private final Authentication clientPrincipal;
 	private final Set<String> scopes;
 
 	/**
 	 * Constructs an {@code OAuth2RefreshTokenAuthenticationToken} using the provided parameters.
 	 *
-	 * @param refreshToken refresh token value
+	 * @param refreshToken the refresh token
 	 * @param clientPrincipal the authenticated client principal
 	 */
 	public OAuth2RefreshTokenAuthenticationToken(String refreshToken, Authentication clientPrincipal) {
-		this(clientPrincipal, refreshToken, Collections.emptySet());
+		this(refreshToken, clientPrincipal, Collections.emptySet());
 	}
 
 	/**
 	 * Constructs an {@code OAuth2RefreshTokenAuthenticationToken} using the provided parameters.
 	 *
+	 * @param refreshToken the refresh token
 	 * @param clientPrincipal the authenticated client principal
-	 * @param refreshToken refresh token value
-	 * @param requestedScopes scopes requested by refresh token
+	 * @param scopes the requested scope(s)
 	 */
-	public OAuth2RefreshTokenAuthenticationToken(Authentication clientPrincipal, String refreshToken, Set<String> requestedScopes) {
+	public OAuth2RefreshTokenAuthenticationToken(String refreshToken, Authentication clientPrincipal,
+			Set<String> scopes) {
 		super(Collections.emptySet());
-
+		Assert.hasText(refreshToken, "refreshToken cannot be empty");
 		Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
-		Assert.hasText(refreshToken, "refreshToken cannot be null or empty");
-
-		this.clientPrincipal = clientPrincipal;
+		Assert.notNull(scopes, "scopes cannot be null");
 		this.refreshToken = refreshToken;
-		this.scopes = requestedScopes;
+		this.clientPrincipal = clientPrincipal;
+		this.scopes = scopes;
 	}
 
 	@Override
-	public Object getCredentials() {
-		return "";
+	public Object getPrincipal() {
+		return this.clientPrincipal;
 	}
 
 	@Override
-	public Object getPrincipal() {
-		return this.clientPrincipal;
+	public Object getCredentials() {
+		return "";
 	}
 
+	/**
+	 * Returns the refresh token.
+	 *
+	 * @return the refresh token
+	 */
 	public String getRefreshToken() {
 		return this.refreshToken;
 	}

+ 13 - 14
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIssuerUtil.java

@@ -13,19 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.springframework.security.oauth2.server.authorization.authentication;
 
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URL;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.Set;
-
 import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
 import org.springframework.security.crypto.keygen.StringKeyGenerator;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
@@ -36,13 +25,23 @@ import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtClaimsSet;
 import org.springframework.security.oauth2.jwt.JwtEncoder;
 
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Set;
+
 /**
  * @author Alexey Nesterov
  * @since 0.0.3
  */
 class OAuth2TokenIssuerUtil {
 
-	private static final StringKeyGenerator CODE_GENERATOR = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
+	private static final StringKeyGenerator TOKEN_GENERATOR = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
 
 	static Jwt issueJwtAccessToken(JwtEncoder jwtEncoder, String subject, String audience, Set<String> scopes) {
 		JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
@@ -71,8 +70,8 @@ class OAuth2TokenIssuerUtil {
 
 	static OAuth2RefreshToken issueRefreshToken(Duration refreshTokenTimeToLive) {
 		Instant issuedAt = Instant.now();
-		Instant refreshTokenExpiresAt = issuedAt.plus(refreshTokenTimeToLive);
+		Instant expiresAt = issuedAt.plus(refreshTokenTimeToLive);
 
-		return new OAuth2RefreshToken(CODE_GENERATOR.generateKey(), issuedAt, refreshTokenExpiresAt);
+		return new OAuth2RefreshToken(TOKEN_GENERATOR.generateKey(), issuedAt, expiresAt);
 	}
 }

+ 25 - 24
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/TokenSettings.java

@@ -15,12 +15,12 @@
  */
 package org.springframework.security.oauth2.server.authorization.config;
 
+import org.springframework.util.Assert;
+
 import java.time.Duration;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.springframework.util.Assert;
-
 /**
  * A facility for token configuration settings.
  *
@@ -61,33 +61,31 @@ public class TokenSettings extends Settings {
 	}
 
 	/**
-	 * Set the time-to-live for an access token.
+	 * Set the time-to-live for an access token. Must be greater than {@code Duration.ZERO}.
 	 *
 	 * @param accessTokenTimeToLive the time-to-live for an access token
 	 * @return the {@link TokenSettings}
 	 */
 	public TokenSettings accessTokenTimeToLive(Duration accessTokenTimeToLive) {
+		Assert.notNull(accessTokenTimeToLive, "accessTokenTimeToLive cannot be null");
+		Assert.isTrue(accessTokenTimeToLive.getSeconds() > 0, "accessTokenTimeToLive must be greater than Duration.ZERO");
 		setting(ACCESS_TOKEN_TIME_TO_LIVE, accessTokenTimeToLive);
 		return this;
 	}
 
 	/**
-	 * Returns {@code true} if refresh tokens support is enabled.
-	 * This include generation of refresh token as a part of Authorization Code Grant flow and support of Refresh Token
-	 * Grant flow. The default is {@code true}.
+	 * Returns {@code true} if refresh tokens are enabled. The default is {@code true}.
 	 *
-	 * @return {@code true} if the client support refresh token, {@code false} otherwise
+	 * @return {@code true} if refresh tokens are enabled, {@code false} otherwise
 	 */
 	public boolean enableRefreshTokens() {
 		return setting(ENABLE_REFRESH_TOKENS);
 	}
 
 	/**
-	 * Set to {@code true} to enable refresh tokens support.
-	 * This include generation of refresh token as a part of Authorization Code Grant flow and support of Refresh Token
-	 * Grant flow.
+	 * Set to {@code true} to enable refresh tokens.
 	 *
-	 * @param enableRefreshTokens {@code true} to enable refresh token grant support, {@code false} otherwise
+	 * @param enableRefreshTokens {@code true} to enable refresh tokens, {@code false} otherwise
 	 * @return the {@link TokenSettings}
 	 */
 	public TokenSettings enableRefreshTokens(boolean enableRefreshTokens) {
@@ -96,18 +94,19 @@ public class TokenSettings extends Settings {
 	}
 
 	/**
-	 * Returns {@code true} if existing refresh token is re-used when a new access token is requested via Refresh Token grant,
-	 * or {@code false} if a new refresh token is generated.
-	 * The default is {@code false}.
+	 * Returns {@code true} if refresh tokens are reused when returning the access token response,
+	 * or {@code false} if a new refresh token is issued. The default is {@code true}.
 	 */
 	public boolean reuseRefreshTokens() {
 		return setting(REUSE_REFRESH_TOKENS);
 	}
 
 	/**
-	 * Set to {@code true} to re-use existing refresh token when new access token is requested via Refresh Token grant,
-	 * or to {@code false} to generate a new refresh token.
-	 * @param reuseRefreshTokens {@code true} to re-use existing refresh token, {@code false} to generate a new one
+	 * Set to {@code true} if refresh tokens are reused when returning the access token response,
+	 * or {@code false} if a new refresh token is issued.
+	 *
+	 * @param reuseRefreshTokens {@code true} to reuse refresh tokens, {@code false} to issue new refresh tokens
+	 * @return the {@link TokenSettings}
 	 */
 	public TokenSettings reuseRefreshTokens(boolean reuseRefreshTokens) {
 		setting(REUSE_REFRESH_TOKENS, reuseRefreshTokens);
@@ -115,21 +114,23 @@ public class TokenSettings extends Settings {
 	}
 
 	/**
-	 * Returns refresh token time-to-live. The default is 60 minutes. Always greater than {@code Duration.ZERO}.
-	 * @return refresh token time-to-live
+	 * Returns the time-to-live for a refresh token. The default is 60 minutes.
+	 *
+	 * @return the time-to-live for a refresh token
 	 */
 	public Duration refreshTokenTimeToLive() {
 		return setting(REFRESH_TOKEN_TIME_TO_LIVE);
 	}
 
 	/**
-	 * Sets refresh token time-to-live.
-	 * @param refreshTokenTimeToLive refresh token time-to-live. Has to be greater than {@code Duration.ZERO}.
+	 * Set the time-to-live for a refresh token. Must be greater than {@code Duration.ZERO}.
+	 *
+	 * @param refreshTokenTimeToLive the time-to-live for a refresh token
+	 * @return the {@link TokenSettings}
 	 */
 	public TokenSettings refreshTokenTimeToLive(Duration refreshTokenTimeToLive) {
 		Assert.notNull(refreshTokenTimeToLive, "refreshTokenTimeToLive cannot be null");
-		Assert.isTrue(refreshTokenTimeToLive.getSeconds() > 0, "refreshTokenTimeToLive has to be greater than Duration.ZERO");
-
+		Assert.isTrue(refreshTokenTimeToLive.getSeconds() > 0, "refreshTokenTimeToLive must be greater than Duration.ZERO");
 		setting(REFRESH_TOKEN_TIME_TO_LIVE, refreshTokenTimeToLive);
 		return this;
 	}
@@ -138,7 +139,7 @@ public class TokenSettings extends Settings {
 		Map<String, Object> settings = new HashMap<>();
 		settings.put(ACCESS_TOKEN_TIME_TO_LIVE, Duration.ofMinutes(5));
 		settings.put(ENABLE_REFRESH_TOKENS, true);
-		settings.put(REUSE_REFRESH_TOKENS, false);
+		settings.put(REUSE_REFRESH_TOKENS, true);
 		settings.put(REFRESH_TOKEN_TIME_TO_LIVE, Duration.ofMinutes(60));
 		return settings;
 	}

+ 20 - 18
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java

@@ -130,8 +130,8 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
 		this.tokenEndpointMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name());
 		Map<AuthorizationGrantType, Converter<HttpServletRequest, Authentication>> converters = new HashMap<>();
 		converters.put(AuthorizationGrantType.AUTHORIZATION_CODE, new AuthorizationCodeAuthenticationConverter());
-		converters.put(AuthorizationGrantType.CLIENT_CREDENTIALS, new ClientCredentialsAuthenticationConverter());
 		converters.put(AuthorizationGrantType.REFRESH_TOKEN, new RefreshTokenAuthenticationConverter());
+		converters.put(AuthorizationGrantType.CLIENT_CREDENTIALS, new ClientCredentialsAuthenticationConverter());
 		this.authorizationGrantAuthenticationConverter = new DelegatingAuthorizationGrantAuthenticationConverter(converters);
 	}
 
@@ -165,7 +165,9 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
 		}
 	}
 
-	private void sendAccessTokenResponse(HttpServletResponse response, OAuth2AccessToken accessToken, OAuth2RefreshToken refreshToken) throws IOException {
+	private void sendAccessTokenResponse(HttpServletResponse response, OAuth2AccessToken accessToken,
+			OAuth2RefreshToken refreshToken) throws IOException {
+
 		OAuth2AccessTokenResponse.Builder builder =
 				OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
 						.tokenType(accessToken.getTokenType())
@@ -235,13 +237,13 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
 		}
 	}
 
-	private static class ClientCredentialsAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
+	private static class RefreshTokenAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
 
 		@Override
 		public Authentication convert(HttpServletRequest request) {
 			// grant_type (REQUIRED)
 			String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
-			if (!AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(grantType)) {
+			if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) {
 				return null;
 			}
 
@@ -249,6 +251,13 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
 
 			MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
 
+			// refresh_token (REQUIRED)
+			String refreshToken = parameters.getFirst(OAuth2ParameterNames.REFRESH_TOKEN);
+			if (!StringUtils.hasText(refreshToken) ||
+					parameters.get(OAuth2ParameterNames.REFRESH_TOKEN).size() != 1) {
+				throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REFRESH_TOKEN);
+			}
+
 			// scope (OPTIONAL)
 			String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
 			if (StringUtils.hasText(scope) &&
@@ -258,20 +267,20 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
 			if (StringUtils.hasText(scope)) {
 				Set<String> requestedScopes = new HashSet<>(
 						Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
-				return new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScopes);
+				return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal, requestedScopes);
 			}
 
-			return new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
+			return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal);
 		}
 	}
 
-	private static class RefreshTokenAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
+	private static class ClientCredentialsAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
 
 		@Override
 		public Authentication convert(HttpServletRequest request) {
 			// grant_type (REQUIRED)
 			String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
-			if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) {
+			if (!AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(grantType)) {
 				return null;
 			}
 
@@ -279,26 +288,19 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
 
 			MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
 
-			// refresh token (REQUIRED)
-			String refreshToken = parameters.getFirst(OAuth2ParameterNames.REFRESH_TOKEN);
-			if (StringUtils.hasText(refreshToken) &&
-					parameters.get(OAuth2ParameterNames.REFRESH_TOKEN).size() != 1) {
-				throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REFRESH_TOKEN);
-			}
-
 			// scope (OPTIONAL)
 			String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
 			if (StringUtils.hasText(scope) &&
-						parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
+					parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
 				throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
 			}
 			if (StringUtils.hasText(scope)) {
 				Set<String> requestedScopes = new HashSet<>(
 						Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
-				return new OAuth2RefreshTokenAuthenticationToken(clientPrincipal, refreshToken, requestedScopes);
+				return new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScopes);
 			}
 
-			return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal);
+			return new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
 		}
 	}
 }

+ 4 - 4
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java

@@ -162,7 +162,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		when(this.authorizationService.findByToken(REFRESH_TOKEN_VALUE, TokenType.REFRESH_TOKEN))
 				.thenReturn(this.authorization);
 
-		RegisteredClient clientWithReuseTokensTrue = TestRegisteredClients.registeredClient()
+		RegisteredClient clientWithReuseTokensTrue = TestRegisteredClients.registeredClient2()
 				.tokenSettings(tokenSettings -> tokenSettings.reuseRefreshTokens(true))
 				.build();
 
@@ -183,7 +183,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		when(this.authorizationService.findByToken(REFRESH_TOKEN_VALUE, TokenType.REFRESH_TOKEN))
 				.thenReturn(this.authorization);
 
-		RegisteredClient clientWithReuseTokensFalse = TestRegisteredClients.registeredClient()
+		RegisteredClient clientWithReuseTokensFalse = TestRegisteredClients.registeredClient2()
 															.tokenSettings(tokenSettings -> tokenSettings.reuseRefreshTokens(false))
 															.build();
 
@@ -208,7 +208,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		requestedScopes.add("openid");
 
 		OAuth2RefreshTokenAuthenticationToken tokenWithScopes
-				= new OAuth2RefreshTokenAuthenticationToken(this.clientPrincipal, REFRESH_TOKEN_VALUE, requestedScopes);
+				= new OAuth2RefreshTokenAuthenticationToken(REFRESH_TOKEN_VALUE, this.clientPrincipal, requestedScopes);
 
 		when(this.authorizationService.findByToken(REFRESH_TOKEN_VALUE, TokenType.REFRESH_TOKEN))
 				.thenReturn(this.authorization);
@@ -227,7 +227,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		requestedScopes.add("another-scope");
 
 		OAuth2RefreshTokenAuthenticationToken tokenWithScopes
-				= new OAuth2RefreshTokenAuthenticationToken(this.clientPrincipal, REFRESH_TOKEN_VALUE, requestedScopes);
+				= new OAuth2RefreshTokenAuthenticationToken(REFRESH_TOKEN_VALUE, this.clientPrincipal, requestedScopes);
 
 		when(this.authorizationService.findByToken(REFRESH_TOKEN_VALUE, TokenType.REFRESH_TOKEN))
 				.thenReturn(this.authorization);

+ 4 - 4
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationTokenTests.java

@@ -34,7 +34,7 @@ public class OAuth2RefreshTokenAuthenticationTokenTests {
 
 	@Test
 	public void constructorWhenClientPrincipalNullThrowException() {
-		assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("", null))
+		assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("test", null))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("clientPrincipal cannot be null");
 	}
@@ -43,18 +43,18 @@ public class OAuth2RefreshTokenAuthenticationTokenTests {
 	public void constructorWhenRefreshTokenNullOrEmptyThrowException() {
 		assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken(null, mock(OAuth2ClientAuthenticationToken.class)))
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("refreshToken cannot be null or empty");
+				.hasMessage("refreshToken cannot be empty");
 
 		assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("", mock(OAuth2ClientAuthenticationToken.class)))
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("refreshToken cannot be null or empty");
+				.hasMessage("refreshToken cannot be empty");
 	}
 
 	@Test
 	public void constructorWhenGettingScopesThenReturnRequestedScopes() {
 		Set<String> expectedScopes = new HashSet<>(Arrays.asList("scope-a", "scope-b"));
 		OAuth2RefreshTokenAuthenticationToken token
-				= new OAuth2RefreshTokenAuthenticationToken(mock(OAuth2ClientAuthenticationToken.class), "test", expectedScopes);
+				= new OAuth2RefreshTokenAuthenticationToken("test", mock(OAuth2ClientAuthenticationToken.class), expectedScopes);
 
 		assertThat(token.getScopes()).containsAll(expectedScopes);
 	}

+ 4 - 4
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/TokenSettingsTests.java

@@ -35,7 +35,7 @@ public class TokenSettingsTests {
 		assertThat(tokenSettings.settings()).hasSize(4);
 		assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(5));
 		assertThat(tokenSettings.enableRefreshTokens()).isTrue();
-		assertThat(tokenSettings.reuseRefreshTokens()).isEqualTo(false);
+		assertThat(tokenSettings.reuseRefreshTokens()).isEqualTo(true);
 		assertThat(tokenSettings.refreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60));
 	}
 
@@ -83,12 +83,12 @@ public class TokenSettingsTests {
 		assertThatThrownBy(() -> new TokenSettings().refreshTokenTimeToLive(Duration.ZERO))
 				.isInstanceOf(IllegalArgumentException.class)
 				.extracting(Throwable::getMessage)
-				.isEqualTo("refreshTokenTimeToLive has to be greater than Duration.ZERO");
+				.isEqualTo("refreshTokenTimeToLive must be greater than Duration.ZERO");
 
 		assertThatThrownBy(() -> new TokenSettings().refreshTokenTimeToLive(Duration.ofSeconds(-10)))
 				.isInstanceOf(IllegalArgumentException.class)
 				.extracting(Throwable::getMessage)
-				.isEqualTo("refreshTokenTimeToLive has to be greater than Duration.ZERO");
+				.isEqualTo("refreshTokenTimeToLive must be greater than Duration.ZERO");
 	}
 
 	@Test
@@ -101,7 +101,7 @@ public class TokenSettingsTests {
 		assertThat(tokenSettings.settings()).hasSize(6);
 		assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(accessTokenTimeToLive);
 		assertThat(tokenSettings.enableRefreshTokens()).isTrue();
-		assertThat(tokenSettings.reuseRefreshTokens()).isFalse();
+		assertThat(tokenSettings.reuseRefreshTokens()).isTrue();
 		assertThat(tokenSettings.refreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60));
 		assertThat(tokenSettings.<String>setting("name1")).isEqualTo("value1");
 		assertThat(tokenSettings.<String>setting("name2")).isEqualTo("value2");

+ 3 - 2
samples/boot/oauth2-integration/authorizationserver/spring-security-samples-boot-oauth2-integrated-authorizationserver.gradle

@@ -1,7 +1,8 @@
-ext['spring-security.version'] = '5.5.+' // TODO remove once Spring Boot upgraded to Spring Security 5.5
-
 apply plugin: 'io.spring.convention.spring-sample-boot'
 
+// TODO Remove once Spring Boot upgrades to Spring Security 5.5
+ext['spring-security.version'] = '5.5.+'
+
 dependencies {
 	compile 'org.springframework.boot:spring-boot-starter-web'
 	compile 'org.springframework.boot:spring-boot-starter-security'

+ 1 - 1
samples/boot/oauth2-integration/client/src/main/java/sample/config/WebClientConfig.java

@@ -50,8 +50,8 @@ public class WebClientConfig {
 		OAuth2AuthorizedClientProvider authorizedClientProvider =
 				OAuth2AuthorizedClientProviderBuilder.builder()
 						.authorizationCode()
-						.clientCredentials()
 						.refreshToken()
+						.clientCredentials()
 						.build();
 		DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
 				clientRegistrationRepository, authorizedClientRepository);