From 08745662bf993e32677fa2c85c2fe381135258f4 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 17 Nov 2011 09:29:31 +0000 Subject: [PATCH] SECOAUTH-163: add access token required for expired token --- .../http/OAuth2ClientHttpRequestFactory.java | 23 +++-- .../token/AccessTokenProviderChain.java | 12 +-- .../oauth2/common/OAuth2AccessToken.java | 10 ++ .../token/RandomValueTokenServices.java | 98 ++----------------- ...mValueOAuth2ProviderTokenServicesBase.java | 2 - 5 files changed, 34 insertions(+), 111 deletions(-) diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ClientHttpRequestFactory.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ClientHttpRequestFactory.java index 94b0d2ed5..dd87c06dc 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ClientHttpRequestFactory.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ClientHttpRequestFactory.java @@ -26,6 +26,7 @@ public class OAuth2ClientHttpRequestFactory implements ClientHttpRequestFactory { private final ClientHttpRequestFactory delegate; + private final OAuth2ProtectedResourceDetails resource; public OAuth2ClientHttpRequestFactory(ClientHttpRequestFactory delegate, OAuth2ProtectedResourceDetails resource) { @@ -50,12 +51,19 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO Map accessTokens = context.getAccessTokens(); OAuth2AccessToken accessToken = accessTokens == null ? null : accessTokens.get(this.resource.getId()); + if (accessToken == null) { throw new AccessTokenRequiredException( "No OAuth 2 security context has been established. Unable to access resource '" + this.resource.getId() + "'.", resource); } + if (accessToken.isExpired()) { + // If the current token has expired we can use this exception as a trigger to try and refresh it + throw new AccessTokenRequiredException("OAuth 2 token is expired. Unable to access resource '" + + this.resource.getId() + "'.", resource); + } + String tokenType = accessToken.getTokenType(); if (!StringUtils.hasText(tokenType)) { tokenType = OAuth2AccessToken.BEARER_TYPE; // we'll assume basic bearer token type if none is specified. @@ -73,7 +81,8 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, accessToken.getValue())); } return req; - } else { + } + else { throw new OAuth2AccessDeniedException("Unsupported access token type: " + tokenType); } } @@ -85,11 +94,11 @@ protected URI appendQueryParameter(URI uri, OAuth2AccessToken accessToken) { // TODO: there is some duplication with UriUtils here. Probably unavoidable as long as this // method signature uses URI not String. String query = uri.getQuery(); - String queryFragment = resource.getTokenName() + "=" - + URLEncoder.encode(accessToken.getValue(), "UTF-8"); + String queryFragment = resource.getTokenName() + "=" + URLEncoder.encode(accessToken.getValue(), "UTF-8"); if (query == null) { query = queryFragment; - } else { + } + else { query = query + "&" + queryFragment; } @@ -108,9 +117,11 @@ protected URI appendQueryParameter(URI uri, OAuth2AccessToken accessToken) { return new URI(sb.toString()); - } catch (URISyntaxException e) { + } + catch (URISyntaxException e) { throw new IllegalArgumentException("Could not parse URI", e); - } catch (UnsupportedEncodingException e) { + } + catch (UnsupportedEncodingException e) { throw new IllegalArgumentException("Could not encode URI", e); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProviderChain.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProviderChain.java index 5c4324f5f..fba00ec62 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProviderChain.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/client/token/AccessTokenProviderChain.java @@ -75,7 +75,7 @@ public OAuth2AccessToken obtainNewAccessToken(OAuth2ProtectedResourceDetails res if (!requireAuthenticated || (auth != null && auth.isAuthenticated())) { existingToken = tokenServices.getToken(auth, resource); if (existingToken != null) { - if (isExpired(existingToken)) { + if (existingToken.isExpired()) { OAuth2RefreshToken refreshToken = existingToken.getRefreshToken(); if (refreshToken != null) { accessToken = refreshAccessToken(resource, refreshToken); @@ -144,16 +144,6 @@ protected OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails re return retrieveToken(form, resource); } - /** - * Whether the specified access token is expired. - * - * @param token The token. - * @return Whether the specified access token is expired. - */ - protected boolean isExpired(OAuth2AccessToken token) { - return token.getExpiration() == null || token.getExpiration().getTime() < System.currentTimeMillis(); - } - public void setTokenServices(OAuth2ClientTokenServices tokenServices) { this.tokenServices = tokenServices; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java index c3cffdd95..6a552eaa2 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java @@ -60,6 +60,15 @@ public void setExpiration(Date expiration) { this.expiration = expiration; } + /** + * Convenience method for checking expiration + * + * @return true if the expiration is befor ethe current time + */ + public boolean isExpired() { + return expiration!=null && expiration.before(new Date()); + } + /** * The token type, as introduced in draft 11 of the OAuth 2 spec. The spec doesn't define (yet) that the valid token * types are, but says it's required so the default will just be "undefined". @@ -129,4 +138,5 @@ public int hashCode() { public String toString() { return getValue(); } + } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/RandomValueTokenServices.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/RandomValueTokenServices.java index 831aa61c1..1f46456b6 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/RandomValueTokenServices.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/RandomValueTokenServices.java @@ -16,9 +16,7 @@ package org.springframework.security.oauth2.provider.token; -import java.security.SecureRandom; import java.util.Date; -import java.util.Random; import java.util.Set; import java.util.UUID; @@ -44,8 +42,6 @@ public class RandomValueTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices, InitializingBean { - private Random random; - private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days. private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours. @@ -54,8 +50,6 @@ public class RandomValueTokenServices implements AuthorizationServerTokenService private boolean reuseRefreshToken = true; - private int tokenSecretLengthBytes = 80; - private TokenStore tokenStore; /** @@ -63,14 +57,11 @@ public class RandomValueTokenServices implements AuthorizationServerTokenService */ public void afterPropertiesSet() throws Exception { Assert.notNull(tokenStore, "tokenStore must be set"); - if (random == null) { - random = new SecureRandom(); - } } public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { ExpiringOAuth2RefreshToken refreshToken = null; - if (isSupportRefreshToken()) { + if (supportRefreshToken) { refreshToken = createRefreshToken(authentication); } @@ -80,7 +71,7 @@ public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, Set scope) throws AuthenticationException { - if (!isSupportRefreshToken()) { + if (!supportRefreshToken) { throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue); } @@ -99,7 +90,7 @@ else if (isExpired(refreshToken)) { OAuth2Authentication authentication = createRefreshedAuthentication( tokenStore.readAuthentication(refreshToken), scope); - if (!isReuseRefreshToken()) { + if (!reuseRefreshToken) { tokenStore.removeRefreshToken(refreshTokenValue); refreshToken = createRefreshToken(authentication); } @@ -138,17 +129,12 @@ protected boolean isExpired(ExpiringOAuth2RefreshToken refreshToken) { || System.currentTimeMillis() > refreshToken.getExpiration().getTime(); } - private boolean isExpired(OAuth2AccessToken accessToken) { - return accessToken.getExpiration() == null - || System.currentTimeMillis() > accessToken.getExpiration().getTime(); - } - public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException { OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue); if (accessToken == null) { throw new InvalidTokenException("Invalid access token: " + accessTokenValue); } - else if (isExpired(accessToken)) { + else if (accessToken.isExpired()) { tokenStore.removeAccessToken(accessTokenValue); throw new InvalidTokenException("Invalid access token: " + accessTokenValue); } @@ -160,7 +146,7 @@ protected ExpiringOAuth2RefreshToken createRefreshToken(OAuth2Authentication aut ExpiringOAuth2RefreshToken refreshToken; String refreshTokenValue = UUID.randomUUID().toString(); refreshToken = new ExpiringOAuth2RefreshToken(refreshTokenValue, new Date(System.currentTimeMillis() - + (getRefreshTokenValiditySeconds() * 1000L))); + + (refreshTokenValiditySeconds * 1000L))); tokenStore.storeRefreshToken(refreshToken, authentication); return refreshToken; } @@ -168,58 +154,13 @@ protected ExpiringOAuth2RefreshToken createRefreshToken(OAuth2Authentication aut protected OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) { String tokenValue = UUID.randomUUID().toString(); OAuth2AccessToken token = new OAuth2AccessToken(tokenValue); - token.setExpiration(new Date(System.currentTimeMillis() + (getAccessTokenValiditySeconds() * 1000L))); + token.setExpiration(new Date(System.currentTimeMillis() + (accessTokenValiditySeconds * 1000L))); token.setRefreshToken(refreshToken); token.setScope(authentication.getClientAuthentication().getScope()); tokenStore.storeAccessToken(token, authentication); return token; } - /** - * The length of the token secret in bytes, before being base64-encoded. - * - * @return The length of the token secret in bytes. - */ - public int getTokenSecretLengthBytes() { - return tokenSecretLengthBytes; - } - - /** - * The length of the token secret in bytes, before being base64-encoded. - * - * @param tokenSecretLengthBytes The length of the token secret in bytes, before being base64-encoded. - */ - public void setTokenSecretLengthBytes(int tokenSecretLengthBytes) { - this.tokenSecretLengthBytes = tokenSecretLengthBytes; - } - - /** - * The random value generator used to create token secrets. - * - * @return The random value generator used to create token secrets. - */ - public Random getRandom() { - return random; - } - - /** - * The random value generator used to create token secrets. - * - * @param random The random value generator used to create token secrets. - */ - public void setRandom(Random random) { - this.random = random; - } - - /** - * The validity (in seconds) of the unauthenticated request token. - * - * @return The validity (in seconds) of the unauthenticated request token. - */ - public int getRefreshTokenValiditySeconds() { - return refreshTokenValiditySeconds; - } - /** * The validity (in seconds) of the unauthenticated request token. * @@ -229,15 +170,6 @@ public void setRefreshTokenValiditySeconds(int refreshTokenValiditySeconds) { this.refreshTokenValiditySeconds = refreshTokenValiditySeconds; } - /** - * The validity (in seconds) of the access token. - * - * @return The validity (in seconds) of the access token. - */ - public int getAccessTokenValiditySeconds() { - return accessTokenValiditySeconds; - } - /** * The validity (in seconds) of the access token. * @@ -247,15 +179,6 @@ public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) { this.accessTokenValiditySeconds = accessTokenValiditySeconds; } - /** - * Whether to support the refresh token. - * - * @return Whether to support the refresh token. - */ - public boolean isSupportRefreshToken() { - return supportRefreshToken; - } - /** * Whether to support the refresh token. * @@ -265,15 +188,6 @@ public void setSupportRefreshToken(boolean supportRefreshToken) { this.supportRefreshToken = supportRefreshToken; } - /** - * Whether to reuse refresh tokens (until expired). - * - * @return Whether to reuse refresh tokens (until expired). - */ - public boolean isReuseRefreshToken() { - return reuseRefreshToken; - } - /** * Whether to reuse refresh tokens (until expired). * diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/TestRandomValueOAuth2ProviderTokenServicesBase.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/TestRandomValueOAuth2ProviderTokenServicesBase.java index 2c8f6bac5..5aa61d408 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/TestRandomValueOAuth2ProviderTokenServicesBase.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/TestRandomValueOAuth2ProviderTokenServicesBase.java @@ -7,7 +7,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; -import java.util.Random; import org.junit.Test; import org.springframework.security.core.Authentication; @@ -79,7 +78,6 @@ public void testReadingRefreshTokenForTokenThatDoesNotExist() { public void testRefreshedTokenHasScopes() throws Exception { RandomValueTokenServices services = new RandomValueTokenServices(); services.setTokenStore(getTokenStore()); - services.setRandom(new Random(1L)); services.afterPropertiesSet(); services.setSupportRefreshToken(true); ExpiringOAuth2RefreshToken expectedExpiringRefreshToken = new ExpiringOAuth2RefreshToken("testToken", new Date(