Initial implementation using a JwtHandler approach with additional convenience methods

This commit is contained in:
Les Hazlewood 2014-09-26 19:52:50 -07:00
parent 1be528b58b
commit f8aff1de0e
5 changed files with 392 additions and 6 deletions

View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* 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 io.jsonwebtoken;
/**
* A JwtHandler is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} after parsing a JWT to indicate the exact
* type of JWT or JWS parsed.
*
* @param <T> the type of object to return to the parser caller after handling the parsed JWT.
* @since 0.2
*/
public interface JwtHandler<T> {
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a plaintext JWT. A plaintext JWT has a String (non-JSON) body payload and it is not cryptographically signed.
*
* @param jwt the parsed plaintext JWT
* @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary.
*/
T onPlaintextJwt(Jwt<Header, String> jwt);
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a Claims JWT. A Claims JWT has a {@link Claims} body and it is not cryptographically signed.
*
* @param jwt the parsed claims JWT
* @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary.
*/
T onClaimsJwt(Jwt<Header, Claims> jwt);
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been
* cryptographically signed.
*
* <p>This method will only be invoked if the cryptographic signature can be successfully verified.</p>
*
* @param jws the parsed plaintext JWS
* @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary.
*/
T onPlaintextJws(Jws<String> jws);
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a valid Claims JWS. A Claims JWS is a JWT with a {@link Claims} body that has been cryptographically signed.
*
* <p>This method will only be invoked if the cryptographic signature can be successfully verified.</p>
*
* @param jws the parsed claims JWS
* @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary.
*/
T onClaimsJws(Jws<Claims> jws);
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* 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 io.jsonwebtoken;
/**
* An <a href="http://en.wikipedia.org/wiki/Adapter_pattern">Adapter</a> implementation of the
* {@link JwtHandler} interface that allows for anonymous subclasses to process only the JWT results that are
* known/expected for a particular use case.
*
* <p>All of the methods in this implementation throw exceptions: overridden methods represent
* scenarios expected by calling code in known situations. It would be unexpected to receive a JWS or JWT that did
* not match parsing expectations, so all non-overridden methods throw exceptions to indicate that the JWT
* input was unexpected.</p>
*
* @param <T> the type of object to return to the parser caller after handling the parsed JWT.
* @since 0.2
*/
public class JwtHandlerAdapter<T> implements JwtHandler<T> {
@Override
public T onPlaintextJwt(Jwt<Header, String> jwt) {
throw new UnsupportedJwtException("Unsigned plaintext JWTs are not supported.");
}
@Override
public T onClaimsJwt(Jwt<Header, Claims> jwt) {
throw new UnsupportedJwtException("Unsigned Claims JWTs are not supported.");
}
@Override
public T onPlaintextJws(Jws<String> jws) {
throw new UnsupportedJwtException("Signed plaintext JWSs are not supported.");
}
@Override
public T onClaimsJws(Jws<Claims> jws) {
throw new UnsupportedJwtException("Signed Claims JWSs are not supported.");
}
}

View File

@ -81,6 +81,9 @@ public interface JwtParser {
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} * Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
* otherwise. * otherwise.
* *
* <p>Note that if you are reasonably sure that the token is signed, it is usually more efficient to attempt to
* parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.</p>
*
* @param jwt the compact serialized JWT to check * @param jwt the compact serialized JWT to check
* @return {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} * @return {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
* otherwise. * otherwise.
@ -88,14 +91,188 @@ public interface JwtParser {
boolean isSigned(String jwt); boolean isSigned(String jwt);
/** /**
* Parses the specified compact serialized JWT string based on the builder's current configuration state. * Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns the resulting JWT or JWS instance.
*
* <p>This method returns a JWT or JWS based on the parsed string. Because it may be cumbersome to determine if it
* is a JWT or JWS, or if the body/payload is a Claims or String with {@code instanceof} checks, the
* {@link #parse(String, JwtHandler) parse(String,JwtHandler)} method allows for a type-safe callback approach that
* may help reduce code or instanceof checks.</p>
* *
* @param jwt the compact serialized JWT to parse * @param jwt the compact serialized JWT to parse
* @return the specified compact serialized JWT string based on the builder's current configuration state. * @return the specified compact serialized JWT string based on the builder's current configuration state.
* @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). Invalid * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid).
* Invalid
* JWTs should not be trusted and should be discarded. * JWTs should not be trusted and should be discarded.
* @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail
* signature validation should not be trusted and should be discarded. * signature validation should not be trusted and should be discarded.
* @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace.
* @see #parse(String, JwtHandler)
* @see #parsePlaintextJwt(String)
* @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String)
* @see #parseClaimsJws(String)
*/ */
Jwt parse(String jwt) throws MalformedJwtException, SignatureException; Jwt parse(String jwt) throws MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* invokes the specified {@code handler} with the resulting JWT or JWS instance.
*
* <p>If you are confident of the format of the JWT before parsing, you can create an anonymous subclass using the
* {@link io.jsonwebtoken.JwtHandlerAdapter JwtHandlerAdapter} and override only the methods you know are relevant
* for your use case(s), for example:</p>
*
* <pre>
* String compactJwt = request.getParameter("jwt"); //we are confident this is a signed JWS
*
* String subject = Jwts.parser().setSigningKey(key).parse(compactJwt, new JwtHandlerAdapter&lt;String&gt;() {
* &#64;Override
* public String onClaimsJws(Jws&lt;Claims&gt; jws) {
* return jws.getBody().getSubject();
* }
* });
* </pre>
*
* <p>If you know the JWT string can be only one type of JWT, then it is even easier to invoke one of the
* following convenience methods instead of this one:</p>
*
* <ul>
* <li>{@link #parsePlaintextJwt(String)}</li>
* <li>{@link #parseClaimsJwt(String)}</li>
* <li>{@link #parsePlaintextJws(String)}</li>
* <li>{@link #parseClaimsJws(String)}</li>
* </ul>
*
* @param jwt the compact serialized JWT to parse
* @return the result returned by the {@code JwtHandler}
* @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid).
* Invalid JWTs should not be trusted and should be discarded.
* @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail
* signature validation should not be trusted and should be discarded.
* @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the
* {@code handler} is {@code null}.
* @see #parsePlaintextJwt(String)
* @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String)
* @see #parseClaimsJws(String)
* @see #parse(String)
* @since 0.2
*/
<T> T parse(String jwt, JwtHandler<T> handler)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns
* the resulting unsigned plaintext JWT instance.
*
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects an
* unsigned plaintext JWT. An unsigned plaintext JWT has a String (non-JSON) body payload and it is not
* cryptographically signed.</p>
*
* <p><b>If the compact string presented does not reflect an unsigned plaintext JWT with non-JSON string body,
* an {@link UnsupportedJwtException} will be thrown.</b></p>
*
* @param plaintextJwt a compact serialized unsigned plaintext JWT string.
* @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string.
* @throws UnsupportedJwtException if the {@code plaintextJwt} argument does not represent an unsigned plaintext
* JWT
* @throws MalformedJwtException if the {@code plaintextJwt} string is not a valid JWT
* @throws SignatureException if the {@code plaintextJwt} string is actually a JWS and signature validation
* fails
* @throws IllegalArgumentException if the {@code plaintextJwt} string is {@code null} or empty or only whitespace
* @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String)
* @see #parseClaimsJws(String)
* @see #parse(String, JwtHandler)
* @see #parse(String)
* @since 0.2
*/
Jwt<Header, String> parsePlaintextJwt(String plaintextJwt)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns
* the resulting unsigned plaintext JWT instance.
*
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects an
* unsigned Claims JWT. An unsigned Claims JWT has a {@link Claims} body and it is not cryptographically
* signed.</p>
*
* <p><b>If the compact string presented does not reflect an unsigned Claims JWT, an
* {@link UnsupportedJwtException} will be thrown.</b></p>
*
* @param claimsJwt a compact serialized unsigned Claims JWT string.
* @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string.
* @throws UnsupportedJwtException if the {@code claimsJwt} argument does not represent an unsigned Claims JWT
* @throws MalformedJwtException if the {@code claimsJwt} string is not a valid JWT
* @throws SignatureException if the {@code claimsJwt} string is actually a JWS and signature validation
* fails
* @throws IllegalArgumentException if the {@code claimsJwt} string is {@code null} or empty or only whitespace
* @see #parsePlaintextJwt(String)
* @see #parsePlaintextJws(String)
* @see #parseClaimsJws(String)
* @see #parse(String, JwtHandler)
* @see #parse(String)
* @since 0.2
*/
Jwt<Header, Claims> parseClaimsJwt(String claimsJwt)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWS string based on the builder's current configuration state and
* returns
* the resulting plaintext JWS instance.
*
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a
* plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been
* cryptographically signed.</p>
*
* <p><b>If the compact string presented does not reflect a plaintext JWS, an {@link UnsupportedJwtException}
* will be thrown.</b></p>
*
* @param plaintextJws a compact serialized JWS string.
* @return the {@link Jws Jws} instance that reflects the specified compact JWS string.
* @throws UnsupportedJwtException if the {@code plaintextJws} argument does not represent an plaintext JWS
* @throws MalformedJwtException if the {@code plaintextJws} string is not a valid JWS
* @throws SignatureException if the {@code plaintextJws} JWS signature validation fails
* @throws IllegalArgumentException if the {@code plaintextJws} string is {@code null} or empty or only whitespace
* @see #parsePlaintextJwt(String)
* @see #parseClaimsJwt(String)
* @see #parseClaimsJws(String)
* @see #parse(String, JwtHandler)
* @see #parse(String)
* @since 0.2
*/
Jws<String> parsePlaintextJws(String plaintextJws)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWS string based on the builder's current configuration state and
* returns
* the resulting Claims JWS instance.
*
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a
* Claims JWS. A Claims JWS is a JWT with a {@link Claims} body that has been cryptographically signed.</p>
*
* <p><b>If the compact string presented does not reflect a Claims JWS, an {@link UnsupportedJwtException} will be
* thrown.</b></p>
*
* @param claimsJws a compact serialized Claims JWS string.
* @return the {@link Jws Jws} instance that reflects the specified compact Claims JWS string.
* @throws UnsupportedJwtException if the {@code claimsJws} argument does not represent an Claims JWS
* @throws MalformedJwtException if the {@code claimsJws} string is not a valid JWS
* @throws SignatureException if the {@code claimsJws} JWS signature validation fails
* @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace
* @see #parsePlaintextJwt(String)
* @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String)
* @see #parse(String, JwtHandler)
* @see #parse(String)
* @since 0.2
*/
Jws<Claims> parseClaimsJws(String claimsJws)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
} }

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* 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 io.jsonwebtoken;
/**
* Exception thrown when receiving a JWT in a particular format/configuration that does not match the format expected
* by the application.
*
* <p>For example, this exception would be thrown if parsing an unsigned plaintext JWT when the application
* requires a cryptographically signed Claims JWS instead.</p>
*
* @since 0.2
*/
public class UnsupportedJwtException extends JwtException {
public UnsupportedJwtException(String message) {
super(message);
}
public UnsupportedJwtException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -18,8 +18,11 @@ package io.jsonwebtoken.impl;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header; import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwt; import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtHandler;
import io.jsonwebtoken.JwtHandlerAdapter;
import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
@ -218,6 +221,56 @@ public class DefaultJwtParser implements JwtParser {
} }
} }
@Override
public <T> T parse(String compact, JwtHandler<T> handler) throws MalformedJwtException, SignatureException {
Assert.notNull(handler, "JwtHandler argument cannot be null.");
Assert.hasText(compact, "JWT String argument cannot be null or empty.");
Jwt jwt = parse(compact);
if (jwt instanceof Jws) {
Jws jws = (Jws)jwt;
Object body = jws.getBody();
if (body instanceof Claims) {
return handler.onClaimsJws(jws);
} else {
return handler.onPlaintextJws(jws);
}
} else {
Object body = jwt.getBody();
if (body instanceof Claims) {
return handler.onPlaintextJwt(jwt);
} else {
return handler.onClaimsJwt(jwt);
}
}
}
@Override
public Jwt<Header, String> parsePlaintextJwt(String plaintextJwt) {
return parse(plaintextJwt, new JwtHandlerAdapter<Jwt<Header, String>>() {
@Override
public Jwt<Header, String> onPlaintextJwt(Jwt<Header, String> jwt) {
return jwt;
}
});
}
@Override
public Jwt<Header, Claims> parseClaimsJwt(String claimsJwt) {
return null;
}
@Override
public Jws<String> parsePlaintextJws(String plaintextJws) {
return null;
}
@Override
public Jws<Claims> parseClaimsJws(String claimsJws) {
return null;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected Map<String, Object> readValue(String val) { protected Map<String, Object> readValue(String val) {
try { try {