浏览代码

Associate Refresh Token to OAuth2AuthorizedClient

Fixes gh-5416
Joe Grandja 7 年之前
父节点
当前提交
02d29887fb
共有 23 个文件被更改,包括 245 次插入27 次删除
  1. 29 1
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java
  2. 2 4
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java
  3. 29 0
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationToken.java
  4. 3 2
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java
  5. 36 1
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationToken.java
  6. 4 2
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManager.java
  7. 7 1
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusAuthorizationCodeTokenResponseClient.java
  8. 6 0
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusReactiveAuthorizationCodeTokenResponseClient.java
  9. 4 6
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java
  10. 2 1
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java
  11. 2 1
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java
  12. 4 0
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java
  13. 5 1
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java
  14. 3 1
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/NimbusAuthorizationCodeTokenResponseClientTests.java
  15. 2 0
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/NimbusReactiveAuthorizationCodeTokenResponseClientTests.java
  16. 6 1
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProviderTests.java
  17. 3 0
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java
  18. 4 1
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilterTests.java
  19. 2 1
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java
  20. 46 0
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2RefreshToken.java
  21. 36 1
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java
  22. 6 1
      oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/AuthorizationGrantTypeTests.java
  23. 4 1
      oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponseTests.java

+ 29 - 1
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -15,8 +15,10 @@
  */
 package org.springframework.security.oauth2.client;
 
+import org.springframework.lang.Nullable;
 import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.util.Assert;
 
 /**
@@ -33,11 +35,13 @@ import org.springframework.util.Assert;
  * @since 5.0
  * @see ClientRegistration
  * @see OAuth2AccessToken
+ * @see OAuth2RefreshToken
  */
 public class OAuth2AuthorizedClient {
 	private final ClientRegistration clientRegistration;
 	private final String principalName;
 	private final OAuth2AccessToken accessToken;
+	private final OAuth2RefreshToken refreshToken;
 
 	/**
 	 * Constructs an {@code OAuth2AuthorizedClient} using the provided parameters.
@@ -47,12 +51,26 @@ public class OAuth2AuthorizedClient {
 	 * @param accessToken the access token credential granted
 	 */
 	public OAuth2AuthorizedClient(ClientRegistration clientRegistration, String principalName, OAuth2AccessToken accessToken) {
+		this(clientRegistration, principalName, accessToken, null);
+	}
+
+	/**
+	 * Constructs an {@code OAuth2AuthorizedClient} using the provided parameters.
+	 *
+	 * @param clientRegistration the authorized client's registration
+	 * @param principalName the name of the End-User {@code Principal} (Resource Owner)
+	 * @param accessToken the access token credential granted
+	 * @param refreshToken the refresh token credential granted
+	 */
+	public OAuth2AuthorizedClient(ClientRegistration clientRegistration, String principalName,
+									OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
 		Assert.notNull(clientRegistration, "clientRegistration cannot be null");
 		Assert.hasText(principalName, "principalName cannot be empty");
 		Assert.notNull(accessToken, "accessToken cannot be null");
 		this.clientRegistration = clientRegistration;
 		this.principalName = principalName;
 		this.accessToken = accessToken;
+		this.refreshToken = refreshToken;
 	}
 
 	/**
@@ -81,4 +99,14 @@ public class OAuth2AuthorizedClient {
 	public OAuth2AccessToken getAccessToken() {
 		return this.accessToken;
 	}
+
+	/**
+	 * Returns the {@link OAuth2RefreshToken refresh token} credential granted.
+	 *
+	 * @since 5.1
+	 * @return the {@link OAuth2RefreshToken}
+	 */
+	public @Nullable OAuth2RefreshToken getRefreshToken() {
+		return this.refreshToken;
+	}
 }

+ 2 - 4
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java

@@ -20,7 +20,6 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
-import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
 import org.springframework.util.Assert;
 
@@ -69,13 +68,12 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
 					authorizationCodeAuthentication.getClientRegistration(),
 					authorizationCodeAuthentication.getAuthorizationExchange()));
 
-		OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
-
 		OAuth2AuthorizationCodeAuthenticationToken authenticationResult =
 			new OAuth2AuthorizationCodeAuthenticationToken(
 				authorizationCodeAuthentication.getClientRegistration(),
 				authorizationCodeAuthentication.getAuthorizationExchange(),
-				accessToken);
+				accessTokenResponse.getAccessToken(),
+				accessTokenResponse.getRefreshToken());
 		authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
 
 		return authenticationResult;

+ 29 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationToken.java

@@ -15,10 +15,12 @@
  */
 package org.springframework.security.oauth2.client.authentication;
 
+import org.springframework.lang.Nullable;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.core.SpringSecurityCoreVersion;
 import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
 import org.springframework.util.Assert;
 
@@ -40,6 +42,7 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
 	private ClientRegistration clientRegistration;
 	private OAuth2AuthorizationExchange authorizationExchange;
 	private OAuth2AccessToken accessToken;
+	private OAuth2RefreshToken refreshToken;
 
 	/**
 	 * This constructor should be used when the Authorization Request/Response is complete.
@@ -67,9 +70,26 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
 	public OAuth2AuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration,
 														OAuth2AuthorizationExchange authorizationExchange,
 														OAuth2AccessToken accessToken) {
+		this(clientRegistration, authorizationExchange, accessToken, null);
+	}
+
+	/**
+	 * This constructor should be used when the Access Token Request/Response is complete,
+	 * which indicates that the Authorization Code Grant flow has fully completed.
+	 *
+	 * @param clientRegistration the client registration
+	 * @param authorizationExchange the authorization exchange
+	 * @param accessToken the access token credential
+	 * @param refreshToken the refresh token credential
+	 */
+	public OAuth2AuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration,
+														OAuth2AuthorizationExchange authorizationExchange,
+														OAuth2AccessToken accessToken,
+														@Nullable OAuth2RefreshToken refreshToken) {
 		this(clientRegistration, authorizationExchange);
 		Assert.notNull(accessToken, "accessToken cannot be null");
 		this.accessToken = accessToken;
+		this.refreshToken = refreshToken;
 		this.setAuthenticated(true);
 	}
 
@@ -111,4 +131,13 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
 	public OAuth2AccessToken getAccessToken() {
 		return this.accessToken;
 	}
+
+	/**
+	 * Returns the {@link OAuth2RefreshToken refresh token}.
+	 *
+	 * @return the {@link OAuth2RefreshToken}
+	 */
+	public @Nullable OAuth2RefreshToken getRefreshToken() {
+		return this.refreshToken;
+	}
 }

+ 3 - 2
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -113,7 +113,8 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
 			authorizationCodeAuthentication.getAuthorizationExchange(),
 			oauth2User,
 			mappedAuthorities,
-			accessToken);
+			accessToken,
+			accessTokenResponse.getRefreshToken());
 		authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
 
 		return authenticationResult;

+ 36 - 1
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationToken.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -15,11 +15,13 @@
  */
 package org.springframework.security.oauth2.client.authentication;
 
+import org.springframework.lang.Nullable;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.SpringSecurityCoreVersion;
 import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
 import org.springframework.security.oauth2.core.user.OAuth2User;
 import org.springframework.util.Assert;
@@ -46,6 +48,7 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
 	private ClientRegistration clientRegistration;
 	private OAuth2AuthorizationExchange authorizationExchange;
 	private OAuth2AccessToken accessToken;
+	private OAuth2RefreshToken refreshToken;
 
 	/**
 	 * This constructor should be used when the Authorization Request/Response is complete.
@@ -80,6 +83,27 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
 											OAuth2User principal,
 											Collection<? extends GrantedAuthority> authorities,
 											OAuth2AccessToken accessToken) {
+		this(clientRegistration, authorizationExchange, principal, authorities, accessToken, null);
+	}
+
+	/**
+	 * This constructor should be used when the Access Token Request/Response is complete,
+	 * which indicates that the Authorization Code Grant flow has fully completed
+	 * and OAuth 2.0 Login has been achieved.
+	 *
+	 * @param clientRegistration the client registration
+	 * @param authorizationExchange the authorization exchange
+	 * @param principal the user {@code Principal} registered with the OAuth 2.0 Provider
+	 * @param authorities the authorities granted to the user
+	 * @param accessToken the access token credential
+	 * @param refreshToken the refresh token credential
+	 */
+	public OAuth2LoginAuthenticationToken(ClientRegistration clientRegistration,
+											OAuth2AuthorizationExchange authorizationExchange,
+											OAuth2User principal,
+											Collection<? extends GrantedAuthority> authorities,
+											OAuth2AccessToken accessToken,
+											@Nullable OAuth2RefreshToken refreshToken) {
 		super(authorities);
 		Assert.notNull(clientRegistration, "clientRegistration cannot be null");
 		Assert.notNull(authorizationExchange, "authorizationExchange cannot be null");
@@ -89,6 +113,7 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
 		this.authorizationExchange = authorizationExchange;
 		this.principal = principal;
 		this.accessToken = accessToken;
+		this.refreshToken = refreshToken;
 		this.setAuthenticated(true);
 	}
 
