
com.sap.cloud.sdk.cloudplatform.security.AuthTokenDecoder Maven / Gradle / Ivy
/*
* Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved.
*/
package com.sap.cloud.sdk.cloudplatform.security;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Enumeration;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.net.HttpHeaders;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.sap.cloud.sdk.cloudplatform.CloudPlatform;
import com.sap.cloud.sdk.cloudplatform.CloudPlatformAccessor;
import com.sap.cloud.sdk.cloudplatform.ScpCfCloudPlatform;
import com.sap.cloud.sdk.cloudplatform.exception.CloudPlatformException;
import com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException;
import com.sap.cloud.sdk.cloudplatform.security.exception.AuthTokenAccessException;
class AuthTokenDecoder
{
private static final String BEARER_PREFIX = "Bearer ";
private int getNumberOfAuthHeaders( final HttpServletRequest request )
{
@Nullable
final Enumeration headers = request.getHeaders(HttpHeaders.AUTHORIZATION);
int numHeaders = 0;
if( headers != null ) {
while( headers.hasMoreElements() ) {
headers.nextElement();
++numHeaders;
}
}
return numHeaders;
}
private ScpCfCloudPlatform getCloudPlatform()
{
final CloudPlatform cloudPlatform = CloudPlatformAccessor.getCloudPlatform();
if( !(cloudPlatform instanceof ScpCfCloudPlatform) ) {
throw new ShouldNotHappenException(
"The current Cloud platform is not an instance of "
+ ScpCfCloudPlatform.class.getSimpleName()
+ ". Please make sure to specify a dependency to com.sap.cloud.s4hana.cloudplatform:core-scp-cf.");
}
return (ScpCfCloudPlatform) cloudPlatform;
}
@Nonnull
private RSAPublicKey getVerificationPublicKey( @Nonnull final String encodedJwt )
throws AuthTokenAccessException
{
final JsonObject xsuaaServiceCredentials;
try {
final DecodedJWT decodedJWT = JWT.decode(encodedJwt); // just decode, no verify
xsuaaServiceCredentials = getCloudPlatform().getXsuaaServiceCredentials(decodedJWT);
}
catch( final CloudPlatformException | JWTDecodeException e ) {
throw new AuthTokenAccessException("Failed to verify JWT bearer.", e);
}
@Nullable
final JsonElement verificationKey = xsuaaServiceCredentials.get("verificationkey");
if( verificationKey == null ) {
throw new AuthTokenAccessException(
"Failed to verify JWT bearer: no verification key found in XSUAA service credentials.");
}
final String publicKey =
verificationKey.getAsString().replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace(
"-----END PUBLIC KEY-----",
"");
try {
final KeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(keySpec);
}
catch( final NoSuchAlgorithmException | InvalidKeySpecException e ) {
throw new AuthTokenAccessException("Failed to verify JWT bearer.", e);
}
}
@Nonnull
private
Algorithm
getVerificationAlgorithm( @Nonnull final RSAPublicKey verificationKey, @Nonnull final String tokenValue )
throws AuthTokenAccessException
{
try {
@Nullable
final String algorithmIdentifier = JWT.decode(tokenValue).getAlgorithm(); // just decode, no verify
if( algorithmIdentifier == null ) {
throw new AuthTokenAccessException(
"Failed to verify JWT bearer: no algorithm specified in token header.");
}
// limit the supported algorithms to RSA with different hash lengths to avoid the risks described at:
// https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
switch( algorithmIdentifier ) {
case "RS256":
return Algorithm.RSA256(verificationKey, null);
case "RS384":
return Algorithm.RSA384(verificationKey, null);
case "RS512":
return Algorithm.RSA512(verificationKey, null);
default:
throw new AuthTokenAccessException(
"Failed to verify JWT bearer: algorithm '" + algorithmIdentifier + "' not supported.");
}
}
catch( final JWTDecodeException | IllegalArgumentException e ) {
throw new AuthTokenAccessException("Failed to verify JWT bearer.", e);
}
}
@Nonnull
AuthToken decode( @Nonnull final String encodedJwt, @Nullable final String refreshToken )
throws AuthTokenAccessException
{
final RSAPublicKey verificationKey = getVerificationPublicKey(encodedJwt);
final Algorithm verificationAlgorithm = getVerificationAlgorithm(verificationKey, encodedJwt);
try {
final JWTVerifier verifier = JWT.require(verificationAlgorithm).build();
DecodedJWT jwt;
try {
jwt = verifier.verify(encodedJwt);
}
catch( final TokenExpiredException e ) {
jwt = Optional.ofNullable(refreshToken).map(token -> {
final String refreshedEncodedJwt = new RefreshJwtTokenCommand(encodedJwt, refreshToken).run();
return verifier.verify(refreshedEncodedJwt);
}).orElseThrow(() -> e);
}
return new AuthToken(jwt);
}
catch( final IllegalArgumentException | JWTVerificationException e ) {
throw new AuthTokenAccessException("Failed to verify JWT bearer.", e);
}
}
@Nonnull
Optional decode( @Nonnull final HttpServletRequest request )
throws AuthTokenAccessException
{
@Nullable
final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if( authorizationHeader == null ) {
return Optional.empty();
}
if( getNumberOfAuthHeaders(request) != 1 ) {
throw new AuthTokenAccessException(
"Failed to decode JWT bearer: multiple '"
+ HttpHeaders.AUTHORIZATION
+ "' headers present in request.");
}
if( !authorizationHeader.startsWith(BEARER_PREFIX) ) {
throw new AuthTokenAccessException(
"Failed to decode JWT bearer: no JWT bearer present in '"
+ HttpHeaders.AUTHORIZATION
+ "' header of request.");
}
final String tokenValue = authorizationHeader.substring(BEARER_PREFIX.length());
return Optional.of(decode(tokenValue, null));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy