mirror of https://github.com/jwtk/jjwt.git
Implement type conversions of integral claim values
Jackson chooses the target type for JSON numbers based on their value, while deserializing without correct typing information present. This leads to a confusing behavior: String token = Jwts.builder() .claim("byte", (byte) 42) .claim("short", (short) 42) .claim("int", 42) .claim("long_small", (long) 42) .claim("long_big", ((long) Integer.MAX_VALUE) + 42) .compact(); Claims claims = (Claims) Jwts.parser().parse(token).getBody(); claims.get("int", Integer.class); // => 42 claims.get("long_big", Long.class); // => ((long) Integer.MAX_VALUE) + 42 claims.get("long_small", Long.class); // throws RequiredTypeException: required=Long, found=Integer claims.get("short", Short.class); // throws RequiredTypeException: required=Short, found=Integer claims.get("byte", Byte.class); // throws RequiredTypeException: required=Byte, found=Integer With this commit, `DefaultClaims.getClaim(String, Class<T>)` will correctly handle cases when required type is `Long`, `Integer`, `Short` or `Byte`: check that value fits in the required type and cast to it. // ... setup is the same as above claims.get("int", Integer.class); // => 42 claims.get("long_big", Long.class); // => ((long) Integer.MAX_VALUE) + 42 claims.get("long_small", Long.class); // => (long) 42 claims.get("short", Short.class); // => (short) 42 claims.get("byte", Byte.class); // => (byte) 42 Fixes #142.
This commit is contained in:
parent
8966c3a912
commit
13906d3746
|
@ -120,10 +120,25 @@ public class DefaultClaims extends JwtMap implements Claims {
|
|||
value = getDate(claimName);
|
||||
}
|
||||
|
||||
return castClaimValue(value, requiredType);
|
||||
}
|
||||
|
||||
private <T> T castClaimValue(Object value, Class<T> requiredType) {
|
||||
if (requiredType == Date.class && value instanceof Long) {
|
||||
value = new Date((Long)value);
|
||||
}
|
||||
|
||||
if (value instanceof Integer) {
|
||||
int intValue = (Integer) value;
|
||||
if (requiredType == Long.class) {
|
||||
value = (long) intValue;
|
||||
} else if (requiredType == Short.class && Short.MIN_VALUE <= intValue && intValue <= Short.MAX_VALUE) {
|
||||
value = (short) intValue;
|
||||
} else if (requiredType == Byte.class && Byte.MIN_VALUE <= intValue && intValue <= Byte.MAX_VALUE) {
|
||||
value = (byte) intValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!requiredType.isInstance(value)) {
|
||||
throw new RequiredTypeException("Expected value to be of type: " + requiredType + ", but was " + value.getClass());
|
||||
}
|
||||
|
|
|
@ -716,6 +716,37 @@ class JwtParserTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseClaimsJwsWithNumericTypes() {
|
||||
byte[] key = randomKey()
|
||||
|
||||
def b = (byte) 42
|
||||
def s = (short) 42
|
||||
def i = 42
|
||||
|
||||
def smallLong = (long) 42
|
||||
def bigLong = ((long) Integer.MAX_VALUE) + 42
|
||||
|
||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
||||
claim("byte", b).
|
||||
claim("short", s).
|
||||
claim("int", i).
|
||||
claim("long_small", smallLong).
|
||||
claim("long_big", bigLong).
|
||||
compact()
|
||||
|
||||
Jwt<Header,Claims> jwt = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
|
||||
|
||||
Claims claims = jwt.getBody()
|
||||
|
||||
assertEquals(b, claims.get("byte", Byte.class))
|
||||
assertEquals(s, claims.get("short", Short.class))
|
||||
assertEquals(i, claims.get("int", Integer.class))
|
||||
assertEquals(smallLong, claims.get("long_small", Long.class))
|
||||
assertEquals(bigLong, claims.get("long_big", Long.class))
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// parsePlaintextJws with signingKey resolver.
|
||||
// ========================================================================
|
||||
|
|
|
@ -52,11 +52,103 @@ class DefaultClaimsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testGetClaimWithRequiredType_Success() {
|
||||
claims.put("anInteger", new Integer(5))
|
||||
void testGetClaimWithRequiredType_Integer_Success() {
|
||||
def expected = new Integer(5)
|
||||
claims.put("anInteger", expected)
|
||||
Object result = claims.get("anInteger", Integer.class)
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
assertTrue(result instanceof Integer)
|
||||
@Test
|
||||
void testGetClaimWithRequiredType_Long_Success() {
|
||||
def expected = new Long(123)
|
||||
claims.put("aLong", expected)
|
||||
Object result = claims.get("aLong", Long.class)
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetClaimWithRequiredType_LongWithInteger_Success() {
|
||||
// long value that fits inside an Integer
|
||||
def expected = new Long(Integer.MAX_VALUE - 100)
|
||||
// deserialized as an Integer from JSON
|
||||
// (type information is not available during parsing)
|
||||
claims.put("smallLong", expected.intValue())
|
||||
// should still be available as Long
|
||||
Object result = claims.get("smallLong", Long.class)
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetClaimWithRequiredType_ShortWithInteger_Success() {
|
||||
def expected = new Short((short) 42)
|
||||
claims.put("short", expected.intValue())
|
||||
Object result = claims.get("short", Short.class)
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetClaimWithRequiredType_ShortWithBigInteger_Exception() {
|
||||
claims.put("tooBigForShort", ((int) Short.MAX_VALUE) + 42)
|
||||
try {
|
||||
claims.get("tooBigForShort", Short.class)
|
||||
fail("getClaim() shouldn't silently lose precision.")
|
||||
} catch (RequiredTypeException e) {
|
||||
assertEquals(
|
||||
e.getMessage(),
|
||||
"Expected value to be of type: class java.lang.Short, but was class java.lang.Integer"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetClaimWithRequiredType_ShortWithSmallInteger_Exception() {
|
||||
claims.put("tooSmallForShort", ((int) Short.MIN_VALUE) - 42)
|
||||
try {
|
||||
claims.get("tooSmallForShort", Short.class)
|
||||
fail("getClaim() shouldn't silently lose precision.")
|
||||
} catch (RequiredTypeException e) {
|
||||
assertEquals(
|
||||
e.getMessage(),
|
||||
"Expected value to be of type: class java.lang.Short, but was class java.lang.Integer"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetClaimWithRequiredType_ByteWithInteger_Success() {
|
||||
def expected = new Byte((byte) 42)
|
||||
claims.put("byte", expected.intValue())
|
||||
Object result = claims.get("byte", Byte.class)
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetClaimWithRequiredType_ByteWithBigInteger_Exception() {
|
||||
claims.put("tooBigForByte", ((int) Byte.MAX_VALUE) + 42)
|
||||
try {
|
||||
claims.get("tooBigForByte", Byte.class)
|
||||
fail("getClaim() shouldn't silently lose precision.")
|
||||
} catch (RequiredTypeException e) {
|
||||
assertEquals(
|
||||
e.getMessage(),
|
||||
"Expected value to be of type: class java.lang.Byte, but was class java.lang.Integer"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetClaimWithRequiredType_ByteWithSmallInteger_Exception() {
|
||||
claims.put("tooSmallForByte", ((int) Byte.MIN_VALUE) - 42)
|
||||
try {
|
||||
claims.get("tooSmallForByte", Byte.class)
|
||||
fail("getClaim() shouldn't silently lose precision.")
|
||||
} catch (RequiredTypeException e) {
|
||||
assertEquals(
|
||||
e.getMessage(),
|
||||
"Expected value to be of type: class java.lang.Byte, but was class java.lang.Integer"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue