package jwt4j;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import jwt4j.exceptions.AlgorithmException;
import jwt4j.exceptions.InvalidSignatureException;
import jwt4j.exceptions.InvalidTokenException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JWTDecoder
{
private final Base64.Decoder decoder;
private final Algorithm algorithm;
private final byte[] secret;
private final Gson gson;
private final List<TokenChecker> tokenCheckers;
public JWTDecoder(final Algorithm algorithm,
final byte[] secret,
final Gson gson,
final List<TokenChecker> tokenCheckers)
{
this.decoder = Base64.getDecoder();
this.algorithm = algorithm;
this.secret = secret;
this.gson = gson;
this.tokenCheckers = Collections.unmodifiableList(tokenCheckers);
}
public Map<String, String> decode(final String token)
{
final String[] tokenParts = getTokenParts(token);
final JsonObject headerJson = gson.fromJson(new String(decoder.decode(tokenParts[0])), JsonObject.class);
final JsonObject payloadJson = gson.fromJson(new String(decoder.decode(tokenParts[1])), JsonObject.class);
final Algorithm tokenAlgorithm = Algorithm.valueOf(headerJson.get(JWTConstants.ALGORITHM).getAsString());
if (tokenAlgorithm != algorithm) {
throw new IllegalStateException(tokenAlgorithm + " is not supported by this decoder.");
}
if (algorithm != Algorithm.none) {
checkSignature(tokenParts);
}
tokenCheckers.forEach(checker -> checker.check(payloadJson));
final Map<String, String> claims =
Stream.of(JWTConstants.CLAIM_NAMES)
.filter(type -> payloadJson.has(type))
.collect(Collectors.toMap(type -> type, type -> payloadJson.get(type).getAsString()));
return claims;
}
private String[] getTokenParts(String token)
{
if (token == null || "".equals(token)) {
throw new InvalidTokenException("No token provided.");
}
final String[] tokenParts = token.split("\\" + JWTConstants.DELIMITER, -1);
if (tokenParts.length != 3) {
throw new InvalidTokenException("Invalid token structure.");
}
return tokenParts;
}
private void checkSignature(String[] tokenParts)
{
try {
final Mac mac = Mac.getInstance(algorithm.name);
mac.init(new SecretKeySpec(secret, algorithm.name));
final byte[] headerAndPayload = new StringJoiner(".")
.add(tokenParts[0])
.add(tokenParts[1])
.toString().getBytes();
final byte[] signature = mac.doFinal(headerAndPayload);
if (!MessageDigest.isEqual(signature, Base64.getDecoder().decode(tokenParts[2]))) {
throw new InvalidSignatureException("Signature has been compromised");
}
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new AlgorithmException(e);
}
}
}