All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nimbusds.common.oauth2.SHA256BasedAccessTokenValidator Maven / Gradle / Ivy

The newest version!
package com.nimbusds.common.oauth2;


import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.thetransactioncompany.util.PropertyParseException;
import com.thetransactioncompany.util.PropertyRetriever;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.WebApplicationException;
import net.jcip.annotations.ThreadSafe;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.util.Collections;
import java.util.List;


/**
 * SHA-256 based access token validator. The expected access tokens are
 * configured as their SHA-256 hashes, to prevent accidental leaks into logs,
 * etc. Supports servlet-based and JAX-RS based web applications.
 */
@ThreadSafe
public class SHA256BasedAccessTokenValidator extends AbstractAccessTokenValidator {
	
	
	/**
	 * The minimum acceptable access token length.
	 */
	public static final int MIN_TOKEN_LENGTH = 32;


	/**
	 * The demo access token.
	 */
	public static final BearerAccessToken DEMO_ACCESS_TOKEN = new BearerAccessToken("ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6");


	/**
	 * The demo access token SHA-256 hash (in hex).
	 */
	public static final String DEMO_ACCESS_TOKEN_HASH = "cca68b8b82bcf0b96cb826199429e50cd95a042f8e8891d1ac56ab135d096633";
	
	
	/**
	 * Creates a new access token validator.
	 *
	 * @param tokenHash The Bearer access token SHA-256 hash (in hex). If
	 *                  {@code null} access to the web API will be
	 *                  disabled.
	 */
	public SHA256BasedAccessTokenValidator(final String tokenHash) {
		
		this(new String[]{tokenHash});
	}
	
	/**
	 * Creates a new access token validator.
	 *
	 * @param tokenHashes The Bearer access token SHA-256 hashes (in hex).
	 *                    If {@code null} access to the web API will be
	 *                    disabled.
	 */
	public SHA256BasedAccessTokenValidator(final String ... tokenHashes) {
		
		for (String hash: tokenHashes) {
			if (hash == null) continue;
			try {
				expectedTokenHashes.add(Hex.decodeHex(hash.toCharArray()));
			} catch (DecoderException e) {
				throw new IllegalArgumentException("Invalid hex for access token SHA-256: " + hash);
			}
		}
		
		hashSalt = null;
	}
	
	
	/**
	 * Creates a new access token validator.
	 *
	 * @param tokenHash             The main Bearer access token SHA-256
	 *                              hash (in hex). If {@code null} access
	 *                              to the web API will be disabled.
	 * @param additionalTokenHashes Additional Bearer access token SHA-256
	 *                              hashes (in hex), empty or {@code null}
	 *                              if none.
	 */
	public SHA256BasedAccessTokenValidator(final String tokenHash, final List additionalTokenHashes) {
	
		if (tokenHash == null) {
			return;
		}
		
		try {
			expectedTokenHashes.add(Hex.decodeHex(tokenHash.toCharArray()));
		} catch (DecoderException e) {
			throw new IllegalArgumentException("Invalid hex for access token SHA-256: " + tokenHash);
		}
		
		if (additionalTokenHashes != null) {
			for (String hash: additionalTokenHashes) {
				if (hash != null) {
					try {
						expectedTokenHashes.add(Hex.decodeHex(hash.toCharArray()));
					} catch (DecoderException e) {
						throw new IllegalArgumentException("Invalid hex for access token SHA-256: " + hash);
					}
				}
			}
		}
	}
	
	
	/**
	 * Creates a new access token validator from the specified properties
	 * retriever.
	 *
	 * @param pr                           The properties retriever. Must
	 *                                     not be {@code null}.
	 * @param propertyName                 The property name for the main
	 *                                     Bearer access token SHA-256 hash
	 *                                     (in hex). If {@code null} access
	 * 	                               to the web API will be disabled.
	 * 	                               Must not be {@code null}.
	 * @param propertyRequired             {@code true} if the property is
	 *                                     required, {@code false} if
	 *                                     optional.
	 * @param additionalPropertyNamePrefix The property name prefix for the
	 *                                     additional Bearer access token
	 *                                     SHA-256 hashes (in hex),
	 *                                     {@code null} if not used.
	 *
	 * @return The access token validator.
	 *
	 * @throws PropertyParseException If parsing failed.
	 */
	public static SHA256BasedAccessTokenValidator from(final PropertyRetriever pr,
							   final String propertyName,
							   final boolean propertyRequired,
							   final String additionalPropertyNamePrefix)
		throws PropertyParseException {
		
		String tokenHash;
		if (propertyRequired) {
			tokenHash = pr.getString(propertyName);
		} else {
			tokenHash = pr.getOptString(propertyName, null);
		}

		if (tokenHash != null) {
			ensureNoDemoInProductionEnv(propertyName, Collections.singletonList(tokenHash));
		}

		if (additionalPropertyNamePrefix == null) {
			return new SHA256BasedAccessTokenValidator(tokenHash);
		}
		
		List additionalTokenHashes = pr.getOptStringListMulti(additionalPropertyNamePrefix, Collections.emptyList());

		ensureNoDemoInProductionEnv(propertyName, additionalTokenHashes);
		
		return new SHA256BasedAccessTokenValidator(tokenHash, additionalTokenHashes);
	}


	private static void ensureNoDemoInProductionEnv(final String propertyName, final List tokenHashes)
		throws PropertyParseException {

		if (tokenHashes == null || tokenHashes.isEmpty()) {
			return;
		}

		final boolean isProductionEnv = "prod".equals(System.getProperties().getProperty("env", "dev"));

		if (! isProductionEnv) {
			return;
		}

		for (String tokenHash: tokenHashes) {
			if (tokenHash == null) {
				continue;
			}
			if (DEMO_ACCESS_TOKEN_HASH.equals(tokenHash)) {
				throw new PropertyParseException(
					"The web API is configured with the demo access token, " +
					"generate a new token for production use", propertyName);
			}
		}
	}
	
	
	@Override
	public void validateBearerAccessToken(final String authzHeader)
		throws WebApplicationException {
		
		// Web API disabled?
		if (accessIsDisabled()) {
			throw WEB_API_DISABLED.toWebAppException();
		}
		
		if (StringUtils.isBlank(authzHeader)) {
			throw MISSING_BEARER_TOKEN.toWebAppException();
		}
		
		BearerAccessToken receivedToken;
		
		try {
			receivedToken = BearerAccessToken.parse(authzHeader);
			
		} catch (ParseException e) {
			throw MISSING_BEARER_TOKEN.toWebAppException();
		}
		
		if (null != log) {
			log.trace("[CM3000] Validating bearer access token: {}", TokenAbbreviator.abbreviate(receivedToken));
		}
		
		// Check min length
		if (receivedToken.getValue().length() < MIN_TOKEN_LENGTH) {
			throw INVALID_BEARER_TOKEN.toWebAppException();
		}
		
		if (isValid(receivedToken)) {
			return; // pass
		}
		
		throw INVALID_BEARER_TOKEN.toWebAppException();
	}
	
	
	@Override
	public boolean validateBearerAccessToken(final HttpServletRequest servletRequest,
						 final HttpServletResponse servletResponse)
		throws IOException {
		
		// Web API disabled?
		if (accessIsDisabled()) {
			WEB_API_DISABLED.apply(servletResponse);
			return false;
		}
		
		BearerAccessToken receivedToken;
		
		if (servletRequest.getHeader("Authorization") != null) {
			
			String authzHeaderValue = servletRequest.getHeader("Authorization");
			
			if (StringUtils.isBlank(authzHeaderValue)) {
				MISSING_BEARER_TOKEN.apply(servletResponse);
				return false;
			}
			
			try {
				receivedToken = BearerAccessToken.parse(authzHeaderValue);
				
			} catch (ParseException e) {
				MISSING_BEARER_TOKEN.apply(servletResponse);
				return false;
			}
			
		} else if (servletRequest.getParameter("access_token") != null) {
			
			String accessTokenValue = servletRequest.getParameter("access_token");
			
			if (StringUtils.isBlank(accessTokenValue)) {
				MISSING_BEARER_TOKEN.apply(servletResponse);
				return false;
			}
			
			receivedToken = new BearerAccessToken(accessTokenValue);
		} else {
			MISSING_BEARER_TOKEN.apply(servletResponse);
			return false;
		}
		
		if (null != log) {
			log.trace("[CM3000] Validating bearer access token: {}", TokenAbbreviator.abbreviate(receivedToken));
		}
		
		// Check min length
		if (receivedToken.getValue().length() < MIN_TOKEN_LENGTH) {
			INVALID_BEARER_TOKEN.apply(servletResponse);
			return false;
		}
		
		// Compare hashes
		if (isValid(receivedToken)) {
			return true; // pass
		}
		
		INVALID_BEARER_TOKEN.apply(servletResponse);
		return false;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy