JCLOUDS-1148: Fix token caches in OAuth flows

This commit is contained in:
Daniel Haeser Rech 2016-08-03 22:54:22 -03:00 committed by Ignasi Barrera
parent 9662edf903
commit 913fdeb168
4 changed files with 58 additions and 37 deletions

View File

@ -38,12 +38,9 @@ public abstract class ClientSecret {
/** The scope(s) to authorize against. **/ /** The scope(s) to authorize against. **/
@Nullable public abstract String scope(); @Nullable public abstract String scope();
/** When does the token expire. **/ @SerializedNames({ "client_id", "client_secret", "resource", "scope" })
public abstract long expire(); public static ClientSecret create(String clientId, String clientSecret, String resource, String scope) {
return new AutoValue_ClientSecret(clientId, clientSecret, resource, scope);
@SerializedNames({ "client_id", "client_secret", "resource", "scope", "expire" })
public static ClientSecret create(String clientId, String clientSecret, String resource, String scope, long expire) {
return new AutoValue_ClientSecret(clientId, clientSecret, resource, scope, expire);
} }
ClientSecret() { ClientSecret() {

View File

@ -58,7 +58,6 @@ public class ClientCredentialsJWTBearerTokenFlow implements OAuthFilter {
private final String audience; private final String audience;
private final Supplier<Credentials> credentialsSupplier; private final Supplier<Credentials> credentialsSupplier;
private final OAuthScopes scopes; private final OAuthScopes scopes;
private final long tokenDuration;
private final LoadingCache<ClientCredentialsAuthArgs, Token> tokenCache; private final LoadingCache<ClientCredentialsAuthArgs, Token> tokenCache;
@Inject @Inject
@ -71,7 +70,6 @@ public class ClientCredentialsJWTBearerTokenFlow implements OAuthFilter {
this.scopes = scopes; this.scopes = scopes;
this.audience = audience; this.audience = audience;
this.resource = resource; this.resource = resource;
this.tokenDuration = tokenDuration;
// since the session interval is also the token expiration time requested to the server make the token expire a // since the session interval is also the token expiration time requested to the server make the token expire a
// bit before the deadline to make sure there aren't session expiration exceptions // bit before the deadline to make sure there aren't session expiration exceptions
long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration; long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration;
@ -80,26 +78,40 @@ public class ClientCredentialsJWTBearerTokenFlow implements OAuthFilter {
static final class AuthorizeToken extends CacheLoader<ClientCredentialsAuthArgs, Token> { static final class AuthorizeToken extends CacheLoader<ClientCredentialsAuthArgs, Token> {
private final AuthorizationApi api; private final AuthorizationApi api;
private final long tokenDuration;
@Inject AuthorizeToken(AuthorizationApi api) { @Inject AuthorizeToken(AuthorizationApi api, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration) {
this.api = api; this.api = api;
this.tokenDuration = tokenDuration;
}
long currentTimeSeconds() {
return System.currentTimeMillis() / 1000;
} }
@Override public Token load(ClientCredentialsAuthArgs key) throws Exception { @Override public Token load(ClientCredentialsAuthArgs key) throws Exception {
return api.authorize(key.clientId(), key.claims(), key.resource(), key.scope()); final long now = currentTimeSeconds();
final ClientCredentialsClaims claims = ClientCredentialsClaims.create(
key.claims().iss(),
key.claims().sub(),
key.claims().aud(),
now + tokenDuration,
now,
UUID.randomUUID().toString()
);
return api.authorize(key.clientId(), claims, key.resource(), key.scope());
} }
} }
@Override public HttpRequest filter(HttpRequest request) throws HttpException { @Override public HttpRequest filter(HttpRequest request) throws HttpException {
long now = currentTimeSeconds();
List<String> configuredScopes = scopes.forRequest(request); List<String> configuredScopes = scopes.forRequest(request);
ClientCredentialsClaims claims = ClientCredentialsClaims.create( // ClientCredentialsClaims claims = ClientCredentialsClaims.create( //
credentialsSupplier.get().identity, // iss credentialsSupplier.get().identity, // iss
credentialsSupplier.get().identity, // sub credentialsSupplier.get().identity, // sub
audience, // aud audience, // aud
now + tokenDuration, // exp -1, // placeholder exp for the cache
now, // nbf -1, // placeholder nbf for the cache
UUID.randomUUID().toString() // jti null // placeholder jti for the cache
); );
ClientCredentialsAuthArgs authArgs = ClientCredentialsAuthArgs.create( ClientCredentialsAuthArgs authArgs = ClientCredentialsAuthArgs.create(
credentialsSupplier.get().identity, credentialsSupplier.get().identity,
@ -112,9 +124,5 @@ public class ClientCredentialsJWTBearerTokenFlow implements OAuthFilter {
String authorization = String.format("%s %s", token.tokenType(), token.accessToken()); String authorization = String.format("%s %s", token.tokenType(), token.accessToken());
return request.toBuilder().addHeader("Authorization", authorization).build(); return request.toBuilder().addHeader("Authorization", authorization).build();
} }
long currentTimeSeconds() {
return System.currentTimeMillis() / 1000;
}
} }

View File

@ -55,7 +55,6 @@ public class ClientCredentialsSecretFlow implements OAuthFilter {
private static final Joiner ON_SPACE = Joiner.on(" "); private static final Joiner ON_SPACE = Joiner.on(" ");
private final Supplier<Credentials> credentialsSupplier; private final Supplier<Credentials> credentialsSupplier;
private final long tokenDuration;
private final LoadingCache<ClientSecret, Token> tokenCache; private final LoadingCache<ClientSecret, Token> tokenCache;
private final String resource; private final String resource;
private final OAuthScopes scopes; private final OAuthScopes scopes;
@ -67,7 +66,6 @@ public class ClientCredentialsSecretFlow implements OAuthFilter {
this.credentialsSupplier = credentialsSupplier; this.credentialsSupplier = credentialsSupplier;
this.scopes = scopes; this.scopes = scopes;
this.resource = resource; this.resource = resource;
this.tokenDuration = tokenDuration;
// since the session interval is also the token expiration time requested to the server make the token expire a // since the session interval is also the token expiration time requested to the server make the token expire a
// bit before the deadline to make sure there aren't session expiration exceptions // bit before the deadline to make sure there aren't session expiration exceptions
long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration; long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration;
@ -87,21 +85,15 @@ public class ClientCredentialsSecretFlow implements OAuthFilter {
} }
@Override public HttpRequest filter(HttpRequest request) throws HttpException { @Override public HttpRequest filter(HttpRequest request) throws HttpException {
long now = currentTimeSeconds();
List<String> configuredScopes = scopes.forRequest(request); List<String> configuredScopes = scopes.forRequest(request);
ClientSecret client = ClientSecret.create( ClientSecret client = ClientSecret.create(
credentialsSupplier.get().identity, credentialsSupplier.get().identity,
credentialsSupplier.get().credential, credentialsSupplier.get().credential,
resource == null ? "" : resource, resource == null ? "" : resource,
configuredScopes.isEmpty() ? null : ON_SPACE.join(configuredScopes), configuredScopes.isEmpty() ? null : ON_SPACE.join(configuredScopes)
now + tokenDuration
); );
Token token = tokenCache.getUnchecked(client); Token token = tokenCache.getUnchecked(client);
String authorization = String.format("%s %s", token.tokenType(), token.accessToken()); String authorization = String.format("%s %s", token.tokenType(), token.accessToken());
return request.toBuilder().addHeader("Authorization", authorization).build(); return request.toBuilder().addHeader("Authorization", authorization).build();
} }
long currentTimeSeconds() {
return System.currentTimeMillis() / 1000;
}
} }

View File

@ -32,6 +32,7 @@ import org.jclouds.oauth.v2.config.OAuthScopes;
import org.jclouds.oauth.v2.domain.Claims; import org.jclouds.oauth.v2.domain.Claims;
import org.jclouds.oauth.v2.domain.Token; import org.jclouds.oauth.v2.domain.Token;
import com.google.auto.value.AutoValue;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
@ -53,30 +54,36 @@ public class JWTBearerTokenFlow implements OAuthFilter {
private final String audience; private final String audience;
private final Supplier<Credentials> credentialsSupplier; private final Supplier<Credentials> credentialsSupplier;
private final OAuthScopes scopes; private final OAuthScopes scopes;
private final long tokenDuration; private final LoadingCache<TokenCacheKey, Token> tokenCache;
private final LoadingCache<Claims, Token> tokenCache;
@Inject JWTBearerTokenFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration, @Inject JWTBearerTokenFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration,
@Provider Supplier<Credentials> credentialsSupplier, OAuthScopes scopes, @Named(AUDIENCE) String audience) { @Provider Supplier<Credentials> credentialsSupplier, OAuthScopes scopes, @Named(AUDIENCE) String audience) {
this.credentialsSupplier = credentialsSupplier; this.credentialsSupplier = credentialsSupplier;
this.scopes = scopes; this.scopes = scopes;
this.audience = audience; this.audience = audience;
this.tokenDuration = tokenDuration;
// since the session interval is also the token expiration time requested to the server make the token expire a // since the session interval is also the token expiration time requested to the server make the token expire a
// bit before the deadline to make sure there aren't session expiration exceptions // bit before the deadline to make sure there aren't session expiration exceptions
long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration; long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration;
this.tokenCache = CacheBuilder.newBuilder().expireAfterWrite(cacheExpirationSeconds, SECONDS).build(loader); this.tokenCache = CacheBuilder.newBuilder().expireAfterWrite(cacheExpirationSeconds, SECONDS).build(loader);
} }
static final class AuthorizeToken extends CacheLoader<Claims, Token> { static final class AuthorizeToken extends CacheLoader<TokenCacheKey, Token> {
private final AuthorizationApi api; private final AuthorizationApi api;
private final long tokenDuration;
@Inject AuthorizeToken(AuthorizationApi api) { @Inject AuthorizeToken(AuthorizationApi api, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration) {
this.api = api; this.api = api;
this.tokenDuration = tokenDuration;
} }
@Override public Token load(Claims key) throws Exception { @Override public Token load(TokenCacheKey tokenCacheKey) throws Exception {
return api.authorize(key); final Claims claims = Claims.create(
tokenCacheKey.claims().iss(),
tokenCacheKey.claims().scope(),
tokenCacheKey.claims().aud(),
tokenCacheKey.startTime + tokenDuration,
tokenCacheKey.startTime);
return api.authorize(claims);
} }
} }
@ -86,10 +93,11 @@ public class JWTBearerTokenFlow implements OAuthFilter {
credentialsSupplier.get().identity, // iss credentialsSupplier.get().identity, // iss
ON_COMMA.join(scopes.forRequest(request)), // scope ON_COMMA.join(scopes.forRequest(request)), // scope
audience, // aud audience, // aud
now + tokenDuration, // exp -1, // placeholder exp for the cache
now // iat -1 // placeholder iat for the cache
); );
Token token = tokenCache.getUnchecked(claims); final TokenCacheKey tokenCacheKey = TokenCacheKey.create(claims, now);
Token token = tokenCache.getUnchecked(tokenCacheKey);
String authorization = String.format("%s %s", token.tokenType(), token.accessToken()); String authorization = String.format("%s %s", token.tokenType(), token.accessToken());
return request.toBuilder().addHeader("Authorization", authorization).build(); return request.toBuilder().addHeader("Authorization", authorization).build();
} }
@ -97,4 +105,20 @@ public class JWTBearerTokenFlow implements OAuthFilter {
long currentTimeSeconds() { long currentTimeSeconds() {
return System.currentTimeMillis() / 1000; return System.currentTimeMillis() / 1000;
} }
@AutoValue
abstract static class TokenCacheKey {
public abstract Claims claims();
long startTime;
public static TokenCacheKey create(Claims claims, long startTime) {
final AutoValue_JWTBearerTokenFlow_TokenCacheKey tokenCacheKey = new AutoValue_JWTBearerTokenFlow_TokenCacheKey(claims);
tokenCacheKey.startTime = startTime;
return tokenCacheKey;
}
TokenCacheKey() {
}
}
} }