Last active
June 9, 2022 02:50
-
-
Save soumyadey/ff748d3e21daded1a30c544198cd9a5b to your computer and use it in GitHub Desktop.
Utility class to parse JWT access token (uncopyrighted). See also: https://stackoverflow.com/questions/61565570/replacement-for-deprecated-spring-security-jwthelper/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.sd.utils; | |
@SuppressWarnings("serial") | |
public class JwtException extends RuntimeException { | |
public static JwtException invalidAccessToken(String message) { | |
return new JwtException(message, null); | |
} | |
public static JwtException invalidClaims(String message, Throwable cause) { | |
return new JwtException(message, cause); | |
} | |
private JwtException(String message, Throwable cause) { | |
super(message, cause); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.sd.utils; | |
import java.io.IOException; | |
import java.nio.charset.StandardCharsets; | |
import java.time.Instant; | |
import java.util.Base64; | |
import java.util.List; | |
import com.fasterxml.jackson.annotation.JsonProperty; | |
import com.fasterxml.jackson.databind.DeserializationFeature; | |
import com.fasterxml.jackson.databind.json.JsonMapper; | |
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | |
import com.sd.utils.JwtException; | |
import lombok.Getter; | |
public class JwtUtils { | |
private static JsonMapper jsonMapper = JsonMapper.builder() | |
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) | |
.addModule(new JavaTimeModule()) | |
.build(); | |
private JwtUtils() { | |
throw new IllegalStateException("Utility class"); | |
} | |
public static JwtClaims getClaims(String accessToken) { | |
if (accessToken == null) { | |
throw JwtException.invalidAccessToken("Null access token"); | |
} | |
String[] tokens = accessToken.split("\\."); | |
if (tokens.length != 3) { | |
throw JwtException.invalidAccessToken("JWT must have 3 tokens"); | |
} | |
try { | |
byte[] claimsBytesDecoded = Base64.getDecoder().decode(tokens[1].getBytes(StandardCharsets.UTF_8)); | |
return jsonMapper.readValue(claimsBytesDecoded, JwtClaimsImpl.class); | |
} catch (IllegalArgumentException | IOException e) { | |
throw JwtException.invalidClaims("Failed to parse access_token claims", e); | |
} | |
} | |
public static interface JwtClaims { | |
public String getIssuer(); | |
public Instant getExpirationTime(); | |
public Instant getIssuedAt(); | |
public String getSubject(); | |
public List<String> getAudience(); | |
public List<String> getAuthorities(); | |
public List<String> getScopes(); | |
public String getClientId(); | |
public String getGrantType(); | |
} | |
/* | |
* https://tools.ietf.org/html/rfc7519#section-4.1 | |
*/ | |
@Getter | |
public static class JwtClaimsImpl implements JwtClaims { | |
@JsonProperty("iss") | |
private String issuer; | |
@JsonProperty("exp") | |
private Instant expirationTime; | |
@JsonProperty("iat") | |
private Instant issuedAt; | |
@JsonProperty("sub") | |
private String subject; | |
@JsonProperty("aud") | |
private List<String> audience; | |
@JsonProperty("authorities") | |
private List<String> authorities; | |
@JsonProperty("scope") | |
private List<String> scopes; | |
@JsonProperty("client_id") | |
private String clientId; | |
@JsonProperty("grant_type") | |
private String grantType; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.sd.utils.test; | |
import static org.junit.Assert.assertEquals; | |
import static org.junit.Assert.assertNotNull; | |
import static org.junit.jupiter.api.Assertions.assertThrows; | |
import static org.junit.jupiter.api.Assertions.assertTrue; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Modifier; | |
import org.junit.jupiter.api.Test; | |
import com.sd.utils.JwtException; | |
import com.sd.utils.JwtUtils; | |
import com.sd.utils.JwtUtils.JwtClaims; | |
class JwtUtilsTest { | |
@Test | |
public void testConstructorIsPrivate() throws NoSuchMethodException { | |
Constructor<JwtUtils> constructor = JwtUtils.class.getDeclaredConstructor(); | |
assertTrue(Modifier.isPrivate(constructor.getModifiers())); | |
constructor.setAccessible(true); | |
InvocationTargetException ex = assertThrows(InvocationTargetException.class, constructor::newInstance); | |
assertTrue(ex.getCause().getClass().isAssignableFrom(IllegalStateException.class)); | |
} | |
@Test | |
void testGetClaims() { | |
JwtClaims claims = JwtUtils.getClaims("<valid token>"); | |
assertNotNull(claims); | |
assertEquals("http://localhost:8080/uaa/oauth/token", claims.getIssuer()); | |
assertEquals(1588487944, claims.getExpirationTime().getEpochSecond()); | |
assertEquals(1588444744, claims.getIssuedAt().getEpochSecond()); | |
assertTrue(claims.getAudience().contains("uaa")); | |
assertTrue(claims.getAuthorities().contains("uaa.resource")); | |
assertTrue(claims.getScopes().contains("uaa.resource")); | |
assertEquals("client_credentials", claims.getGrantType()); | |
} | |
@Test | |
void testInvalidToken() { | |
assertThrows(JwtException.class, () -> JwtUtils.getClaims(null)); | |
assertThrows(JwtException.class, () -> JwtUtils.getClaims("a.b")); | |
assertThrows(JwtException.class, () -> JwtUtils.getClaims("a.b.c")); | |
assertThrows(JwtException.class, () -> JwtUtils.getClaims("aaa.bbb.ccc")); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment