Java tutorial
/** * Copyright 2010 Google Inc. * * 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. * */ package net.oauth.jsontoken; import com.google.common.base.Preconditions; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import net.oauth.jsontoken.crypto.AsciiStringVerifier; import net.oauth.jsontoken.crypto.SignatureAlgorithm; import net.oauth.jsontoken.crypto.Verifier; import net.oauth.jsontoken.discovery.VerifierProviders; import org.apache.commons.codec.binary.Base64; import org.joda.time.Instant; import java.security.SignatureException; import java.util.List; import java.util.regex.Pattern; /** * Class that parses and verifies JSON Tokens. */ public class JsonTokenParser { private final Clock clock; private final VerifierProviders verifierProviders; private final Checker[] checkers; /** * Creates a new {@link JsonTokenParser} with a default system clock. The default * system clock tolerates a clock skew of up to {@link SystemClock#DEFAULT_ACCEPTABLE_CLOCK_SKEW}. * * @param verifierProviders an object that provides signature verifiers * based on a signature algorithm, the signer, and key ids. * @param checker an audience checker that validates the audience in the JSON token. */ public JsonTokenParser(VerifierProviders verifierProviders, Checker checker) { this(new SystemClock(), verifierProviders, checker); } /** * Creates a new {@link JsonTokenParser}. * * @param clock a clock object that will decide whether a given token is * currently valid or not. * @param verifierProviders an object that provides signature verifiers * based on a signature algorithm, the signer, and key ids. * @param checkers an array of checkers that validates the parameters in the JSON token. */ public JsonTokenParser(Clock clock, VerifierProviders verifierProviders, Checker... checkers) { this.clock = Preconditions.checkNotNull(clock); this.verifierProviders = verifierProviders; this.checkers = checkers; } /** * Decodes the JWT token string into a JsonToken object. Does not perform * any validation of headers or claims. * * @param tokenString The original encoded representation of a JWT * @return Unverified contents of the JWT as a JsonToken */ public JsonToken deserialize(String tokenString) { String[] pieces = splitTokenString(tokenString); String jwtHeaderSegment = pieces[0]; String jwtPayloadSegment = pieces[1]; byte[] signature = Base64.decodeBase64(pieces[2]); JsonParser parser = new JsonParser(); JsonObject header = parser.parse(JsonTokenUtil.fromBase64ToJsonString(jwtHeaderSegment)).getAsJsonObject(); JsonObject payload = parser.parse(JsonTokenUtil.fromBase64ToJsonString(jwtPayloadSegment)) .getAsJsonObject(); JsonToken jsonToken = new JsonToken(header, payload, clock, tokenString); return jsonToken; } /** * Verifies that the jsonToken has a valid signature and valid standard claims * (iat, exp). Uses VerifierProviders to obtain the secret key. * * @param jsonToken * @throws SignatureException */ public void verify(JsonToken jsonToken) throws SignatureException { List<Verifier> verifiers = provideVerifiers(jsonToken); verify(jsonToken, verifiers); } /** * Parses, and verifies, a JSON Token. * * @param tokenString the serialized token that is to parsed and verified. * @return the deserialized {@link JsonObject}, suitable for passing to the constructor * of {@link JsonToken} or equivalent constructor of {@link JsonToken} subclasses. * @throws SignatureException */ public JsonToken verifyAndDeserialize(String tokenString) throws SignatureException { JsonToken jsonToken = deserialize(tokenString); verify(jsonToken); return jsonToken; } /** * Verifies that the jsonToken has a valid signature and valid standard claims * (iat, exp). Does not need VerifierProviders because verifiers are passed in * directly. * * @param jsonToken the token to verify * @throws SignatureException when the signature is invalid * @throws IllegalStateException when exp or iat are invalid */ public void verify(JsonToken jsonToken, List<Verifier> verifiers) throws SignatureException { if (!signatureIsValid(jsonToken.getTokenString(), verifiers)) { throw new SignatureException("Invalid signature for token: " + jsonToken.getTokenString()); } Instant issuedAt = jsonToken.getIssuedAt(); Instant expiration = jsonToken.getExpiration(); if (issuedAt == null && expiration != null) { issuedAt = new Instant(0); } if (issuedAt != null && expiration == null) { expiration = new Instant(Long.MAX_VALUE); } if (issuedAt != null && expiration != null) { if (issuedAt.isAfter(expiration) || !clock.isCurrentTimeInInterval(issuedAt, expiration)) { throw new IllegalStateException( String.format("Invalid iat and/or exp. iat: %s exp: %s " + "now: %s", jsonToken.getIssuedAt(), jsonToken.getExpiration(), clock.now())); } } if (checkers != null) { for (Checker checker : checkers) { checker.check(jsonToken.getPayloadAsJsonObject()); } } } /** * Verifies that a JSON Web Token's signature is valid. * * @param tokenString the encoded and signed JSON Web Token to verify. * @param verifiers used to verify the signature. These usually encapsulate * secret keys. */ public boolean signatureIsValid(String tokenString, List<Verifier> verifiers) { String[] pieces = splitTokenString(tokenString); byte[] signature = Base64.decodeBase64(pieces[2]); String baseString = JsonTokenUtil.toDotFormat(pieces[0], pieces[1]); boolean sigVerified = false; for (Verifier verifier : verifiers) { AsciiStringVerifier asciiVerifier = new AsciiStringVerifier(verifier); try { asciiVerifier.verifySignature(baseString, signature); sigVerified = true; break; } catch (SignatureException e) { continue; } } return sigVerified; } /** * Verifies that a JSON Web Token is not expired. * * @param jsonToken the token to verify * @param now the instant to use as point of reference for current time * @return false if the token is expired, true otherwise */ public boolean expirationIsValid(JsonToken jsonToken, Instant now) { Instant expiration = jsonToken.getExpiration(); if ((expiration != null) && now.isAfter(expiration)) { return false; } return true; } /** * Verifies that a JSON Web Token was issued in the past. * * @param jsonToken the token to verify * @param now the instant to use as point of reference for current time * @return false if the JWT's 'iat' is later than now, true otherwise */ public boolean issuedAtIsValid(JsonToken jsonToken, Instant now) { Instant issuedAt = jsonToken.getIssuedAt(); if ((issuedAt != null) && now.isBefore(issuedAt)) { return false; } return true; } /** * Use VerifierProviders to get a list of verifiers for this token * * @param jsonToken * @return list of verifiers * @throws SignatureException */ private List<Verifier> provideVerifiers(JsonToken jsonToken) throws SignatureException { Preconditions.checkNotNull(verifierProviders); JsonObject header = jsonToken.getHeader(); JsonElement keyIdJson = header.get(JsonToken.KEY_ID_HEADER); String keyId = (keyIdJson == null) ? null : keyIdJson.getAsString(); SignatureAlgorithm sigAlg = jsonToken.getSignatureAlgorithm(); List<Verifier> verifiers = verifierProviders.getVerifierProvider(sigAlg).findVerifier(jsonToken.getIssuer(), keyId); if (verifiers == null) { throw new IllegalStateException("No valid verifier for issuer: " + jsonToken.getIssuer()); } return verifiers; } /** * @param tokenString The original encoded representation of a JWT * @return Three components of the JWT as an array of strings */ private String[] splitTokenString(String tokenString) { String[] pieces = tokenString.split(Pattern.quote(JsonTokenUtil.DELIMITER)); if (pieces.length != 3) { throw new IllegalStateException("Expected JWT to have 3 segments separated by '" + JsonTokenUtil.DELIMITER + "', but it has " + pieces.length + " segments"); } return pieces; } }