/** * 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. See accompanying LICENSE file. */ package com.hortonworks.registries.auth.util; import java.util.Properties; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.servlet.ServletContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An abstract SignerSecretProvider that can be use used as the base for a * rolling secret. The secret will roll over at the same interval as the token * validity, so there are only ever a maximum of two valid secrets at any * given time. This class handles storing and returning the secrets, as well * as the rolling over. At a minimum, subclasses simply need to implement the * generateNewSecret() method. More advanced implementations can override * other methods to provide more advanced behavior, but should be careful when * doing so. */ public abstract class RolloverSignerSecretProvider extends SignerSecretProvider { private static Logger LOG = LoggerFactory.getLogger( RolloverSignerSecretProvider.class); /** * Stores the currently valid secrets. The current secret is the 0th element * in the array. */ private volatile byte[][] secrets; private ScheduledExecutorService scheduler; private boolean schedulerRunning; private boolean isDestroyed; public RolloverSignerSecretProvider() { schedulerRunning = false; isDestroyed = false; } /** * Initialize the SignerSecretProvider. It initializes the current secret * and starts the scheduler for the rollover to run at an interval of * tokenValidity. * @param config configuration properties * @param servletContext servlet context * @param tokenValidity The amount of time a token is valid for * @throws Exception */ @Override public void init(Properties config, ServletContext servletContext, long tokenValidity) throws Exception { initSecrets(generateNewSecret(), null); startScheduler(tokenValidity, tokenValidity); } /** * Initializes the secrets array. This should typically be called only once, * during init but some implementations may wish to call it other times. * previousSecret can be null if there isn't a previous secret, but * currentSecret should never be null. * @param currentSecret The current secret * @param previousSecret The previous secret */ protected void initSecrets(byte[] currentSecret, byte[] previousSecret) { secrets = new byte[][]{currentSecret, previousSecret}; } /** * Starts the scheduler for the rollover to run at an interval. * @param initialDelay The initial delay in the rollover in milliseconds * @param period The interval for the rollover in milliseconds */ protected synchronized void startScheduler(long initialDelay, long period) { if (!schedulerRunning) { schedulerRunning = true; scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { rollSecret(); } }, initialDelay, period, TimeUnit.MILLISECONDS); } } @Override public synchronized void destroy() { if (!isDestroyed) { isDestroyed = true; if (scheduler != null) { scheduler.shutdown(); } schedulerRunning = false; super.destroy(); } } /** * Rolls the secret. It is called automatically at the rollover interval. */ protected synchronized void rollSecret() { if (!isDestroyed) { LOG.debug("rolling secret"); byte[] newSecret = generateNewSecret(); secrets = new byte[][]{newSecret, secrets[0]}; } } /** * Subclasses should implement this to return a new secret. It will be called * automatically at the secret rollover interval. It should never return null. * @return a new secret */ protected abstract byte[] generateNewSecret(); @Override public byte[] getCurrentSecret() { return secrets[0]; } @Override public byte[][] getAllSecrets() { return secrets; } }