mirror of https://github.com/jwtk/jjwt.git
Expanded Parser method argument support (#844)
Closes #328 - Ensured Parser worked with CharSequence, InputStream and Reader, not just String - Changed Deserializer#deserialize(InputStream) to deserialize(Reader) - JwtParser now extends from Parser to support these additional methods. - Changed remaining JwtParser.parse* methods to accept CharSequence arguments instead of String args.
This commit is contained in:
parent
36a6e1383b
commit
05717d0a18
|
@ -55,6 +55,7 @@ The Apache Software Foundation (https://www.apache.org/).
|
|||
```
|
||||
|
||||
Also, the following classes were copied from the Apache Commons-IO project, with further JJWT-specific modifications:
|
||||
* io.jsonwebtoken.impl.io.CharSequenceReader
|
||||
* io.jsonwebtoken.impl.io.FilteredInputStream
|
||||
* io.jsonwebtoken.impl.io.FilteredOutputStream
|
||||
* io.jsonwebtoken.impl.io.ClosedInputStream
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package io.jsonwebtoken;
|
||||
|
||||
import io.jsonwebtoken.io.Parser;
|
||||
import io.jsonwebtoken.security.SecurityException;
|
||||
import io.jsonwebtoken.security.SignatureException;
|
||||
|
||||
|
@ -25,7 +26,7 @@ import java.io.InputStream;
|
|||
*
|
||||
* @since 0.1
|
||||
*/
|
||||
public interface JwtParser {
|
||||
public interface JwtParser extends Parser<Jwt<?, ?>> {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
|
||||
|
@ -38,7 +39,7 @@ public interface JwtParser {
|
|||
* @return {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
|
||||
* otherwise.
|
||||
*/
|
||||
boolean isSigned(String compact);
|
||||
boolean isSigned(CharSequence compact);
|
||||
|
||||
/**
|
||||
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
|
||||
|
@ -46,7 +47,7 @@ public interface JwtParser {
|
|||
*
|
||||
* <p>This method returns a JWT, JWS, or JWE based on the parsed string. Because it may be cumbersome to
|
||||
* determine if it is a JWT, JWS or JWE, or if the payload is a Claims or byte array with {@code instanceof} checks,
|
||||
* the {@link #parse(String, JwtHandler) parse(String,JwtHandler)} method allows for a type-safe callback approach
|
||||
* the {@link #parse(CharSequence, 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
|
||||
|
@ -59,15 +60,15 @@ public interface JwtParser {
|
|||
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
|
||||
* before the time this method is invoked.
|
||||
* @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace.
|
||||
* @see #parse(String, JwtHandler)
|
||||
* @see #parseContentJwt(String)
|
||||
* @see #parseClaimsJwt(String)
|
||||
* @see #parseContentJws(String)
|
||||
* @see #parseClaimsJws(String)
|
||||
* @see #parseContentJwe(String)
|
||||
* @see #parseClaimsJwe(String)
|
||||
* @see #parse(CharSequence, JwtHandler)
|
||||
* @see #parseContentJwt(CharSequence)
|
||||
* @see #parseClaimsJwt(CharSequence)
|
||||
* @see #parseContentJws(CharSequence)
|
||||
* @see #parseClaimsJws(CharSequence)
|
||||
* @see #parseContentJwe(CharSequence)
|
||||
* @see #parseClaimsJwe(CharSequence)
|
||||
*/
|
||||
Jwt<?, ?> parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException,
|
||||
Jwt<?, ?> parse(CharSequence jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException,
|
||||
SecurityException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
|
@ -93,12 +94,12 @@ public interface JwtParser {
|
|||
* following convenience methods instead of this one:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #parseContentJwt(String)}</li>
|
||||
* <li>{@link #parseClaimsJwt(String)}</li>
|
||||
* <li>{@link #parseContentJws(String)}</li>
|
||||
* <li>{@link #parseClaimsJws(String)}</li>
|
||||
* <li>{@link #parseContentJwe(String)}</li>
|
||||
* <li>{@link #parseClaimsJwe(String)}</li>
|
||||
* <li>{@link #parseContentJwt(CharSequence)}</li>
|
||||
* <li>{@link #parseClaimsJwt(CharSequence)}</li>
|
||||
* <li>{@link #parseContentJws(CharSequence)}</li>
|
||||
* <li>{@link #parseClaimsJws(CharSequence)}</li>
|
||||
* <li>{@link #parseContentJwe(CharSequence)}</li>
|
||||
* <li>{@link #parseClaimsJwe(CharSequence)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param jwt the compact serialized JWT to parse
|
||||
|
@ -114,16 +115,16 @@ public interface JwtParser {
|
|||
* before the time this method is invoked.
|
||||
* @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the
|
||||
* {@code handler} is {@code null}.
|
||||
* @see #parseContentJwt(String)
|
||||
* @see #parseClaimsJwt(String)
|
||||
* @see #parseContentJws(String)
|
||||
* @see #parseClaimsJws(String)
|
||||
* @see #parseContentJwe(String)
|
||||
* @see #parseClaimsJwe(String)
|
||||
* @see #parse(String)
|
||||
* @see #parseContentJwt(CharSequence)
|
||||
* @see #parseClaimsJwt(CharSequence)
|
||||
* @see #parseContentJws(CharSequence)
|
||||
* @see #parseClaimsJws(CharSequence)
|
||||
* @see #parseContentJwe(CharSequence)
|
||||
* @see #parseClaimsJwe(CharSequence)
|
||||
* @see #parse(CharSequence)
|
||||
* @since 0.2
|
||||
*/
|
||||
<T> T parse(String jwt, JwtHandler<T> handler) throws ExpiredJwtException, UnsupportedJwtException,
|
||||
<T> T parse(CharSequence jwt, JwtHandler<T> handler) throws ExpiredJwtException, UnsupportedJwtException,
|
||||
MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
|
@ -148,14 +149,14 @@ public interface JwtParser {
|
|||
* @throws SignatureException if the {@code jwt} string is actually a JWS and signature validation fails
|
||||
* @throws SecurityException if the {@code jwt} string is actually a JWE and decryption fails
|
||||
* @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace
|
||||
* @see #parseClaimsJwt(String)
|
||||
* @see #parseContentJws(String)
|
||||
* @see #parseClaimsJws(String)
|
||||
* @see #parse(String, JwtHandler)
|
||||
* @see #parse(String)
|
||||
* @see #parseClaimsJwt(CharSequence)
|
||||
* @see #parseContentJws(CharSequence)
|
||||
* @see #parseClaimsJws(CharSequence)
|
||||
* @see #parse(CharSequence, JwtHandler)
|
||||
* @see #parse(CharSequence)
|
||||
* @since 0.2
|
||||
*/
|
||||
Jwt<Header, byte[]> parseContentJwt(String jwt) throws UnsupportedJwtException, MalformedJwtException,
|
||||
Jwt<Header, byte[]> parseContentJwt(CharSequence jwt) throws UnsupportedJwtException, MalformedJwtException,
|
||||
SignatureException, SecurityException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
|
@ -178,14 +179,14 @@ public interface JwtParser {
|
|||
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
|
||||
* before the time this method is invoked.
|
||||
* @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace
|
||||
* @see #parseContentJwt(String)
|
||||
* @see #parseContentJws(String)
|
||||
* @see #parseClaimsJws(String)
|
||||
* @see #parse(String, JwtHandler)
|
||||
* @see #parse(String)
|
||||
* @see #parseContentJwt(CharSequence)
|
||||
* @see #parseContentJws(CharSequence)
|
||||
* @see #parseClaimsJws(CharSequence)
|
||||
* @see #parse(CharSequence, JwtHandler)
|
||||
* @see #parse(CharSequence)
|
||||
* @since 0.2
|
||||
*/
|
||||
Jwt<Header, Claims> parseClaimsJwt(String jwt) throws ExpiredJwtException, UnsupportedJwtException,
|
||||
Jwt<Header, Claims> parseClaimsJwt(CharSequence jwt) throws ExpiredJwtException, UnsupportedJwtException,
|
||||
MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
|
@ -207,16 +208,16 @@ public interface JwtParser {
|
|||
* @throws SignatureException if the {@code jws} JWS signature validation fails
|
||||
* @throws SecurityException if the {@code jws} string is actually a JWE and decryption fails
|
||||
* @throws IllegalArgumentException if the {@code jws} string is {@code null} or empty or only whitespace
|
||||
* @see #parseContentJwt(String)
|
||||
* @see #parseContentJwe(String)
|
||||
* @see #parseClaimsJwt(String)
|
||||
* @see #parseClaimsJws(String)
|
||||
* @see #parseClaimsJwe(String)
|
||||
* @see #parse(String, JwtHandler)
|
||||
* @see #parse(String)
|
||||
* @see #parseContentJwt(CharSequence)
|
||||
* @see #parseContentJwe(CharSequence)
|
||||
* @see #parseClaimsJwt(CharSequence)
|
||||
* @see #parseClaimsJws(CharSequence)
|
||||
* @see #parseClaimsJwe(CharSequence)
|
||||
* @see #parse(CharSequence, JwtHandler)
|
||||
* @see #parse(CharSequence)
|
||||
* @since 0.2
|
||||
*/
|
||||
Jws<byte[]> parseContentJws(String jws) throws UnsupportedJwtException, MalformedJwtException, SignatureException,
|
||||
Jws<byte[]> parseContentJws(CharSequence jws) throws UnsupportedJwtException, MalformedJwtException, SignatureException,
|
||||
SecurityException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
|
@ -234,7 +235,7 @@ public interface JwtParser {
|
|||
* @param unencodedPayload the JWS's associated required unencoded payload used for signature verification.
|
||||
* @return the parsed Unencoded Payload.
|
||||
*/
|
||||
Jws<byte[]> parseContentJws(String jws, byte[] unencodedPayload);
|
||||
Jws<byte[]> parseContentJws(CharSequence jws, byte[] unencodedPayload);
|
||||
|
||||
/**
|
||||
* Parses a JWS known to use the
|
||||
|
@ -251,7 +252,7 @@ public interface JwtParser {
|
|||
* @param unencodedPayload the JWS's associated required unencoded payload used for signature verification.
|
||||
* @return the parsed Unencoded Payload.
|
||||
*/
|
||||
Jws<Claims> parseClaimsJws(String jws, byte[] unencodedPayload);
|
||||
Jws<Claims> parseClaimsJws(CharSequence jws, byte[] unencodedPayload);
|
||||
|
||||
/**
|
||||
* Parses a JWS known to use the
|
||||
|
@ -274,7 +275,7 @@ public interface JwtParser {
|
|||
* @param unencodedPayload the JWS's associated required unencoded payload used for signature verification.
|
||||
* @return the parsed Unencoded Payload.
|
||||
*/
|
||||
Jws<byte[]> parseContentJws(String jws, InputStream unencodedPayload);
|
||||
Jws<byte[]> parseContentJws(CharSequence jws, InputStream unencodedPayload);
|
||||
|
||||
/**
|
||||
* Parses a JWS known to use the
|
||||
|
@ -285,7 +286,7 @@ public interface JwtParser {
|
|||
* <p><b>NOTE:</b> however, because calling this method indicates a completed
|
||||
* {@link Claims} instance is desired, the specified {@code unencodedPayload} JSON stream will be fully
|
||||
* read into a Claims instance. If this will be problematic for your application (perhaps if you expect extremely
|
||||
* large Claims), it is recommended to use the {@link #parseContentJws(String, InputStream)} method instead.</p>
|
||||
* large Claims), it is recommended to use the {@link #parseContentJws(CharSequence, InputStream)} method instead.</p>
|
||||
*
|
||||
* <p><b>Unencoded Non-Detached Payload</b></p>
|
||||
* <p>Note that if the JWS contains a valid unencoded Payload string (what RFC 7797 calls an
|
||||
|
@ -297,7 +298,7 @@ public interface JwtParser {
|
|||
* @param unencodedPayload the JWS's associated required unencoded payload used for signature verification.
|
||||
* @return the parsed Unencoded Payload.
|
||||
*/
|
||||
Jws<Claims> parseClaimsJws(String jws, InputStream unencodedPayload);
|
||||
Jws<Claims> parseClaimsJws(CharSequence jws, InputStream unencodedPayload);
|
||||
|
||||
/**
|
||||
* Parses the specified compact serialized JWS string based on the builder's current configuration state and
|
||||
|
@ -318,16 +319,16 @@ public interface JwtParser {
|
|||
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
|
||||
* before the time this method is invoked.
|
||||
* @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace
|
||||
* @see #parseContentJwt(String)
|
||||
* @see #parseContentJws(String)
|
||||
* @see #parseContentJwe(String)
|
||||
* @see #parseClaimsJwt(String)
|
||||
* @see #parseClaimsJwe(String)
|
||||
* @see #parse(String, JwtHandler)
|
||||
* @see #parse(String)
|
||||
* @see #parseContentJwt(CharSequence)
|
||||
* @see #parseContentJws(CharSequence)
|
||||
* @see #parseContentJwe(CharSequence)
|
||||
* @see #parseClaimsJwt(CharSequence)
|
||||
* @see #parseClaimsJwe(CharSequence)
|
||||
* @see #parse(CharSequence, JwtHandler)
|
||||
* @see #parse(CharSequence)
|
||||
* @since 0.2
|
||||
*/
|
||||
Jws<Claims> parseClaimsJws(String jws) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException,
|
||||
Jws<Claims> parseClaimsJws(CharSequence jws) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException,
|
||||
SignatureException, SecurityException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
|
@ -348,16 +349,16 @@ public interface JwtParser {
|
|||
* @throws MalformedJwtException if the {@code jwe} string is not a valid JWE
|
||||
* @throws SecurityException if the {@code jwe} JWE decryption fails
|
||||
* @throws IllegalArgumentException if the {@code jwe} string is {@code null} or empty or only whitespace
|
||||
* @see #parseContentJwt(String)
|
||||
* @see #parseContentJws(String)
|
||||
* @see #parseClaimsJwt(String)
|
||||
* @see #parseClaimsJws(String)
|
||||
* @see #parseClaimsJwe(String)
|
||||
* @see #parse(String, JwtHandler)
|
||||
* @see #parse(String)
|
||||
* @see #parseContentJwt(CharSequence)
|
||||
* @see #parseContentJws(CharSequence)
|
||||
* @see #parseClaimsJwt(CharSequence)
|
||||
* @see #parseClaimsJws(CharSequence)
|
||||
* @see #parseClaimsJwe(CharSequence)
|
||||
* @see #parse(CharSequence, JwtHandler)
|
||||
* @see #parse(CharSequence)
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
Jwe<byte[]> parseContentJwe(String jwe) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException,
|
||||
Jwe<byte[]> parseContentJwe(CharSequence jwe) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException,
|
||||
SecurityException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
|
@ -378,15 +379,15 @@ public interface JwtParser {
|
|||
* @throws ExpiredJwtException if the specified JWT is a Claims JWE and the Claims has an expiration time
|
||||
* before the time this method is invoked.
|
||||
* @throws IllegalArgumentException if the {@code claimsJwe} string is {@code null} or empty or only whitespace
|
||||
* @see #parseContentJwt(String)
|
||||
* @see #parseContentJws(String)
|
||||
* @see #parseContentJwe(String)
|
||||
* @see #parseClaimsJwt(String)
|
||||
* @see #parseClaimsJws(String)
|
||||
* @see #parse(String, JwtHandler)
|
||||
* @see #parse(String)
|
||||
* @see #parseContentJwt(CharSequence)
|
||||
* @see #parseContentJws(CharSequence)
|
||||
* @see #parseContentJwe(CharSequence)
|
||||
* @see #parseClaimsJwt(CharSequence)
|
||||
* @see #parseClaimsJws(CharSequence)
|
||||
* @see #parse(CharSequence, JwtHandler)
|
||||
* @see #parse(CharSequence)
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
Jwe<Claims> parseClaimsJwe(String jwe) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException,
|
||||
Jwe<Claims> parseClaimsJwe(CharSequence jwe) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException,
|
||||
SecurityException, IllegalArgumentException;
|
||||
}
|
||||
|
|
|
@ -19,10 +19,13 @@ import io.jsonwebtoken.lang.Assert;
|
|||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Convenient base class to use to implement {@link Deserializer}s, with subclasses only needing to implement
|
||||
* {@link #doDeserialize(InputStream)}.
|
||||
* {@link #doDeserialize(Reader)}.
|
||||
*
|
||||
* @param <T> the type of object returned after deserialization
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
|
@ -48,17 +51,19 @@ public abstract class AbstractDeserializer<T> implements Deserializer<T> {
|
|||
@Override
|
||||
public final T deserialize(byte[] bytes) throws DeserializationException {
|
||||
bytes = bytes == null ? EMPTY_BYTES : bytes; // null safe
|
||||
return deserialize(new ByteArrayInputStream(bytes));
|
||||
InputStream in = new ByteArrayInputStream(bytes);
|
||||
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
|
||||
return deserialize(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public final T deserialize(InputStream in) throws DeserializationException {
|
||||
Assert.notNull(in, "InputStream argument cannot be null.");
|
||||
public final T deserialize(Reader reader) throws DeserializationException {
|
||||
Assert.notNull(reader, "Reader argument cannot be null.");
|
||||
try {
|
||||
return doDeserialize(in);
|
||||
return doDeserialize(reader);
|
||||
} catch (Throwable t) {
|
||||
if (t instanceof DeserializationException) {
|
||||
throw (DeserializationException) t;
|
||||
|
@ -69,11 +74,11 @@ public abstract class AbstractDeserializer<T> implements Deserializer<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Reads the specified {@code InputStream} and returns the corresponding Java object.
|
||||
* Reads the specified character stream and returns the corresponding Java object.
|
||||
*
|
||||
* @param in the input stream to read
|
||||
* @param reader the reader to use to read the character stream
|
||||
* @return the deserialized Java object
|
||||
* @throws Exception if there is a problem reading the stream or creating the expected Java object
|
||||
*/
|
||||
protected abstract T doDeserialize(InputStream in) throws Exception;
|
||||
protected abstract T doDeserialize(Reader reader) throws Exception;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package io.jsonwebtoken.io;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* A {@code Deserializer} is able to convert serialized byte streams into Java objects.
|
||||
|
@ -31,18 +31,18 @@ public interface Deserializer<T> {
|
|||
* @param bytes the formatted data byte array to convert
|
||||
* @return the reconstituted Java object
|
||||
* @throws DeserializationException if there is a problem converting the byte array to an object.
|
||||
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #deserialize(InputStream)}
|
||||
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #deserialize(Reader)}
|
||||
*/
|
||||
@Deprecated
|
||||
T deserialize(byte[] bytes) throws DeserializationException;
|
||||
|
||||
/**
|
||||
* Reads the specified {@code InputStream} and returns the corresponding Java object.
|
||||
* Reads the specified character stream and returns the corresponding Java object.
|
||||
*
|
||||
* @param in the input stream to read
|
||||
* @param reader the reader to use to read the character stream
|
||||
* @return the deserialized Java object
|
||||
* @throws DeserializationException if there is a problem reading the stream or creating the expected Java object
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
T deserialize(InputStream in) throws DeserializationException;
|
||||
T deserialize(Reader reader) throws DeserializationException;
|
||||
}
|
||||
|
|
|
@ -15,32 +15,53 @@
|
|||
*/
|
||||
package io.jsonwebtoken.io;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* A Parser converts character input into a Java object.
|
||||
* A Parser converts a character stream into a Java object.
|
||||
*
|
||||
* <p>Semantically, this interface might have been more accurately named
|
||||
* <a href="https://en.wikipedia.org/wiki/Marshalling_(computer_science)">Unmarshaller</a> because it technically
|
||||
* converts a content stream into a Java object. However, the {@code Parser} name was chosen for consistency with the
|
||||
* {@link io.jsonwebtoken.JwtParser JwtParser} concept (which is a 'real' parser that scans text for tokens). This
|
||||
* helps avoid confusion when trying to find similar concepts in the JJWT API by using the same taxonomy, for
|
||||
* example:</p>
|
||||
* <ul>
|
||||
* <li>{@link io.jsonwebtoken.Jwts#parser() Jwts.parser()}</li>
|
||||
* <li>{@link io.jsonwebtoken.security.Jwks#parser() Jwks.parser()}</li>
|
||||
* <li>{@link io.jsonwebtoken.security.Jwks#setParser() Jwks.setParser()}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <T> the instance type created after parsing/unmarshalling
|
||||
* @param <T> the instance type created after parsing
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface Parser<T> {
|
||||
|
||||
/**
|
||||
* Parse the specified input into a Java object.
|
||||
* Parse the specified character sequence into a Java object.
|
||||
*
|
||||
* @param input the string to parse into a Java object.
|
||||
* @param input the character sequence to parse into a Java object.
|
||||
* @return the Java object represented by the specified {@code input} stream.
|
||||
*/
|
||||
T parse(String input);
|
||||
T parse(CharSequence input);
|
||||
|
||||
/**
|
||||
* @param input The character sequence, may be {@code null}
|
||||
* @param start The start index in the character sequence, inclusive
|
||||
* @param end The end index in the character sequence, exclusive
|
||||
* @return the Java object represented by the specified sequence bounds
|
||||
* @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index
|
||||
*/
|
||||
T parse(CharSequence input, int start, int end);
|
||||
|
||||
/**
|
||||
* Parse the specified character sequence into a Java object.
|
||||
*
|
||||
* @param reader the reader to use to parse a Java object.
|
||||
* @return the Java object represented by the specified {@code input} stream.
|
||||
*/
|
||||
T parse(Reader reader);
|
||||
|
||||
/**
|
||||
* Parses the specified {@link InputStream} assuming {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} encoding.
|
||||
* This is a convenience alias for:
|
||||
*
|
||||
* <blockquote><pre>{@link #parse(Reader) parse}(new {@link java.io.InputStreamReader
|
||||
* InputStreamReader}(in, {@link java.nio.charset.StandardCharsets#UTF_8
|
||||
* StandardCharsets.UTF_8});</pre></blockquote>
|
||||
*
|
||||
*
|
||||
* @param in the UTF-8 InputStream.
|
||||
* @return the Java object represented by the specified {@link InputStream}.
|
||||
*/
|
||||
T parse(InputStream in);
|
||||
}
|
||||
|
|
|
@ -26,8 +26,8 @@ class AbstractDeserializerTest {
|
|||
boolean invoked = false
|
||||
def deser = new AbstractDeserializer() {
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream inputStream) throws Exception {
|
||||
assertEquals EOF, inputStream.read()
|
||||
protected Object doDeserialize(Reader reader) throws Exception {
|
||||
assertEquals EOF, reader.read()
|
||||
invoked = true
|
||||
}
|
||||
}
|
||||
|
@ -40,8 +40,8 @@ class AbstractDeserializerTest {
|
|||
boolean invoked = false
|
||||
def deser = new AbstractDeserializer() {
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream inputStream) throws Exception {
|
||||
assertEquals EOF, inputStream.read()
|
||||
protected Object doDeserialize(Reader reader) throws Exception {
|
||||
assertEquals EOF, reader.read()
|
||||
invoked = true
|
||||
}
|
||||
}
|
||||
|
@ -56,8 +56,8 @@ class AbstractDeserializerTest {
|
|||
bytes[0] = b
|
||||
def des = new AbstractDeserializer() {
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream inputStream) throws Exception {
|
||||
assertEquals b, inputStream.read()
|
||||
protected Object doDeserialize(Reader reader) throws Exception {
|
||||
assertEquals b, reader.read()
|
||||
return 42
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ class AbstractDeserializerTest {
|
|||
def ex = new RuntimeException('foo')
|
||||
def des = new AbstractDeserializer() {
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream inputStream) throws Exception {
|
||||
protected Object doDeserialize(Reader reader) throws Exception {
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,7 @@ import com.google.gson.Gson;
|
|||
import io.jsonwebtoken.io.AbstractDeserializer;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class GsonDeserializer<T> extends AbstractDeserializer<T> {
|
||||
|
||||
|
@ -46,8 +43,7 @@ public class GsonDeserializer<T> extends AbstractDeserializer<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected T doDeserialize(InputStream in) throws Exception {
|
||||
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
|
||||
protected T doDeserialize(Reader reader) {
|
||||
return gson.fromJson(reader, returnType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,9 @@ class GsonDeserializerTest {
|
|||
private GsonDeserializer deserializer
|
||||
|
||||
private def deser(byte[] data) {
|
||||
deserializer.deserialize(new ByteArrayInputStream(data))
|
||||
def ins = new ByteArrayInputStream(data)
|
||||
def reader = new InputStreamReader(ins, Strings.UTF_8)
|
||||
deserializer.deserialize(reader)
|
||||
}
|
||||
|
||||
private def deser(String s) {
|
||||
|
@ -76,7 +78,7 @@ class GsonDeserializerTest {
|
|||
def ex = new IOException('foo')
|
||||
deserializer = new GsonDeserializer() {
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream inputStream) throws Exception {
|
||||
protected Object doDeserialize(Reader reader) throws Exception {
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import io.jsonwebtoken.io.AbstractDeserializer;
|
|||
import io.jsonwebtoken.lang.Assert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -100,8 +100,8 @@ public class JacksonDeserializer<T> extends AbstractDeserializer<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected T doDeserialize(InputStream in) throws Exception {
|
||||
return objectMapper.readValue(in, returnType);
|
||||
protected T doDeserialize(Reader reader) throws Exception {
|
||||
return objectMapper.readValue(reader, returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,7 +22,6 @@ import io.jsonwebtoken.io.Deserializer
|
|||
import io.jsonwebtoken.io.Encoders
|
||||
import io.jsonwebtoken.jackson.io.stubs.CustomBean
|
||||
import io.jsonwebtoken.lang.Maps
|
||||
import io.jsonwebtoken.lang.Strings
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
|
@ -62,9 +61,9 @@ class JacksonDeserializerTest {
|
|||
|
||||
@Test
|
||||
void testDeserialize() {
|
||||
byte[] data = Strings.utf8('{"hello":"世界"}')
|
||||
def reader = new StringReader('{"hello":"世界"}')
|
||||
def expected = [hello: '世界']
|
||||
def result = deserializer.deserialize(new ByteArrayInputStream(data))
|
||||
def result = deserializer.deserialize(reader)
|
||||
assertEquals expected, result
|
||||
}
|
||||
|
||||
|
@ -97,8 +96,6 @@ class JacksonDeserializerTest {
|
|||
}
|
||||
"""
|
||||
|
||||
byte[] serialized = Strings.utf8(json)
|
||||
|
||||
CustomBean expectedCustomBean = new CustomBean()
|
||||
.setByteArrayValue("bytes".getBytes("UTF-8"))
|
||||
.setByteValue(0xF as byte)
|
||||
|
@ -119,7 +116,7 @@ class JacksonDeserializerTest {
|
|||
|
||||
def expected = [oneKey: "oneValue", custom: expectedCustomBean]
|
||||
def result = new JacksonDeserializer(Maps.of("custom", CustomBean).build())
|
||||
.deserialize(new ByteArrayInputStream(serialized))
|
||||
.deserialize(new StringReader(json))
|
||||
assertEquals expected, result
|
||||
}
|
||||
|
||||
|
@ -154,8 +151,8 @@ class JacksonDeserializerTest {
|
|||
typeMap.put("custom", CustomBean)
|
||||
|
||||
def deserializer = new JacksonDeserializer(typeMap)
|
||||
def ins = new ByteArrayInputStream(Strings.utf8('{"alg":"HS256"}'))
|
||||
def result = deserializer.deserialize(ins)
|
||||
def reader = new StringReader('{"alg":"HS256"}')
|
||||
def result = deserializer.deserialize(reader)
|
||||
assertEquals(["alg": "HS256"], result)
|
||||
}
|
||||
|
||||
|
@ -171,12 +168,12 @@ class JacksonDeserializerTest {
|
|||
|
||||
deserializer = new JacksonDeserializer() {
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream inputStream) throws Exception {
|
||||
protected Object doDeserialize(Reader reader) throws Exception {
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
try {
|
||||
deserializer.deserialize(new ByteArrayInputStream(Strings.utf8('{"hello":"世界"}')))
|
||||
deserializer.deserialize(new StringReader('{"hello":"世界"}'))
|
||||
fail()
|
||||
} catch (DeserializationException se) {
|
||||
String msg = 'Unable to deserialize: foo'
|
||||
|
|
|
@ -21,10 +21,7 @@ import org.json.JSONException;
|
|||
import org.json.JSONObject;
|
||||
import org.json.JSONTokener;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
|
@ -37,8 +34,7 @@ import java.util.Map;
|
|||
public class OrgJsonDeserializer extends AbstractDeserializer<Object> {
|
||||
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream in) {
|
||||
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
|
||||
protected Object doDeserialize(Reader reader) {
|
||||
return parse(reader);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,9 @@ class OrgJsonDeserializerTest {
|
|||
private OrgJsonDeserializer des
|
||||
|
||||
private Object fromBytes(byte[] data) {
|
||||
return des.deserialize(new ByteArrayInputStream(data))
|
||||
def ins = new ByteArrayInputStream(data)
|
||||
def reader = new InputStreamReader(ins, Strings.UTF_8)
|
||||
return des.deserialize(reader)
|
||||
}
|
||||
|
||||
private Object read(String s) {
|
||||
|
@ -38,7 +40,7 @@ class OrgJsonDeserializerTest {
|
|||
|
||||
@Test(expected = IllegalArgumentException)
|
||||
void testNullArgument() {
|
||||
des.deserialize((InputStream) null)
|
||||
des.deserialize((Reader) null)
|
||||
}
|
||||
|
||||
@Test(expected = DeserializationException)
|
||||
|
@ -157,7 +159,7 @@ class OrgJsonDeserializerTest {
|
|||
|
||||
@Test(expected = IllegalArgumentException)
|
||||
void deserializeNull() {
|
||||
des.deserialize((InputStream) null)
|
||||
des.deserialize((Reader) null)
|
||||
}
|
||||
|
||||
@Test(expected = DeserializationException)
|
||||
|
@ -172,7 +174,7 @@ class OrgJsonDeserializerTest {
|
|||
|
||||
des = new OrgJsonDeserializer() {
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream inputStream) {
|
||||
protected Object doDeserialize(Reader reader) {
|
||||
throw t
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ import io.jsonwebtoken.PrematureJwtException;
|
|||
import io.jsonwebtoken.ProtectedHeader;
|
||||
import io.jsonwebtoken.SigningKeyResolver;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import io.jsonwebtoken.impl.io.AbstractParser;
|
||||
import io.jsonwebtoken.impl.io.CharSequenceReader;
|
||||
import io.jsonwebtoken.impl.io.JsonObjectDeserializer;
|
||||
import io.jsonwebtoken.impl.io.Streams;
|
||||
import io.jsonwebtoken.impl.io.UncloseableInputStream;
|
||||
|
@ -76,6 +78,7 @@ import java.io.BufferedInputStream;
|
|||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
|
@ -91,7 +94,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class DefaultJwtParser implements JwtParser {
|
||||
public class DefaultJwtParser extends AbstractParser<Jwt<?, ?>> implements JwtParser {
|
||||
|
||||
static final char SEPARATOR_CHAR = '.';
|
||||
|
||||
|
@ -248,12 +251,12 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isSigned(String compact) {
|
||||
if (compact == null) {
|
||||
public boolean isSigned(CharSequence compact) {
|
||||
if (!Strings.hasText(compact)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
final TokenizedJwt tokenized = jwtTokenizer.tokenize(compact);
|
||||
final TokenizedJwt tokenized = jwtTokenizer.tokenize(new CharSequenceReader(compact));
|
||||
return !(tokenized instanceof TokenizedJwe) && Strings.hasText(tokenized.getDigest());
|
||||
} catch (MalformedJwtException e) {
|
||||
return false;
|
||||
|
@ -356,15 +359,15 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Jwt<?, ?> parse(String compact) {
|
||||
CharBuffer buffer = Strings.wrap(compact); // so compact.subsequence calls don't add new Strings on the heap
|
||||
return parse(buffer, Payload.EMPTY);
|
||||
public Jwt<?, ?> parse(Reader reader) {
|
||||
Assert.notNull(reader, "Reader cannot be null.");
|
||||
return parse(reader, Payload.EMPTY);
|
||||
}
|
||||
|
||||
private Jwt<?, ?> parse(CharSequence compact, Payload unencodedPayload)
|
||||
private Jwt<?, ?> parse(Reader compact, Payload unencodedPayload)
|
||||
throws ExpiredJwtException, MalformedJwtException, SignatureException {
|
||||
|
||||
Assert.hasText(compact, "JWT String cannot be null or empty.");
|
||||
Assert.notNull(compact, "Compact reader cannot be null.");
|
||||
Assert.stateNotNull(unencodedPayload, "internal error: unencodedPayload is null.");
|
||||
|
||||
final TokenizedJwt tokenized = jwtTokenizer.tokenize(compact);
|
||||
|
@ -767,16 +770,16 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public <T> T parse(String compact, JwtHandler<T> handler) {
|
||||
public <T> T parse(CharSequence compact, JwtHandler<T> handler) {
|
||||
return parse(compact, Payload.EMPTY, handler);
|
||||
}
|
||||
|
||||
private <T> T parse(String compact, Payload unencodedPayload, JwtHandler<T> handler)
|
||||
private <T> T parse(CharSequence compact, Payload unencodedPayload, JwtHandler<T> handler)
|
||||
throws ExpiredJwtException, 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, unencodedPayload);
|
||||
Jwt<?, ?> jwt = parse(new CharSequenceReader(compact), unencodedPayload);
|
||||
|
||||
if (jwt instanceof Jws) {
|
||||
Jws<?> jws = (Jws<?>) jwt;
|
||||
|
@ -805,7 +808,7 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Jwt<Header, byte[]> parseContentJwt(String compact) {
|
||||
public Jwt<Header, byte[]> parseContentJwt(CharSequence compact) {
|
||||
return parse(compact, new JwtHandlerAdapter<Jwt<Header, byte[]>>() {
|
||||
@Override
|
||||
public Jwt<Header, byte[]> onContentJwt(Jwt<Header, byte[]> jwt) {
|
||||
|
@ -815,7 +818,7 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Jwt<Header, Claims> parseClaimsJwt(String compact) {
|
||||
public Jwt<Header, Claims> parseClaimsJwt(CharSequence compact) {
|
||||
return parse(compact, new JwtHandlerAdapter<Jwt<Header, Claims>>() {
|
||||
@Override
|
||||
public Jwt<Header, Claims> onClaimsJwt(Jwt<Header, Claims> jwt) {
|
||||
|
@ -825,7 +828,7 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Jws<byte[]> parseContentJws(String compact) {
|
||||
public Jws<byte[]> parseContentJws(CharSequence compact) {
|
||||
return parse(compact, new JwtHandlerAdapter<Jws<byte[]>>() {
|
||||
@Override
|
||||
public Jws<byte[]> onContentJws(Jws<byte[]> jws) {
|
||||
|
@ -835,7 +838,7 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Jws<Claims> parseClaimsJws(String compact) {
|
||||
public Jws<Claims> parseClaimsJws(CharSequence compact) {
|
||||
return parse(compact, new JwtHandlerAdapter<Jws<Claims>>() {
|
||||
@Override
|
||||
public Jws<Claims> onClaimsJws(Jws<Claims> jws) {
|
||||
|
@ -844,7 +847,7 @@ public class DefaultJwtParser implements JwtParser {
|
|||
});
|
||||
}
|
||||
|
||||
private Jws<byte[]> parseContentJws(String jws, Payload unencodedPayload) {
|
||||
private Jws<byte[]> parseContentJws(CharSequence jws, Payload unencodedPayload) {
|
||||
return parse(jws, unencodedPayload, new JwtHandlerAdapter<Jws<byte[]>>() {
|
||||
@Override
|
||||
public Jws<byte[]> onContentJws(Jws<byte[]> jws) {
|
||||
|
@ -853,7 +856,7 @@ public class DefaultJwtParser implements JwtParser {
|
|||
});
|
||||
}
|
||||
|
||||
private Jws<Claims> parseClaimsJws(String jws, Payload unencodedPayload) {
|
||||
private Jws<Claims> parseClaimsJws(CharSequence jws, Payload unencodedPayload) {
|
||||
unencodedPayload.setClaimsExpected(true);
|
||||
return parse(jws, unencodedPayload, new JwtHandlerAdapter<Jws<Claims>>() {
|
||||
@Override
|
||||
|
@ -864,13 +867,13 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Jws<byte[]> parseContentJws(String jws, byte[] unencodedPayload) {
|
||||
public Jws<byte[]> parseContentJws(CharSequence jws, byte[] unencodedPayload) {
|
||||
Assert.notEmpty(unencodedPayload, "unencodedPayload argument cannot be null or empty.");
|
||||
return parseContentJws(jws, new Payload(unencodedPayload, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Jws<Claims> parseClaimsJws(String jws, byte[] unencodedPayload) {
|
||||
public Jws<Claims> parseClaimsJws(CharSequence jws, byte[] unencodedPayload) {
|
||||
Assert.notEmpty(unencodedPayload, "unencodedPayload argument cannot be null or empty.");
|
||||
return parseClaimsJws(jws, new Payload(unencodedPayload, null));
|
||||
}
|
||||
|
@ -885,13 +888,13 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Jws<byte[]> parseContentJws(String jws, InputStream unencodedPayload) {
|
||||
public Jws<byte[]> parseContentJws(CharSequence jws, InputStream unencodedPayload) {
|
||||
Assert.notNull(unencodedPayload, "unencodedPayload InputStream cannot be null.");
|
||||
return parseContentJws(jws, payloadFor(unencodedPayload));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Jws<Claims> parseClaimsJws(String jws, InputStream unencodedPayload) {
|
||||
public Jws<Claims> parseClaimsJws(CharSequence jws, InputStream unencodedPayload) {
|
||||
Assert.notNull(unencodedPayload, "unencodedPayload InputStream cannot be null.");
|
||||
byte[] bytes = Streams.bytes(unencodedPayload,
|
||||
"Unable to obtain Claims bytes from unencodedPayload InputStream");
|
||||
|
@ -899,7 +902,7 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Jwe<byte[]> parseContentJwe(String compact) throws JwtException {
|
||||
public Jwe<byte[]> parseContentJwe(CharSequence compact) throws JwtException {
|
||||
return parse(compact, new JwtHandlerAdapter<Jwe<byte[]>>() {
|
||||
@Override
|
||||
public Jwe<byte[]> onContentJwe(Jwe<byte[]> jwe) {
|
||||
|
@ -909,7 +912,7 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Jwe<Claims> parseClaimsJwe(String compact) throws JwtException {
|
||||
public Jwe<Claims> parseClaimsJwe(CharSequence compact) throws JwtException {
|
||||
return parse(compact, new JwtHandlerAdapter<Jwe<Claims>>() {
|
||||
@Override
|
||||
public Jwe<Claims> onClaimsJwe(Jwe<Claims> jwe) {
|
||||
|
@ -932,8 +935,9 @@ public class DefaultJwtParser implements JwtParser {
|
|||
|
||||
protected Map<String, ?> deserialize(InputStream in, final String name) {
|
||||
try {
|
||||
Reader reader = Streams.reader(in);
|
||||
JsonObjectDeserializer deserializer = new JsonObjectDeserializer(this.deserializer, name);
|
||||
return deserializer.apply(in);
|
||||
return deserializer.apply(reader);
|
||||
} finally {
|
||||
Objects.nullSafeClose(in);
|
||||
}
|
||||
|
|
|
@ -16,9 +16,13 @@
|
|||
package io.jsonwebtoken.impl;
|
||||
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.impl.io.Streams;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.Strings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
public class JwtTokenizer {
|
||||
|
||||
static final char DELIMITER = '.';
|
||||
|
@ -26,23 +30,37 @@ public class JwtTokenizer {
|
|||
private static final String DELIM_ERR_MSG_PREFIX = "Invalid compact JWT string: Compact JWSs must contain " +
|
||||
"exactly 2 period characters, and compact JWEs must contain exactly 4. Found: ";
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends TokenizedJwt> T tokenize(CharSequence jwt) {
|
||||
private static int read(Reader r, char[] buf) {
|
||||
try {
|
||||
return r.read(buf);
|
||||
} catch (IOException e) {
|
||||
String msg = "Unable to read compact JWT: " + e.getMessage();
|
||||
throw new MalformedJwtException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.hasText(jwt, "Argument cannot be null or empty.");
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends TokenizedJwt> T tokenize(Reader reader) {
|
||||
|
||||
Assert.notNull(reader, "Reader argument cannot be null.");
|
||||
|
||||
CharSequence protectedHeader = Strings.EMPTY; //Both JWS and JWE
|
||||
CharSequence body = Strings.EMPTY; //JWS payload or JWE Ciphertext
|
||||
CharSequence encryptedKey = Strings.EMPTY; //JWE only
|
||||
CharSequence iv = Strings.EMPTY; //JWE only
|
||||
CharSequence digest; //JWS Signature or JWE AAD Tag
|
||||
CharSequence digest = Strings.EMPTY; //JWS Signature or JWE AAD Tag
|
||||
|
||||
int delimiterCount = 0;
|
||||
int start = 0;
|
||||
char[] buf = new char[4096];
|
||||
int len = 0;
|
||||
StringBuilder sb = new StringBuilder(4096);
|
||||
while (len != Streams.EOF) {
|
||||
|
||||
for (int i = 0; i < jwt.length(); i++) {
|
||||
len = read(reader, buf);
|
||||
|
||||
char c = jwt.charAt(i);
|
||||
for (int i = 0; i < len; i++) {
|
||||
|
||||
char c = buf[i];
|
||||
|
||||
if (Character.isWhitespace(c)) {
|
||||
String msg = "Compact JWT strings may not contain whitespace.";
|
||||
|
@ -51,8 +69,8 @@ public class JwtTokenizer {
|
|||
|
||||
if (c == DELIMITER) {
|
||||
|
||||
CharSequence token = jwt.subSequence(start, i);
|
||||
start = i + 1;
|
||||
CharSequence seq = Strings.clean(sb);
|
||||
String token = seq != null ? seq.toString() : Strings.EMPTY;
|
||||
|
||||
switch (delimiterCount) {
|
||||
case 0:
|
||||
|
@ -72,6 +90,10 @@ public class JwtTokenizer {
|
|||
}
|
||||
|
||||
delimiterCount++;
|
||||
sb.setLength(0);
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,7 +102,9 @@ public class JwtTokenizer {
|
|||
throw new MalformedJwtException(msg);
|
||||
}
|
||||
|
||||
digest = jwt.subSequence(start, jwt.length());
|
||||
if (sb.length() > 0) {
|
||||
digest = sb.toString();
|
||||
}
|
||||
|
||||
if (delimiterCount == 2) {
|
||||
return (T) new DefaultTokenizedJwt(protectedHeader, body, digest);
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright © 2023 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.impl.io;
|
||||
|
||||
import io.jsonwebtoken.io.Parser;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
public abstract class AbstractParser<T> implements Parser<T> {
|
||||
|
||||
@Override
|
||||
public final T parse(CharSequence input) {
|
||||
Assert.hasText(input, "CharSequence cannot be null or empty.");
|
||||
return parse(input, 0, input.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public T parse(CharSequence input, int start, int end) {
|
||||
Assert.hasText(input, "CharSequence cannot be null or empty.");
|
||||
Reader reader = new CharSequenceReader(input, start, end);
|
||||
return parse(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T parse(InputStream in) {
|
||||
Assert.notNull(in, "InputStream cannot be null.");
|
||||
Reader reader = Streams.reader(in);
|
||||
return parse(reader);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,297 @@
|
|||
/*
|
||||
* Copyright © 2023 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.impl.io;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@link Reader} implementation that can read from String, StringBuffer, StringBuilder or CharBuffer.
|
||||
*
|
||||
* <p>
|
||||
* <strong>Note:</strong> Supports {@link #mark(int)} and {@link #reset()}.
|
||||
* </p>
|
||||
*
|
||||
* @since JJWT_RELEASE_VERSION, copied from commons-io
|
||||
* <a href="https://github.com/apache/commons-io/blob/e67946c81a55069dcd32dd588faa57dd1532455f/src/main/java/org/apache/commons/io/input/CharSequenceInputStream.java">2.14.0</a>
|
||||
*/
|
||||
public class CharSequenceReader extends Reader implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3724187752191401220L;
|
||||
private final CharSequence charSequence;
|
||||
private int idx;
|
||||
private int mark;
|
||||
|
||||
/**
|
||||
* The start index in the character sequence, inclusive.
|
||||
* <p>
|
||||
* When de-serializing a CharSequenceReader that was serialized before
|
||||
* this fields was added, this field will be initialized to 0, which
|
||||
* gives the same behavior as before: start reading from the start.
|
||||
* </p>
|
||||
*
|
||||
* @see #start()
|
||||
* @since 2.7
|
||||
*/
|
||||
private final int start;
|
||||
|
||||
/**
|
||||
* The end index in the character sequence, exclusive.
|
||||
* <p>
|
||||
* When de-serializing a CharSequenceReader that was serialized before
|
||||
* this fields was added, this field will be initialized to {@code null},
|
||||
* which gives the same behavior as before: stop reading at the
|
||||
* CharSequence's length.
|
||||
* If this field was an int instead, it would be initialized to 0 when the
|
||||
* CharSequenceReader is de-serialized, causing it to not return any
|
||||
* characters at all.
|
||||
* </p>
|
||||
*
|
||||
* @see #end()
|
||||
* @since 2.7
|
||||
*/
|
||||
private final Integer end;
|
||||
|
||||
/**
|
||||
* Constructs a new instance with the specified character sequence.
|
||||
*
|
||||
* @param charSequence The character sequence, may be {@code null}
|
||||
*/
|
||||
public CharSequenceReader(final CharSequence charSequence) {
|
||||
this(charSequence, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance with a portion of the specified character sequence.
|
||||
* <p>
|
||||
* The start index is not strictly enforced to be within the bounds of the
|
||||
* character sequence. This allows the character sequence to grow or shrink
|
||||
* in size without risking any {@link IndexOutOfBoundsException} to be thrown.
|
||||
* Instead, if the character sequence grows smaller than the start index, this
|
||||
* instance will act as if all characters have been read.
|
||||
* </p>
|
||||
*
|
||||
* @param charSequence The character sequence, may be {@code null}
|
||||
* @param start The start index in the character sequence, inclusive
|
||||
* @throws IllegalArgumentException if the start index is negative
|
||||
*/
|
||||
public CharSequenceReader(final CharSequence charSequence, final int start) {
|
||||
this(charSequence, start, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance with a portion of the specified character sequence.
|
||||
* <p>
|
||||
* The start and end indexes are not strictly enforced to be within the bounds
|
||||
* of the character sequence. This allows the character sequence to grow or shrink
|
||||
* in size without risking any {@link IndexOutOfBoundsException} to be thrown.
|
||||
* Instead, if the character sequence grows smaller than the start index, this
|
||||
* instance will act as if all characters have been read; if the character sequence
|
||||
* grows smaller than the end, this instance will use the actual character sequence
|
||||
* length.
|
||||
* </p>
|
||||
*
|
||||
* @param charSequence The character sequence, may be {@code null}
|
||||
* @param start The start index in the character sequence, inclusive
|
||||
* @param end The end index in the character sequence, exclusive
|
||||
* @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index
|
||||
*/
|
||||
public CharSequenceReader(final CharSequence charSequence, final int start, final int end) {
|
||||
if (start < 0) {
|
||||
throw new IllegalArgumentException("Start index is less than zero: " + start);
|
||||
}
|
||||
if (end < start) {
|
||||
throw new IllegalArgumentException("End index is less than start " + start + ": " + end);
|
||||
}
|
||||
// Don't check the start and end indexes against the CharSequence,
|
||||
// to let it grow and shrink without breaking existing behavior.
|
||||
|
||||
this.charSequence = charSequence != null ? charSequence : "";
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
|
||||
this.idx = start;
|
||||
this.mark = start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close resets the file back to the start and removes any marked position.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
idx = start;
|
||||
mark = start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index in the character sequence to end reading at, taking into account its length.
|
||||
*
|
||||
* @return The end index in the character sequence (exclusive).
|
||||
*/
|
||||
private int end() {
|
||||
/*
|
||||
* end == null for de-serialized instances that were serialized before start and end were added.
|
||||
* Use Integer.MAX_VALUE to get the same behavior as before - use the entire CharSequence.
|
||||
*/
|
||||
return Math.min(charSequence.length(), end == null ? Integer.MAX_VALUE : end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the current position.
|
||||
*
|
||||
* @param readAheadLimit ignored
|
||||
*/
|
||||
@Override
|
||||
public void mark(final int readAheadLimit) {
|
||||
mark = idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark is supported (returns true).
|
||||
*
|
||||
* @return {@code true}
|
||||
*/
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single character.
|
||||
*
|
||||
* @return the next character from the character sequence
|
||||
* or -1 if the end has been reached.
|
||||
*/
|
||||
@Override
|
||||
public int read() {
|
||||
if (idx >= end()) {
|
||||
return Streams.EOF;
|
||||
}
|
||||
return charSequence.charAt(idx++);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the specified number of characters into the array.
|
||||
*
|
||||
* @param array The array to store the characters in
|
||||
* @param offset The starting position in the array to store
|
||||
* @param length The maximum number of characters to read
|
||||
* @return The number of characters read or -1 if there are
|
||||
* no more
|
||||
*/
|
||||
@Override
|
||||
public int read(final char[] array, final int offset, final int length) {
|
||||
if (idx >= end()) {
|
||||
return Streams.EOF;
|
||||
}
|
||||
Objects.requireNonNull(array, "array");
|
||||
if (length < 0 || offset < 0 || offset + length > array.length) {
|
||||
throw new IndexOutOfBoundsException("Array Size=" + array.length +
|
||||
", offset=" + offset + ", length=" + length);
|
||||
}
|
||||
|
||||
if (charSequence instanceof String) {
|
||||
final int count = Math.min(length, end() - idx);
|
||||
((String) charSequence).getChars(idx, idx + count, array, offset);
|
||||
idx += count;
|
||||
return count;
|
||||
}
|
||||
if (charSequence instanceof StringBuilder) {
|
||||
final int count = Math.min(length, end() - idx);
|
||||
((StringBuilder) charSequence).getChars(idx, idx + count, array, offset);
|
||||
idx += count;
|
||||
return count;
|
||||
}
|
||||
if (charSequence instanceof StringBuffer) {
|
||||
final int count = Math.min(length, end() - idx);
|
||||
((StringBuffer) charSequence).getChars(idx, idx + count, array, offset);
|
||||
idx += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
final int c = read();
|
||||
if (c == Streams.EOF) {
|
||||
return count;
|
||||
}
|
||||
array[offset + i] = (char) c;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether this stream is ready to be read.
|
||||
*
|
||||
* @return {@code true} if more characters from the character sequence are available, or {@code false} otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean ready() {
|
||||
return idx < end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the reader to the last marked position (or the beginning if
|
||||
* mark has not been called).
|
||||
*/
|
||||
@Override
|
||||
public void reset() {
|
||||
idx = mark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the specified number of characters.
|
||||
*
|
||||
* @param n The number of characters to skip
|
||||
* @return The actual number of characters skipped
|
||||
*/
|
||||
@Override
|
||||
public long skip(final long n) {
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("Number of characters to skip is less than zero: " + n);
|
||||
}
|
||||
if (idx >= end()) {
|
||||
return 0;
|
||||
}
|
||||
final int dest = (int) Math.min(end(), idx + n);
|
||||
final int count = dest - idx;
|
||||
idx = dest;
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index in the character sequence to start reading from, taking into account its length.
|
||||
*
|
||||
* @return The start index in the character sequence (inclusive).
|
||||
*/
|
||||
private int start() {
|
||||
return Math.min(charSequence.length(), start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a String representation of the underlying
|
||||
* character sequence.
|
||||
*
|
||||
* @return The contents of the character sequence
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
final CharSequence subSequence = charSequence.subSequence(start(), end());
|
||||
return subSequence.toString();
|
||||
}
|
||||
}
|
|
@ -17,33 +17,25 @@ package io.jsonwebtoken.impl.io;
|
|||
|
||||
import io.jsonwebtoken.impl.lang.Converter;
|
||||
import io.jsonwebtoken.impl.lang.Function;
|
||||
import io.jsonwebtoken.io.Parser;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.Strings;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.util.Map;
|
||||
|
||||
public class ConvertingParser<T> implements Parser<T> {
|
||||
public class ConvertingParser<T> extends AbstractParser<T> {
|
||||
|
||||
private final Function<InputStream, Map<String, ?>> deserializer;
|
||||
private final Function<Reader, Map<String, ?>> deserializer;
|
||||
private final Converter<T, Object> converter;
|
||||
|
||||
public ConvertingParser(Function<InputStream, Map<String, ?>> deserializer, Converter<T, Object> converter) {
|
||||
public ConvertingParser(Function<Reader, Map<String, ?>> deserializer, Converter<T, Object> converter) {
|
||||
this.deserializer = Assert.notNull(deserializer, "Deserializer function cannot be null.");
|
||||
this.converter = Assert.notNull(converter, "Converter cannot be null.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T parse(String input) {
|
||||
Assert.hasText(input, "Parse input String cannot be null or empty.");
|
||||
InputStream in = new ByteArrayInputStream(Strings.utf8(input));
|
||||
return parse(in);
|
||||
}
|
||||
|
||||
public final T parse(InputStream in) {
|
||||
Map<String, ?> m = this.deserializer.apply(in);
|
||||
public final T parse(Reader reader) {
|
||||
Assert.notNull(reader, "Reader cannot be null.");
|
||||
Map<String, ?> m = this.deserializer.apply(reader);
|
||||
return this.converter.applyFrom(m);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import io.jsonwebtoken.io.DeserializationException;
|
|||
import io.jsonwebtoken.io.Deserializer;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -29,7 +29,7 @@ import java.util.Map;
|
|||
*
|
||||
* @since 0.11.3 (renamed from JwtDeserializer)
|
||||
*/
|
||||
public class JsonObjectDeserializer implements Function<InputStream, Map<String, ?>> {
|
||||
public class JsonObjectDeserializer implements Function<Reader, Map<String, ?>> {
|
||||
|
||||
private static final String MALFORMED_ERROR = "Malformed %s JSON: %s";
|
||||
private static final String MALFORMED_COMPLEX_ERROR = "Malformed or excessively complex %s JSON. " +
|
||||
|
@ -45,7 +45,7 @@ public class JsonObjectDeserializer implements Function<InputStream, Map<String,
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ?> apply(InputStream in) {
|
||||
public Map<String, ?> apply(Reader in) {
|
||||
Assert.notNull(in, "InputStream argument cannot be null.");
|
||||
Object value;
|
||||
try {
|
||||
|
|
|
@ -27,7 +27,9 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.Flushable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
|
@ -57,6 +59,18 @@ public class Streams {
|
|||
return of(Strings.utf8(seq));
|
||||
}
|
||||
|
||||
public static Reader reader(byte[] bytes) {
|
||||
return reader(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
public static Reader reader(InputStream in) {
|
||||
return new InputStreamReader(in, Strings.UTF_8);
|
||||
}
|
||||
|
||||
public static Reader reader(CharSequence seq) {
|
||||
return new CharSequenceReader(seq);
|
||||
}
|
||||
|
||||
public static void flush(Flushable... flushables) {
|
||||
Objects.nullSafeFlush(flushables);
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ class RFC7515AppendixETest {
|
|||
}
|
||||
|
||||
static <T> T deser(String s) {
|
||||
T t = deserializer.deserialize(Streams.of(s)) as T
|
||||
T t = deserializer.deserialize(Streams.reader(s)) as T
|
||||
return t
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,8 @@ class DefaultJwtBuilderTest {
|
|||
}
|
||||
|
||||
private static Map<String, ?> deser(byte[] data) {
|
||||
Map<String, ?> m = Services.loadFirst(Deserializer).deserialize(Streams.of(data)) as Map<String, ?>
|
||||
def reader = Streams.reader(data)
|
||||
Map<String, ?> m = Services.loadFirst(Deserializer).deserialize(reader) as Map<String, ?>
|
||||
return m
|
||||
}
|
||||
|
||||
|
@ -598,7 +599,7 @@ class DefaultJwtBuilderTest {
|
|||
def jwt = builder.audience().single(audienceSingleString).compact()
|
||||
// can't use the parser here to validate because it coerces the string value into an array automatically,
|
||||
// so we need to check the raw payload:
|
||||
def encoded = new JwtTokenizer().tokenize(jwt).getPayload()
|
||||
def encoded = new JwtTokenizer().tokenize(Streams.reader(jwt)).getPayload()
|
||||
byte[] bytes = Decoders.BASE64URL.decode(encoded)
|
||||
def claims = deser(bytes)
|
||||
|
||||
|
@ -617,7 +618,7 @@ class DefaultJwtBuilderTest {
|
|||
def jwt = builder.audience().single(first).audience().single(second).compact()
|
||||
// can't use the parser here to validate because it coerces the string value into an array automatically,
|
||||
// so we need to check the raw payload:
|
||||
def encoded = new JwtTokenizer().tokenize(jwt).getPayload()
|
||||
def encoded = new JwtTokenizer().tokenize(Streams.reader(jwt)).getPayload()
|
||||
byte[] bytes = Decoders.BASE64URL.decode(encoded)
|
||||
def claims = deser(bytes)
|
||||
|
||||
|
@ -753,9 +754,9 @@ class DefaultJwtBuilderTest {
|
|||
|
||||
// can't use the parser here to validate because it coerces the string value into an array automatically,
|
||||
// so we need to check the raw payload:
|
||||
def encoded = new JwtTokenizer().tokenize(jwt).getPayload()
|
||||
def encoded = new JwtTokenizer().tokenize(Streams.reader(jwt)).getPayload()
|
||||
byte[] bytes = Decoders.BASE64URL.decode(encoded)
|
||||
def claims = Services.loadFirst(Deserializer).deserialize(Streams.of(bytes))
|
||||
def claims = Services.loadFirst(Deserializer).deserialize(Streams.reader(bytes))
|
||||
|
||||
assertEquals two, claims.aud
|
||||
}
|
||||
|
@ -790,7 +791,7 @@ class DefaultJwtBuilderTest {
|
|||
|
||||
// can't use the parser here to validate because it coerces the string value into an array automatically,
|
||||
// so we need to check the raw payload:
|
||||
def encoded = new JwtTokenizer().tokenize(jwt).getPayload()
|
||||
def encoded = new JwtTokenizer().tokenize(Streams.reader(jwt)).getPayload()
|
||||
byte[] bytes = Decoders.BASE64URL.decode(encoded)
|
||||
def claims = deser(bytes)
|
||||
|
||||
|
|
|
@ -121,8 +121,8 @@ class DefaultJwtParserBuilderTest {
|
|||
void testDeserializeJsonWithCustomSerializer() {
|
||||
def deserializer = new AbstractDeserializer() {
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream inputStream) throws Exception {
|
||||
return OBJECT_MAPPER.readValue(inputStream, Map.class)
|
||||
protected Object doDeserialize(Reader reader) throws Exception {
|
||||
return OBJECT_MAPPER.readValue(reader, Map.class)
|
||||
}
|
||||
}
|
||||
def p = builder.deserializeJsonWith(deserializer)
|
||||
|
|
|
@ -75,9 +75,9 @@ class DefaultJwtParserTest {
|
|||
boolean invoked = false
|
||||
def deserializer = new AbstractDeserializer() {
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream inputStream) throws Exception {
|
||||
protected Object doDeserialize(Reader reader) throws Exception {
|
||||
invoked = true
|
||||
return OBJECT_MAPPER.readValue(inputStream, Map.class)
|
||||
return OBJECT_MAPPER.readValue(reader, Map.class)
|
||||
}
|
||||
}
|
||||
def pb = Jwts.parser().deserializeJsonWith(deserializer)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package io.jsonwebtoken.impl
|
||||
|
||||
import io.jsonwebtoken.MalformedJwtException
|
||||
import io.jsonwebtoken.impl.io.Streams
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
|
@ -32,40 +33,63 @@ class JwtTokenizerTest {
|
|||
tokenizer = new JwtTokenizer()
|
||||
}
|
||||
|
||||
private def tokenize(CharSequence s) {
|
||||
return tokenizer.tokenize(Streams.reader(s))
|
||||
}
|
||||
|
||||
@Test(expected = MalformedJwtException)
|
||||
void testParseWithWhitespaceInBase64UrlHeader() {
|
||||
def input = 'header .body.signature'
|
||||
tokenizer.tokenize(input)
|
||||
tokenize(input)
|
||||
}
|
||||
|
||||
@Test(expected = MalformedJwtException)
|
||||
void testParseWithWhitespaceInBase64UrlBody() {
|
||||
def input = 'header. body.signature'
|
||||
tokenizer.tokenize(input)
|
||||
tokenize(input)
|
||||
}
|
||||
|
||||
@Test(expected = MalformedJwtException)
|
||||
void testParseWithWhitespaceInBase64UrlSignature() {
|
||||
def input = 'header.body. signature'
|
||||
tokenizer.tokenize(input)
|
||||
tokenize(input)
|
||||
}
|
||||
|
||||
@Test(expected = MalformedJwtException)
|
||||
void testParseWithWhitespaceInBase64UrlJweBody() {
|
||||
def input = 'header.encryptedKey.initializationVector. body.authenticationTag'
|
||||
tokenizer.tokenize(input)
|
||||
tokenize(input)
|
||||
}
|
||||
|
||||
@Test(expected = MalformedJwtException)
|
||||
void testParseWithWhitespaceInBase64UrlJweTag() {
|
||||
def input = 'header.encryptedKey.initializationVector.body. authenticationTag'
|
||||
tokenizer.tokenize(input)
|
||||
tokenize(input)
|
||||
}
|
||||
|
||||
@Test
|
||||
void readerExceptionResultsInMalformedJwtException() {
|
||||
IOException ioe = new IOException('foo')
|
||||
def reader = new StringReader('hello') {
|
||||
@Override
|
||||
int read(char[] chars) throws IOException {
|
||||
throw ioe
|
||||
}
|
||||
}
|
||||
try {
|
||||
JwtTokenizer.read(reader, new char[0])
|
||||
fail()
|
||||
} catch (MalformedJwtException expected) {
|
||||
String msg = 'Unable to read compact JWT: foo'
|
||||
assertEquals msg, expected.message
|
||||
assertSame ioe, expected.cause
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyJws() {
|
||||
def input = CharBuffer.wrap('header..digest'.toCharArray())
|
||||
def t = tokenizer.tokenize(input)
|
||||
def t = tokenize(input)
|
||||
assertTrue t instanceof TokenizedJwt
|
||||
assertFalse t instanceof TokenizedJwe
|
||||
assertEquals 'header', t.getProtected().toString()
|
||||
|
@ -78,7 +102,7 @@ class JwtTokenizerTest {
|
|||
|
||||
def input = 'header.encryptedKey.initializationVector.body.authenticationTag'
|
||||
|
||||
def t = tokenizer.tokenize(input)
|
||||
def t = tokenize(input)
|
||||
|
||||
assertNotNull t
|
||||
assertTrue t instanceof TokenizedJwe
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl
|
||||
|
||||
import io.jsonwebtoken.impl.io.Streams
|
||||
import io.jsonwebtoken.impl.io.CharSequenceReader
|
||||
import io.jsonwebtoken.impl.lang.Services
|
||||
import io.jsonwebtoken.impl.security.Randoms
|
||||
import io.jsonwebtoken.io.Decoders
|
||||
|
@ -37,7 +37,8 @@ class RfcTests {
|
|||
}
|
||||
|
||||
static final Map<String, ?> jsonToMap(String json) {
|
||||
Map<String, ?> m = Services.loadFirst(Deserializer).deserialize(Streams.of(json)) as Map<String, ?>
|
||||
Reader r = new CharSequenceReader(json)
|
||||
Map<String, ?> m = Services.loadFirst(Deserializer).deserialize(r) as Map<String, ?>
|
||||
return m
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
package io.jsonwebtoken.impl.io
|
||||
|
||||
import io.jsonwebtoken.MalformedJwtException
|
||||
import io.jsonwebtoken.impl.lang.Bytes
|
||||
import io.jsonwebtoken.io.DeserializationException
|
||||
import io.jsonwebtoken.io.Deserializer
|
||||
import io.jsonwebtoken.lang.Strings
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
@ -40,14 +40,14 @@ class JsonObjectDeserializerTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
Object deserialize(InputStream inputStream) throws DeserializationException {
|
||||
Object deserialize(Reader reader) throws DeserializationException {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
try {
|
||||
// doesn't matter for this test, just has to be non-null:
|
||||
def ins = new ByteArrayInputStream(Bytes.EMPTY)
|
||||
new JsonObjectDeserializer(deser, 'claims').apply(ins)
|
||||
def r = new StringReader(Strings.EMPTY)
|
||||
new JsonObjectDeserializer(deser, 'claims').apply(r)
|
||||
fail()
|
||||
} catch (DeserializationException e) {
|
||||
String msg = String.format(JsonObjectDeserializer.MALFORMED_COMPLEX_ERROR, 'claims', 'claims', 'foo')
|
||||
|
@ -67,15 +67,16 @@ class JsonObjectDeserializerTest {
|
|||
fail() // should not be called in this test
|
||||
return null
|
||||
}
|
||||
|
||||
@Override
|
||||
Object deserialize(InputStream inputStream) throws DeserializationException {
|
||||
Object deserialize(Reader reader) throws DeserializationException {
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
try {
|
||||
// doesn't matter for this test, just has to be non-null:
|
||||
def ins = new ByteArrayInputStream(Bytes.EMPTY)
|
||||
new JsonObjectDeserializer(deser, 'claims').apply(ins)
|
||||
def r = new StringReader(Strings.EMPTY)
|
||||
new JsonObjectDeserializer(deser, 'claims').apply(r)
|
||||
fail()
|
||||
} catch (MalformedJwtException e) {
|
||||
String msg = String.format(JsonObjectDeserializer.MALFORMED_ERROR, 'claims', 'foo')
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl.security
|
||||
|
||||
import io.jsonwebtoken.impl.io.CharSequenceReader
|
||||
import io.jsonwebtoken.impl.io.ConvertingParser
|
||||
import io.jsonwebtoken.io.AbstractDeserializer
|
||||
import io.jsonwebtoken.io.DeserializationException
|
||||
|
@ -49,6 +50,16 @@ class DefaultJwkParserBuilderTest {
|
|||
KipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt"
|
||||
}''')
|
||||
|
||||
@Test(expected = IllegalArgumentException)
|
||||
void parseNull() {
|
||||
Jwks.parser().build().parse((CharSequence)null)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException)
|
||||
void parseEmpty() {
|
||||
Jwks.parser().build().parse(Strings.EMPTY)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStaticFactoryMethod() {
|
||||
assertTrue Jwks.parser() instanceof DefaultJwkParserBuilder
|
||||
|
@ -65,7 +76,7 @@ class DefaultJwkParserBuilderTest {
|
|||
void testDeserializer() {
|
||||
Deserializer<Map<String, ?>> deser = createMock(Deserializer)
|
||||
def m = RFC7516AppendixA3Test.KEK_VALUES // any test key will do
|
||||
expect(deser.deserialize((InputStream)anyObject(InputStream))).andReturn(m)
|
||||
expect(deser.deserialize((Reader) anyObject(Reader))).andReturn(m)
|
||||
replay deser
|
||||
def jwk = Jwks.parser().json(deser).build().parse('foo')
|
||||
verify deser
|
||||
|
@ -110,7 +121,19 @@ class DefaultJwkParserBuilderTest {
|
|||
//noinspection GroovyAssignabilityCheck
|
||||
def jwk = Jwks.builder().key(key).build()
|
||||
String json = Jwks.UNSAFE_JSON(jwk)
|
||||
def parsed = Jwks.parser().build().parse(json)
|
||||
|
||||
def parser = Jwks.parser().build()
|
||||
|
||||
// CharSequence parsing:
|
||||
def parsed = parser.parse(json)
|
||||
assertEquals jwk, parsed
|
||||
|
||||
// Reader parsing:
|
||||
parsed = parser.parse(new CharSequenceReader(json))
|
||||
assertEquals jwk, parsed
|
||||
|
||||
// InputStream parsing:
|
||||
parsed = parser.parse(new ByteArrayInputStream(Strings.utf8(json)))
|
||||
assertEquals jwk, parsed
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +164,7 @@ class DefaultJwkParserBuilderTest {
|
|||
void testDeserializationFailure() {
|
||||
def deser = new AbstractDeserializer() {
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream inputStream) throws Exception {
|
||||
protected Object doDeserialize(Reader reader) throws Exception {
|
||||
throw new DeserializationException('test')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ class DefaultJwkSetParserBuilderTest {
|
|||
void testDeserializeException() {
|
||||
def deser = new AbstractDeserializer() {
|
||||
@Override
|
||||
protected Object doDeserialize(InputStream inputStream) throws Exception {
|
||||
protected Object doDeserialize(Reader reader) throws Exception {
|
||||
throw new DeserializationException('foo')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,8 +47,7 @@ class JwkSerializationTest {
|
|||
}
|
||||
|
||||
static Map<String, ?> deserialize(Deserializer des, String value) {
|
||||
def ins = new ByteArrayInputStream(Strings.utf8(value))
|
||||
return des.deserialize(ins) as Map<String, ?>
|
||||
return des.deserialize(new StringReader(value)) as Map<String, ?>
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -18,7 +18,6 @@ package io.jsonwebtoken.impl.security
|
|||
import io.jsonwebtoken.Claims
|
||||
import io.jsonwebtoken.Jwe
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.impl.io.Streams
|
||||
import io.jsonwebtoken.impl.lang.Services
|
||||
import io.jsonwebtoken.io.Decoders
|
||||
import io.jsonwebtoken.io.Deserializer
|
||||
|
@ -44,7 +43,7 @@ class RFC7518AppendixCTest {
|
|||
}
|
||||
|
||||
private static final Map<String, ?> fromJson(String s) {
|
||||
return Services.loadFirst(Deserializer).deserialize(Streams.of(s)) as Map<String, ?>
|
||||
return Services.loadFirst(Deserializer).deserialize(new StringReader(s)) as Map<String, ?>
|
||||
}
|
||||
|
||||
private static EcPrivateJwk readJwk(String json) {
|
||||
|
|
1
pom.xml
1
pom.xml
|
@ -309,6 +309,7 @@
|
|||
<exclude>io/jsonwebtoken/impl/io/Base64InputStream.java</exclude>
|
||||
<exclude>io/jsonwebtoken/impl/io/FilteredInputStream.java</exclude>
|
||||
<exclude>io/jsonwebtoken/impl/io/FilteredOutputStream.java</exclude>
|
||||
<exclude>io/jsonwebtoken/impl/io/CharSequenceReader.java</exclude>
|
||||
</excludes>
|
||||
<methodPercentage>100.000000%</methodPercentage>
|
||||
<statementPercentage>100.000000%</statementPercentage>
|
||||
|
|
Loading…
Reference in New Issue