com.aerospike.client.proxy.auth.AuthTokenManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aerospike-proxy-client Show documentation
Show all versions of aerospike-proxy-client Show documentation
Aerospike Java proxy client interface for database-as-a-service (dbaas).
The newest version!
/*
* Copyright 2012-2023 Aerospike, Inc.
*
* Portions may be licensed to Aerospike, Inc. under one or more contributor
* license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
*
* 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 com.aerospike.client.proxy.auth;
import java.io.Closeable;
import java.io.IOException;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import com.aerospike.client.AerospikeException;
import com.aerospike.client.Log;
import com.aerospike.client.ResultCode;
import com.aerospike.client.policy.ClientPolicy;
import com.aerospike.client.proxy.auth.credentials.BearerTokenCallCredentials;
import com.aerospike.client.proxy.grpc.GrpcChannelProvider;
import com.aerospike.client.proxy.grpc.GrpcConversions;
import com.aerospike.proxy.client.Auth;
import com.aerospike.proxy.client.AuthServiceGrpc;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.grpc.CallOptions;
import io.grpc.Deadline;
import io.grpc.ManagedChannel;
import io.grpc.stub.StreamObserver;
/**
* An access token manager for Aerospike proxy.
*/
public class AuthTokenManager implements Closeable {
/**
* A conservative estimate of minimum amount of time in millis it takes for
* token refresh to complete. Auto refresh should be scheduled at least
* this amount before expiry, i.e, if remaining expiry time is less than
* this amount refresh should be scheduled immediately.
*/
private static final int refreshMinTime = 5000;
/**
* A cap on refresh time in millis to throttle an auto refresh requests in
* case of token refresh failure.
*/
private static final int maxExponentialBackOff = 15000;
/**
* Fraction of token expiry time to elapse before scheduling an auto
* refresh.
*
* @see AuthTokenManager#refreshMinTime
*/
private static final float refreshAfterFraction = 0.95f;
/**
* An {@link ObjectMapper} to parse access token.
*/
private static final ObjectMapper objectMapper = new ObjectMapper();
private final ClientPolicy clientPolicy;
private final GrpcChannelProvider channelProvider;
private final ScheduledExecutorService executor;
private final AtomicBoolean isFetchingToken = new AtomicBoolean(false);
private final AtomicBoolean isClosed = new AtomicBoolean(false);
/**
* Count of consecutive errors while refreshing the token.
*/
private final AtomicInteger consecutiveRefreshErrors = new AtomicInteger(0);
/**
* The error encountered when refreshing the token. It will be null when
* {@link #consecutiveRefreshErrors} is zero.
*/
private final AtomicReference refreshError =
new AtomicReference<>(null);
private volatile AccessToken accessToken;
private volatile boolean fetchScheduled;
/**
* A {@link ScheduledFuture} holding reference to the next auto schedule task.
*/
private ScheduledFuture> refreshFuture;
public AuthTokenManager(ClientPolicy clientPolicy, GrpcChannelProvider grpcCallExecutor) {
this.clientPolicy = clientPolicy;
this.channelProvider = grpcCallExecutor;
this.executor = Executors.newSingleThreadScheduledExecutor(
new ThreadFactoryBuilder().setNameFormat("aerospike-auth-manager").build());
this.accessToken = new AccessToken(System.currentTimeMillis(), 0, "");
fetchToken(true);
}
/**
* Fetch the new token if expired or scheduled for auto refresh.
*
* @param forceRefresh A boolean flag to refresh token forcefully. This is required for initialization and auto
* refresh. Auto refresh will get rejected as token won't be expired at that time, but we need
* to refresh it beforehand. If true, this function will run from the invoking thread,
* not from the scheduler.
*/
private void fetchToken(boolean forceRefresh) {
fetchScheduled = false;
if (isClosed.get() || !isTokenRequired() || isFetchingToken.get()) {
return;
}
if (shouldRefresh(forceRefresh)) {
try {
if (Log.debugEnabled()) {
Log.debug("Starting token refresh");
}
Auth.AerospikeAuthRequest aerospikeAuthRequest = Auth.AerospikeAuthRequest.newBuilder()
.setUsername(clientPolicy.user).setPassword(clientPolicy.password).build();
ManagedChannel channel = channelProvider.getControlChannel();
if (channel == null) {
isFetchingToken.set(false);
// Channel is unavailable. Try again.
unsafeScheduleRefresh(10, true);
return;
}
isFetchingToken.set(true);
AuthServiceGrpc.newStub(channel).withDeadline(Deadline.after(refreshMinTime, TimeUnit.MILLISECONDS))
.get(aerospikeAuthRequest, new StreamObserver() {
@Override
public void onNext(Auth.AerospikeAuthResponse aerospikeAuthResponse) {
try {
accessToken =
parseToken(aerospikeAuthResponse.getToken());
if (Log.debugEnabled()) {
Log.debug(String.format("Fetched token successfully " +
"with TTL %d", accessToken.ttl));
}
unsafeScheduleNextRefresh();
clearRefreshErrors();
}
catch (Exception e) {
onFetchError(e);
}
}
@Override
public void onError(Throwable t) {
onFetchError(t);
}
@Override
public void onCompleted() {
isFetchingToken.set(false);
}
});
}
catch (Exception e) {
onFetchError(e);
}
}
}
private void clearRefreshErrors() {
consecutiveRefreshErrors.set(0);
refreshError.set(null);
}
private void updateRefreshErrors(Throwable t) {
consecutiveRefreshErrors.incrementAndGet();
refreshError.set(t);
}
private void onFetchError(Throwable t) {
updateRefreshErrors(t);
Exception e = new Exception("Error fetching access token", t);
Log.error(GrpcConversions.getDisplayMessage(e, GrpcConversions.MAX_ERR_MSG_LENGTH));
unsafeScheduleNextRefresh();
isFetchingToken.set(false);
}
private boolean shouldRefresh(boolean forceRefresh) {
return forceRefresh || !isTokenValid();
}
private void unsafeScheduleNextRefresh() {
long ttl = accessToken.ttl;
long delay = (long)Math.floor(ttl * refreshAfterFraction);
if (ttl - delay < refreshMinTime) {
// We need at least refreshMinTimeMillis to refresh, schedule
// immediately.
delay = ttl - refreshMinTime;
}
if (!isTokenValid()) {
// Force immediate refresh.
delay = 0;
}
if (delay == 0 && consecutiveRefreshErrors.get() > 0) {
// If we continue to fail then schedule will be too aggressive on fetching new token. Avoid that by increasing
// fetch delay.
delay = (long)(Math.pow(2, consecutiveRefreshErrors.get()) * 1000);
if (delay > maxExponentialBackOff) {
delay = maxExponentialBackOff;
}
// Handle wrap around.
if (delay < 0) {
delay = 0;
}
}
unsafeScheduleRefresh(delay, true);
}
private void unsafeScheduleRefresh(long delay, boolean forceRefresh) {
if (isClosed.get() || !forceRefresh || fetchScheduled) {
return;
}
if (!executor.isShutdown()) {
//noinspection ConstantValue
refreshFuture = executor.schedule(() -> fetchToken(forceRefresh), delay, TimeUnit.MILLISECONDS);
fetchScheduled = true;
if (Log.debugEnabled()) {
Log.debug(String.format("Scheduled refresh after %d millis", delay));
}
}
}
private boolean isTokenRequired() {
return clientPolicy.user != null;
}
private AccessToken parseToken(String token) throws IOException {
String claims = token.split("\\.")[1];
byte[] decodedClaims = Base64.getUrlDecoder().decode(claims);
@SuppressWarnings("unchecked")
Map
© 2015 - 2024 Weber Informatics LLC | Privacy Policy