@@ -128,4 +153,14 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
 	public OAuth2AccessToken getAccessToken() {
 		return this.accessToken;
 	}
+
+	/**
+	 * Returns the {@link OAuth2RefreshToken refresh token}.
+	 *
+	 * @since 5.1
+	 * @return the {@link OAuth2RefreshToken}
+	 */
+	public @Nullable OAuth2RefreshToken getRefreshToken() {
+		return this.refreshToken;
+	}
 }

+ 4 - 2
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManager.java

@@ -120,11 +120,13 @@ public class OAuth2LoginReactiveAuthenticationManager implements
 							authorizationCodeAuthentication.getAuthorizationExchange(),
 							oauth2User,
 							mappedAuthorities,
-							accessToken);
+							accessToken,
+							accessTokenResponse.getRefreshToken());
 					OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
 							authenticationResult.getClientRegistration(),
 							authenticationResult.getName(),
-							authenticationResult.getAccessToken());
+							authenticationResult.getAccessToken(),
+							authenticationResult.getRefreshToken());
 					OAuth2AuthenticationToken result =  new OAuth2AuthenticationToken(
 							authenticationResult.getPrincipal(),
 							authenticationResult.getAuthorities(),

+ 7 - 1
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusAuthorizationCodeTokenResponseClient.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -138,12 +138,18 @@ public class NimbusAuthorizationCodeTokenResponseClient implements OAuth2AccessT
 				accessTokenResponse.getTokens().getAccessToken().getScope().toStringList());
 		}
 
+		String refreshToken = null;
+		if (accessTokenResponse.getTokens().getRefreshToken() != null) {
+			refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue();
+		}
+
 		Map<String, Object> additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters());
 
 		return OAuth2AccessTokenResponse.withToken(accessToken)
 			.tokenType(accessTokenType)
 			.expiresIn(expiresIn)
 			.scopes(scopes)
+			.refreshToken(refreshToken)
 			.additionalParameters(additionalParameters)
 			.build();
 	}

+ 6 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusReactiveAuthorizationCodeTokenResponseClient.java

@@ -118,6 +118,11 @@ public class NimbusReactiveAuthorizationCodeTokenResponseClient implements React
 									accessToken.getScope().toStringList());
 						}
 
+						String refreshToken = null;
+						if (accessTokenResponse.getTokens().getRefreshToken() != null) {
+							refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue();
+						}
+
 						Map<String, Object> additionalParameters = new LinkedHashMap<>(
 								accessTokenResponse.getCustomParameters());
 
@@ -125,6 +130,7 @@ public class NimbusReactiveAuthorizationCodeTokenResponseClient implements React
 								.tokenType(accessTokenType)
 								.expiresIn(expiresIn)
 								.scopes(scopes)
+								.refreshToken(refreshToken)
 								.additionalParameters(additionalParameters)
 								.build();
 					});

+ 4 - 6
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -27,7 +27,6 @@ import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
 import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
 import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
-import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
@@ -142,8 +141,6 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
 					authorizationCodeAuthentication.getClientRegistration(),
 					authorizationCodeAuthentication.getAuthorizationExchange()));
 
-		OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
-
 		ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
 
 		if (!accessTokenResponse.getAdditionalParameters().containsKey(OidcParameterNames.ID_TOKEN)) {
@@ -161,7 +158,7 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
 		this.validateIdToken(idToken, clientRegistration);
 
 		OidcUser oidcUser = this.userService.loadUser(
-			new OidcUserRequest(clientRegistration, accessToken, idToken));
+			new OidcUserRequest(clientRegistration, accessTokenResponse.getAccessToken(), idToken));
 
 		Collection<? extends GrantedAuthority> mappedAuthorities =
 			this.authoritiesMapper.mapAuthorities(oidcUser.getAuthorities());
@@ -171,7 +168,8 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
 			authorizationCodeAuthentication.getAuthorizationExchange(),
 			oidcUser,
 			mappedAuthorities,
-			accessToken);
+			accessTokenResponse.getAccessToken(),
+			accessTokenResponse.getRefreshToken());
 		authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
 
 		return authenticationResult;

+ 2 - 1
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java

@@ -198,7 +198,8 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
 		OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
 			authenticationResult.getClientRegistration(),
 			currentAuthentication.getName(),
-			authenticationResult.getAccessToken());
+			authenticationResult.getAccessToken(),
+			authenticationResult.getRefreshToken());
 
 		this.authorizedClientService.saveAuthorizedClient(authorizedClient, currentAuthentication);
 

+ 2 - 1
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java

@@ -173,7 +173,8 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
 		OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
 			authenticationResult.getClientRegistration(),
 			oauth2Authentication.getName(),
-			authenticationResult.getAccessToken());
+			authenticationResult.getAccessToken(),
+			authenticationResult.getRefreshToken());
 
 		this.authorizedClientService.saveAuthorizedClient(authorizedClient, oauth2Authentication);
 

+ 4 - 0
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java

@@ -27,6 +27,7 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
@@ -122,8 +123,10 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 	@Test
 	public void authenticateWhenAuthorizationSuccessResponseThenExchangedForAccessToken() {
 		OAuth2AccessToken accessToken = mock(OAuth2AccessToken.class);
+		OAuth2RefreshToken refreshToken = mock(OAuth2RefreshToken.class);
 		OAuth2AccessTokenResponse accessTokenResponse = mock(OAuth2AccessTokenResponse.class);
 		when(accessTokenResponse.getAccessToken()).thenReturn(accessToken);
+		when(accessTokenResponse.getRefreshToken()).thenReturn(refreshToken);
 		when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(accessTokenResponse);
 
 		OAuth2AuthorizationCodeAuthenticationToken authenticationResult =
@@ -137,5 +140,6 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		assertThat(authenticationResult.getClientRegistration()).isEqualTo(this.clientRegistration);
 		assertThat(authenticationResult.getAuthorizationExchange()).isEqualTo(this.authorizationExchange);
 		assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken);
+		assertThat(authenticationResult.getRefreshToken()).isEqualTo(refreshToken);
 	}
 }

