blob: b77e67838ce0fe10c2cf8daeba6fb1694fa785cf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to you 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.apache.knox.gateway.services.token.impl;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.security.token.TokenStateService;
import org.apache.knox.gateway.services.security.token.impl.JWTToken;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* In-Memory authentication token state management implementation.
*/
public class DefaultTokenStateService implements TokenStateService {
protected static final long DEFAULT_RENEWAL_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
protected static final int MAX_RENEWALS = 7;
protected static final long DEFAULT_MAX_LIFETIME = MAX_RENEWALS * DEFAULT_RENEWAL_INTERVAL; // 7 days
private final Map<String, Long> tokenExpirations = new HashMap<>();
private final Set<String> revokedTokens = new HashSet<>();
private final Map<String, Long> maxTokenLifetimes = new HashMap<>();
@Override
public void init(final GatewayConfig config, final Map<String, String> options) throws ServiceLifecycleException {
}
@Override
public void start() throws ServiceLifecycleException {
}
@Override
public void stop() throws ServiceLifecycleException {
}
@Override
public long getDefaultRenewInterval() {
return DEFAULT_RENEWAL_INTERVAL;
}
@Override
public long getDefaultMaxLifetimeDuration() {
return DEFAULT_MAX_LIFETIME;
}
@Override
public void addToken(final JWTToken token, long issueTime) {
if (token == null) {
throw new IllegalArgumentException("Token data cannot be null.");
}
addToken(token.getPayload(), issueTime, token.getExpiresDate().getTime());
}
@Override
public void addToken(final String token, long issueTime, long expiration) {
addToken(token, issueTime, expiration, getDefaultMaxLifetimeDuration());
}
@Override
public void addToken(final String token,
long issueTime,
long expiration,
long maxLifetimeDuration) {
if (!isValidIdentifier(token)) {
throw new IllegalArgumentException("Token data cannot be null.");
}
synchronized (tokenExpirations) {
tokenExpirations.put(token, expiration);
}
setMaxLifetime(token, issueTime, maxLifetimeDuration);
}
@Override
public long getTokenExpiration(final String token) {
long expiration;
validateToken(token);
synchronized (tokenExpirations) {
expiration = tokenExpirations.get(token);
}
return expiration;
}
@Override
public long renewToken(final JWTToken token) {
return renewToken(token, DEFAULT_RENEWAL_INTERVAL);
}
@Override
public long renewToken(final JWTToken token, long renewInterval) {
if (token == null) {
throw new IllegalArgumentException("Token data cannot be null.");
}
return renewToken(token.getPayload(), renewInterval);
}
@Override
public long renewToken(final String token) { // Should return new expiration?
return renewToken(token, DEFAULT_RENEWAL_INTERVAL);
}
@Override
public long renewToken(final String token, long renewInterval) {
long expiration;
validateToken(token, true);
// Make sure the maximum lifetime has not been (and will not be) exceeded
if (hasRemainingRenewals(token, renewInterval)) {
expiration = System.currentTimeMillis() + renewInterval;
updateExpiration(token, expiration);
} else {
throw new IllegalArgumentException("The renewal limit for the token has been exceeded");
}
return expiration;
}
@Override
public void revokeToken(final JWTToken token) {
if (token == null) {
throw new IllegalArgumentException("Token data cannot be null.");
}
revokeToken(token.getPayload());
}
@Override
public void revokeToken(final String token) {
validateToken(token);
revokedTokens.add(token);
}
@Override
public boolean isExpired(final JWTToken token) {
return isExpired(token.getPayload());
}
@Override
public boolean isExpired(final String token) {
boolean isExpired;
isExpired = isRevoked(token); // Check if it has been revoked first
if (!isExpired) {
// If it has not been revoked, check its expiration
isExpired = (getTokenExpiration(token) <= System.currentTimeMillis());
}
return isExpired;
}
protected void setMaxLifetime(final String token, long issueTime, long maxLifetimeDuration) {
synchronized (maxTokenLifetimes) {
maxTokenLifetimes.put(token, issueTime + maxLifetimeDuration);
}
}
/**
* @param token token to check
* @return false, if the service has previously stored the specified token; Otherwise, true.
*/
protected boolean isUnknown(final String token) {
boolean isUnknown;
synchronized (tokenExpirations) {
isUnknown = !(tokenExpirations.containsKey(token));
}
return isUnknown;
}
protected void updateExpiration(final String token, long expiration) {
synchronized (tokenExpirations) {
tokenExpirations.replace(token, expiration);
}
}
protected boolean hasRemainingRenewals(final String token, long renewInterval) {
// Is the current time + 30-second buffer + the renewal interval is less than the max lifetime for the token?
return ((System.currentTimeMillis() + 30000 + renewInterval) < getMaxLifetime(token));
}
protected long getMaxLifetime(final String token) {
long result;
synchronized (maxTokenLifetimes) {
result = maxTokenLifetimes.getOrDefault(token, 0L);
}
return result;
}
protected boolean isRevoked(final String token) {
return revokedTokens.contains(token);
}
protected boolean isValidIdentifier(final String token) {
return token != null && !token.isEmpty();
}
/**
* Validate the specified token identifier.
*
* @param token The token identifier to validate.
*
* @throws IllegalArgumentException if the specified token in invalid.
*/
protected void validateToken(final String token) throws IllegalArgumentException {
validateToken(token, false);
}
/**
* Validate the specified token identifier.
*
* @param token The token identifier to validate.
* @param includeRevocation true, if the revocation status of the specified token should be considered in the validation.
*
* @throws IllegalArgumentException if the specified token in invalid.
*/
protected void validateToken(final String token, boolean includeRevocation) throws IllegalArgumentException {
if (!isValidIdentifier(token)) {
throw new IllegalArgumentException("Token data cannot be null.");
}
// First, make sure the token is one we know about
if (isUnknown(token)) {
throw new IllegalArgumentException("Unknown token");
}
// Then, make sure it has not been revoked
if (includeRevocation && isRevoked(token)) {
throw new IllegalArgumentException("The specified token has been revoked");
}
}
}