2323import io .jsonwebtoken .JwtException ;
2424import io .jsonwebtoken .Jwts ;
2525import io .jsonwebtoken .MalformedJwtException ;
26- import io .jsonwebtoken .SigningKeyResolverAdapter ;
2726import io .jsonwebtoken .UnsupportedJwtException ;
2827import io .jsonwebtoken .security .Keys ;
2928import io .jsonwebtoken .security .MacAlgorithm ;
@@ -52,7 +51,6 @@ public class JwtService {
5251 private static final org .slf4j .Logger logger = LoggerFactory .getLogger (JwtService .class );
5352
5453 private static final MacAlgorithm SIGNATURE_ALGORITHM = Jwts .SIG .HS256 ;
55- private static final String KEY_ID_CLAIM = "kid" ;
5654 private static final String USERNAME_CLAIM = "preferred_username" ;
5755 private static final String GROUPS_CLAIM = "groups" ;
5856
@@ -101,27 +99,25 @@ public Set<String> getUserGroupsFromToken(final Jws<Claims> jws) throws JwtExcep
10199
102100 private Jws <Claims > parseTokenFromBase64EncodedString (final String base64EncodedToken ) throws JwtException {
103101 try {
104- return Jwts .parser ().setSigningKeyResolver (new SigningKeyResolverAdapter () {
105- @ Override
106- public byte [] resolveSigningKeyBytes (JwsHeader header , Claims claims ) {
107- final String identity = claims .getSubject ();
102+ return Jwts .parser ().keyLocator (header -> {
103+ if (header instanceof JwsHeader jwsHeader ) {
104+ final String keyId = jwsHeader .getKeyId ();
105+ if (keyId == null ) {
106+ throw new UnsupportedJwtException ("Key Identifier not found in header" );
107+ }
108108
109- // Get the key based on the key id in the claims
110- final String keyId = claims .get (KEY_ID_CLAIM , String .class );
111109 final Key key = keyService .getKey (keyId );
112-
113- // Ensure we were able to find a key that was previously issued by this key service for this user
114110 if (key == null || key .getKey () == null ) {
115- throw new UnsupportedJwtException ("Unable to determine signing key for " + identity + " [kid: " + keyId + "]" );
111+ throw new UnsupportedJwtException ("Signing Key [%s] not found" . formatted ( keyId ) );
116112 }
117-
118- return key .getKey ().getBytes (StandardCharsets .UTF_8 );
113+ final byte [] keyBytes = key .getKey ().getBytes (StandardCharsets .UTF_8 );
114+ return Keys .hmacShaKeyFor (keyBytes );
115+ } else {
116+ throw new UnsupportedJwtException ("JWE is not currently supported" );
119117 }
120118 }).build ().parseSignedClaims (base64EncodedToken );
121119 } catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException e ) {
122- // TODO: Exercise all exceptions to ensure none leak key material to logs
123- final String errorMessage = "Unable to validate the access token." ;
124- throw new JwtException (errorMessage , e );
120+ throw new JwtException ("Access Token validation failed" , e );
125121 }
126122 }
127123
@@ -146,10 +142,6 @@ public String generateSignedToken(final AuthenticationResponse authenticationRes
146142 null );
147143 }
148144
149- public String generateSignedToken (String identity , String preferredUsername , String issuer , String audience , long expirationMillis ) throws JwtException {
150- return this .generateSignedToken (identity , preferredUsername , issuer , audience , expirationMillis , null );
151- }
152-
153145 public String generateSignedToken (String identity , String preferredUsername , String issuer , String audience , long expirationMillis , Collection <String > groups ) throws JwtException {
154146 if (identity == null || StringUtils .isEmpty (identity )) {
155147 String errorMessage = "Cannot generate a JWT for a token with an empty identity" ;
@@ -168,14 +160,16 @@ public String generateSignedToken(String identity, String preferredUsername, Str
168160 // Get/create the key for this user
169161 final Key key = keyService .getOrCreateKey (identity );
170162 final byte [] keyBytes = key .getKey ().getBytes (StandardCharsets .UTF_8 );
163+ final String keyId = key .getId ();
171164
172- // TODO: Implement "jti" claim with nonce to prevent replay attacks and allow blacklisting of revoked tokens
173- // Build the token
174- return Jwts .builder ().subject (identity )
165+ return Jwts .builder ()
166+ .header ()
167+ .keyId (keyId )
168+ .and ()
169+ .subject (identity )
175170 .issuer (issuer )
176171 .audience ().add (audience ).and ()
177172 .claim (USERNAME_CLAIM , preferredUsername )
178- .claim (KEY_ID_CLAIM , key .getId ())
179173 .claim (GROUPS_CLAIM , groups != null ? groups : Collections .EMPTY_LIST )
180174 .issuedAt (now .getTime ())
181175 .expiration (expiration .getTime ())
@@ -216,23 +210,4 @@ private static long validateTokenExpiration(long proposedTokenExpiration, String
216210
217211 return proposedTokenExpiration ;
218212 }
219-
220- private static String describe (AuthenticationResponse authenticationResponse ) {
221- Calendar expirationTime = Calendar .getInstance ();
222- expirationTime .setTimeInMillis (authenticationResponse .getExpiration ());
223- long remainingTime = expirationTime .getTimeInMillis () - Calendar .getInstance ().getTimeInMillis ();
224-
225- return new StringBuilder ("LoginAuthenticationToken for " )
226- .append (authenticationResponse .getUsername ())
227- .append (" issued by " )
228- .append (authenticationResponse .getIssuer ())
229- .append (" expiring at " )
230- .append (expirationTime .getTime ().toInstant ().toString ())
231- .append (" [" )
232- .append (authenticationResponse .getExpiration ())
233- .append (" ms, " )
234- .append (remainingTime )
235- .append (" ms remaining]" )
236- .toString ();
237- }
238213}
0 commit comments