Associate Refresh Token to OAuth2AuthorizedClient

Fixes gh-5416
This commit is contained in:
Joe Grandja 2018-06-11 14:10:38 -04:00
parent 1137f3b46d
commit 02d29887fb
23 changed files with 245 additions and 27 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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(),

View File

@ -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();
}

View File

@ -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();
});

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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

View File

@ -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");

View File

@ -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");

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
/**

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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");
}
}

View File

@ -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);
}
}