22
33import com .nimbusds .jwt .JWTClaimsSet ;
44import com .nimbusds .jwt .JWTParser ;
5+ import org .jspecify .annotations .Nullable ;
6+ import org .slf4j .Logger ;
7+ import org .slf4j .LoggerFactory ;
8+
59import java .text .ParseException ;
610import java .util .Arrays ;
711import java .util .HashSet ;
812import java .util .Set ;
9- import org .slf4j .Logger ;
10- import org .slf4j .LoggerFactory ;
1113
1214/**
1315 * Service for prechecking JWT token scopes before forwarding to the authorization server.
@@ -43,24 +45,41 @@ public JwtScopeService(ScopeDiscoveryService scopeDiscoveryService) {
4345 this .scopeDiscoveryService = scopeDiscoveryService ;
4446 }
4547
48+ /**
49+ * Verifies that the token from authorization header is jwt token and contains all required scopes.
50+ * Tokens must be prefixed with "Bearer". Bearer prefix is case-insensitive and leading/trailing
51+ * whitespace around the token is ignored. Uses the "scope" claim, which is expected to be a
52+ * space-separated string as per RFC 6749.
53+ *
54+ * @param authHeader The Authorization header value
55+ * @return True if all required scopes are present, false otherwise
56+ */
57+ public boolean verifyScopes (String authHeader ) {
58+ var tokenScopes = decodeAndExtractScopesFromJwtToken (authHeader );
59+ var requiredScopes = scopeDiscoveryService .getDiscoveredScopes ();
60+ return tokenScopes .containsAll (requiredScopes );
61+ }
62+
4663 /**
4764 * Decodes a JWT token from the Authorization header. Tokens must be prefixed with "Bearer".
4865 * Bearer prefix is case-insensitive and leading/trailing whitespace around the token is ignored.
4966 *
5067 * @param authHeader The Authorization header value
51- * @return The decoded JWT claims , or null if decoding fails
68+ * @return A set of scopes extracted from the token , or an empty set if decoding fails
5269 */
53- public JWTClaimsSet decodeJwtToken (String authHeader ) {
70+ private Set < String > decodeAndExtractScopesFromJwtToken (String authHeader ) {
5471 if (authHeader == null || !authHeader .regionMatches (true , 0 , "Bearer " , 0 , 7 )) {
55- return null ;
72+ return Set . of () ;
5673 }
5774
5875 var token = authHeader .substring (7 ).trim ();
5976 try {
60- return JWTParser .parse (token ).getJWTClaimsSet ();
61- } catch (Exception e ) {
62- return null ;
77+ var claimsSet = JWTParser .parse (token ).getJWTClaimsSet ();
78+ return extractScopes (claimsSet );
79+ } catch (ParseException e ) {
80+ LOGGER .debug ("No 'scope' claim found in JWT token." , e );
6381 }
82+ return Set .of ();
6483 }
6584
6685 /**
@@ -70,29 +89,17 @@ public JWTClaimsSet decodeJwtToken(String authHeader) {
7089 * @param claims The JWT claims
7190 * @return A set of scopes extracted from the "scope" claim
7291 */
73- public Set <String > extractScopes (JWTClaimsSet claims ) {
74- var scopes = new HashSet <String >();
92+ private Set <String > extractScopes (@ Nullable JWTClaimsSet claims ) throws ParseException {
93+ if (claims == null ) {
94+ return Set .of ();
95+ }
7596
76- try {
77- var scopeString = claims .getStringClaim ("scope" );
78- if (scopeString != null && !scopeString .isBlank ()) {
79- scopes .addAll (Arrays .asList (scopeString .split (" " )));
80- }
81- } catch (ParseException e ) {
82- LOGGER .debug ("No 'scope' claim found in JWT token." , e );
97+ var scopeString = claims .getStringClaim ("scope" );
98+ if (scopeString != null && !scopeString .isBlank ()) {
99+ return new HashSet <>(Arrays .asList (scopeString .split (" " )));
83100 }
84101
85- return scopes ;
102+ return Set . of () ;
86103 }
87104
88- /**
89- * Verifies that the token scopes include all required scopes.
90- *
91- * @param tokenScopes The scopes extracted from the JWT token
92- * @return True if all required scopes are present, false otherwise
93- */
94- public boolean verifyScopes (Set <String > tokenScopes ) {
95- var requiredScopes = scopeDiscoveryService .getDiscoveredScopes ();
96- return tokenScopes .containsAll (requiredScopes );
97- }
98105}
0 commit comments