+ 5 - 1
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -35,6 +35,7 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
@@ -164,8 +165,10 @@ public class OAuth2LoginAuthenticationProviderTests {
 	@Test
 	public void authenticateWhenLoginSuccessThenReturnAuthentication() {
 		OAuth2AccessToken accessToken = mock(OAuth2AccessToken.class);
+		OAuth2RefreshToken refreshToken = mock(OAuth2RefreshToken.class);
 		OAuth2AccessTokenResponse accessTokenResponse = mock(OAuth2AccessTokenResponse.class);
 		when(accessTokenResponse.getAccessToken()).thenReturn(accessToken);
+		when(accessTokenResponse.getRefreshToken()).thenReturn(refreshToken);
 		when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(accessTokenResponse);
 
 		OAuth2User principal = mock(OAuth2User.class);
@@ -185,6 +188,7 @@ public class OAuth2LoginAuthenticationProviderTests {
 		assertThat(authentication.getClientRegistration()).isEqualTo(this.clientRegistration);
 		assertThat(authentication.getAuthorizationExchange()).isEqualTo(this.authorizationExchange);
 		assertThat(authentication.getAccessToken()).isEqualTo(accessToken);
+		assertThat(authentication.getRefreshToken()).isEqualTo(refreshToken);
 	}
 
 	@Test

+ 3 - 1
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/NimbusAuthorizationCodeTokenResponseClientTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -91,6 +91,7 @@ public class NimbusAuthorizationCodeTokenResponseClientTests {
 			"   \"token_type\": \"bearer\",\n" +
 			"   \"expires_in\": \"3600\",\n" +
 			"   \"scope\": \"openid profile\",\n" +
+			"	\"refresh_token\": \"refresh-token-1234\",\n" +
 			"   \"custom_parameter_1\": \"custom-value-1\",\n" +
 			"   \"custom_parameter_2\": \"custom-value-2\"\n" +
 			"}\n";
@@ -115,6 +116,7 @@ public class NimbusAuthorizationCodeTokenResponseClientTests {
 		assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER);
 		assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter);
 		assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile");
+		assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234");
 		assertThat(accessTokenResponse.getAdditionalParameters().size()).isEqualTo(2);
 		assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1");
 		assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2");

+ 2 - 0
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/NimbusReactiveAuthorizationCodeTokenResponseClientTests.java

@@ -85,6 +85,7 @@ public class NimbusReactiveAuthorizationCodeTokenResponseClientTests {
 				"   \"token_type\": \"bearer\",\n" +
 				"   \"expires_in\": \"3600\",\n" +
 				"   \"scope\": \"openid profile\",\n" +
+				"	\"refresh_token\": \"refresh-token-1234\",\n" +
 				"   \"custom_parameter_1\": \"custom-value-1\",\n" +
 				"   \"custom_parameter_2\": \"custom-value-2\"\n" +
 				"}\n";
@@ -102,6 +103,7 @@ public class NimbusReactiveAuthorizationCodeTokenResponseClientTests {
 				OAuth2AccessToken.TokenType.BEARER);
 		assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter);
 		assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile");
+		assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234");
 		assertThat(accessTokenResponse.getAdditionalParameters().size()).isEqualTo(2);
 		assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1");
 		assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2");

+ 6 - 1
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProviderTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -37,6 +37,7 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
@@ -78,6 +79,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 	private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
 	private OAuth2AccessTokenResponse accessTokenResponse;
 	private OAuth2AccessToken accessToken;
+	private OAuth2RefreshToken refreshToken;
 	private OAuth2UserService<OidcUserRequest, OidcUser> userService;
 	private OidcAuthorizationCodeAuthenticationProvider authenticationProvider;
 
@@ -95,6 +97,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 		this.accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
 		this.accessTokenResponse = mock(OAuth2AccessTokenResponse.class);
 		this.accessToken = mock(OAuth2AccessToken.class);
+		this.refreshToken = mock(OAuth2RefreshToken.class);
 		this.userService = mock(OAuth2UserService.class);
 		this.authenticationProvider = PowerMockito.spy(
 			new OidcAuthorizationCodeAuthenticationProvider(this.accessTokenResponseClient, this.userService));
@@ -109,6 +112,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 		when(this.authorizationRequest.getRedirectUri()).thenReturn("http://example.com");
 		when(this.authorizationResponse.getRedirectUri()).thenReturn("http://example.com");
 		when(this.accessTokenResponse.getAccessToken()).thenReturn(this.accessToken);
+		when(this.accessTokenResponse.getRefreshToken()).thenReturn(this.refreshToken);
 		Map<String, Object> additionalParameters = new HashMap<>();
 		additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token");
 		when(this.accessTokenResponse.getAdditionalParameters()).thenReturn(additionalParameters);
@@ -365,6 +369,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
 		assertThat(authentication.getClientRegistration()).isEqualTo(this.clientRegistration);
 		assertThat(authentication.getAuthorizationExchange()).isEqualTo(this.authorizationExchange);
 		assertThat(authentication.getAccessToken()).isEqualTo(this.accessToken);
+		assertThat(authentication.getRefreshToken()).isEqualTo(this.refreshToken);
 	}
 
 	@Test

+ 3 - 0
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java

@@ -41,6 +41,7 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
@@ -238,6 +239,7 @@ public class OAuth2AuthorizationCodeGrantFilterTests {
 		assertThat(authorizedClient.getClientRegistration()).isEqualTo(this.registration1);
 		assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principalName1);
 		assertThat(authorizedClient.getAccessToken()).isNotNull();
+		assertThat(authorizedClient.getRefreshToken()).isNotNull();
 	}
 
 	@Test
@@ -299,6 +301,7 @@ public class OAuth2AuthorizationCodeGrantFilterTests {
 		when(authentication.getClientRegistration()).thenReturn(registration);
 		when(authentication.getAuthorizationExchange()).thenReturn(mock(OAuth2AuthorizationExchange.class));
 		when(authentication.getAccessToken()).thenReturn(mock(OAuth2AccessToken.class));
+		when(authentication.getRefreshToken()).thenReturn(mock(OAuth2RefreshToken.class));
 		when(this.authenticationManager.authenticate(any(Authentication.class))).thenReturn(authentication);
 	}
 }

+ 4 - 1
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilterTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -40,6 +40,7 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
@@ -281,6 +282,7 @@ public class OAuth2LoginAuthenticationFilterTests {
 		assertThat(authorizedClient.getClientRegistration()).isEqualTo(this.registration1);
 		assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principalName1);
 		assertThat(authorizedClient.getAccessToken()).isNotNull();
+		assertThat(authorizedClient.getRefreshToken()).isNotNull();
 	}
 
 	@Test
@@ -328,6 +330,7 @@ public class OAuth2LoginAuthenticationFilterTests {
 		when(loginAuthentication.getClientRegistration()).thenReturn(registration);
 		when(loginAuthentication.getAuthorizationExchange()).thenReturn(mock(OAuth2AuthorizationExchange.class));
 		when(loginAuthentication.getAccessToken()).thenReturn(mock(OAuth2AccessToken.class));
+		when(loginAuthentication.getRefreshToken()).thenReturn(mock(OAuth2RefreshToken.class));
 		when(this.authenticationManager.authenticate(any(Authentication.class))).thenReturn(loginAuthentication);
 	}
 }

+ 2 - 1
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -37,6 +37,7 @@ public final class AuthorizationGrantType implements Serializable {
 	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
 	public static final AuthorizationGrantType AUTHORIZATION_CODE = new AuthorizationGrantType("authorization_code");
 	public static final AuthorizationGrantType IMPLICIT = new AuthorizationGrantType("implicit");
+	public static final AuthorizationGrantType REFRESH_TOKEN = new AuthorizationGrantType("refresh_token");
 	private final String value;
 
 	/**

+ 46 - 0
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2RefreshToken.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2018 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
+ *
+ *      http://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 org.springframework.security.oauth2.core;
+
+import java.time.Instant;
+
+/**
+ * An implementation of an {@link AbstractOAuth2Token} representing an OAuth 2.0 Refresh Token.
+ *
+ * <p>
+ * A refresh token is a credential that represents an authorization
+ * granted by the resource owner to the client.
+ * It is used by the client to obtain a new access token when the current access token
+ * becomes invalid or expires, or to obtain additional access tokens with identical or narrower scope.
+ *
+ * @author Joe Grandja
+ * @since 5.1
+ * @see OAuth2AccessToken
+ * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.5">Section 1.5 Refresh Token</a>
+ */
+public class OAuth2RefreshToken extends AbstractOAuth2Token {
+
+	/**
+	 * Constructs an {@code OAuth2RefreshToken} using the provided parameters.
+	 *
+	 * @param tokenValue the token value
+	 * @param issuedAt the time at which the token was issued
+	 * @param expiresAt the expiration time on or after which the token MUST NOT be accepted
+	 */
+	public OAuth2RefreshToken(String tokenValue, Instant issuedAt, Instant expiresAt) {
+		super(tokenValue, issuedAt, expiresAt);
+	}
+}

+ 36 - 1
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -15,8 +15,11 @@
  */
 package org.springframework.security.oauth2.core.endpoint;
 
+import org.springframework.lang.Nullable;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
 
 import java.time.Instant;
 import java.util.Collections;
@@ -29,10 +32,12 @@ import java.util.Set;
  * @author Joe Grandja
  * @since 5.0
  * @see OAuth2AccessToken
+ * @see OAuth2RefreshToken
  * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-5.1">Section 5.1 Access Token Response</a>
  */
 public final class OAuth2AccessTokenResponse {
 	private OAuth2AccessToken accessToken;
+	private OAuth2RefreshToken refreshToken;
 	private Map<String, Object> additionalParameters;
 
 	private OAuth2AccessTokenResponse() {
@@ -47,6 +52,16 @@ public final class OAuth2AccessTokenResponse {
 		return this.accessToken;
 	}
 
+	/**
+	 * Returns the {@link OAuth2RefreshToken Refresh Token}.
+	 *
+	 * @since 5.1
+	 * @return the {@link OAuth2RefreshToken}
+	 */
+	public @Nullable OAuth2RefreshToken getRefreshToken() {
+		return this.refreshToken;
+	}
+
 	/**
 	 * Returns the additional parameters returned in the response.
 	 *
@@ -74,6 +89,7 @@ public final class OAuth2AccessTokenResponse {
 		private OAuth2AccessToken.TokenType tokenType;
 		private long expiresIn;
 		private Set<String> scopes;
+		private String refreshToken;
 		private Map<String, Object> additionalParameters;
 
 		private Builder(String tokenValue) {
@@ -113,6 +129,17 @@ public final class OAuth2AccessTokenResponse {
 			return this;
 		}
 
+		/**
+		 * Sets the refresh token associated to the access token.
+		 *
+		 * @param refreshToken the refresh token associated to the access token.
+		 * @return the {@link Builder}
+		 */
+		public Builder refreshToken(String refreshToken) {
+			this.refreshToken = refreshToken;
+			return this;
+		}
+
 		/**
 		 * Sets the additional parameters returned in the response.
 		 *
@@ -142,6 +169,14 @@ public final class OAuth2AccessTokenResponse {
 			OAuth2AccessTokenResponse accessTokenResponse = new OAuth2AccessTokenResponse();
 			accessTokenResponse.accessToken = new OAuth2AccessToken(
 				this.tokenType, this.tokenValue, issuedAt, expiresAt, this.scopes);
+			if (StringUtils.hasText(this.refreshToken)) {
+				// The Access Token response does not return an expires_in for the Refresh Token,
+				// therefore, we'll default to +1 second from issuedAt time.
+				// NOTE:
+				// The expiry or invalidity of a Refresh Token can only be determined by performing
+				// the refresh_token grant and if it fails than likely it has expired or has been invalidated.
+				accessTokenResponse.refreshToken = new OAuth2RefreshToken(this.refreshToken, issuedAt, issuedAt.plusSeconds(1));
+			}
 			accessTokenResponse.additionalParameters = Collections.unmodifiableMap(
 				CollectionUtils.isEmpty(this.additionalParameters) ? Collections.emptyMap() : this.additionalParameters);
 			return accessTokenResponse;

+ 6 - 1
oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/AuthorizationGrantTypeTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -40,4 +40,9 @@ public class AuthorizationGrantTypeTests {
 	public void getValueWhenImplicitGrantTypeThenReturnImplicit() {
 		assertThat(AuthorizationGrantType.IMPLICIT.getValue()).isEqualTo("implicit");
 	}
+
+	@Test
+	public void getValueWhenRefreshTokenGrantTypeThenReturnRefreshToken() {
+		assertThat(AuthorizationGrantType.REFRESH_TOKEN.getValue()).isEqualTo("refresh_token");
+	}
 }

+ 4 - 1
oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponseTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
  */
 public class OAuth2AccessTokenResponseTests {
 	private static final String TOKEN_VALUE = "access-token";
+	private static final String REFRESH_TOKEN_VALUE = "refresh-token";
 	private static final long EXPIRES_IN = Instant.now().plusSeconds(5).toEpochMilli();
 
 	@Test(expected = IllegalArgumentException.class)
@@ -88,6 +89,7 @@ public class OAuth2AccessTokenResponseTests {
 			.tokenType(OAuth2AccessToken.TokenType.BEARER)
 			.expiresIn(expiresAt.toEpochMilli())
 			.scopes(scopes)
+			.refreshToken(REFRESH_TOKEN_VALUE)
 			.additionalParameters(additionalParameters)
 			.build();
 
@@ -97,6 +99,7 @@ public class OAuth2AccessTokenResponseTests {
 		assertThat(tokenResponse.getAccessToken().getIssuedAt()).isNotNull();
 		assertThat(tokenResponse.getAccessToken().getExpiresAt()).isAfterOrEqualTo(expiresAt);
 		assertThat(tokenResponse.getAccessToken().getScopes()).isEqualTo(scopes);
+		assertThat(tokenResponse.getRefreshToken().getTokenValue()).isEqualTo(REFRESH_TOKEN_VALUE);
 		assertThat(tokenResponse.getAdditionalParameters()).isEqualTo(additionalParameters);
 	}
 }