Copilot commented on code in PR #4334:
URL: https://github.com/apache/solr/pull/4334#discussion_r3142270928


##########
solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java:
##########
@@ -561,150 +566,195 @@ protected JWTAuthenticationResponse authenticate(String 
authorizationHeader) {
       String jwtCompact = parseAuthorizationHeader(authorizationHeader);
       if (jwtCompact != null) {
         try {
+          // Pre-parse JWT (without signature verification) to get issuer and 
check algorithm
+          SignedJWT signedJWT;
           try {
-            JwtClaims jwtClaims = jwtConsumer.processToClaims(jwtCompact);
-            String principal = jwtClaims.getStringClaimValue(principalClaim);
-            if (principal == null || principal.isEmpty()) {
-              return new JWTAuthenticationResponse(
-                  AuthCode.PRINCIPAL_MISSING,
-                  "Cannot identify principal from JWT. Required claim "
-                      + principalClaim
-                      + " missing. Cannot authenticate");
+            signedJWT = SignedJWT.parse(jwtCompact);
+          } catch (ParseException e) {
+            if (e.getMessage() != null
+                && e.getMessage().contains("Invalid JOSE Compact 
Serialization")) {
+              return new JWTAuthenticationResponse(AuthCode.JWT_PARSE_ERROR, 
e.getMessage());
             }
-            if (claimsMatchCompiled != null) {
-              for (Map.Entry<String, Pattern> entry : 
claimsMatchCompiled.entrySet()) {
-                String claim = entry.getKey();
-                if (jwtClaims.hasClaim(claim)) {
-                  Object claimValue = jwtClaims.getClaimValue(claim);
-                  String claimValueStr = (claimValue != null) ? 
String.valueOf(claimValue) : "";
-                  if (!entry.getValue().matcher(claimValueStr).matches()) {
-                    return new JWTAuthenticationResponse(
-                        AuthCode.CLAIM_MISMATCH,
-                        "Claim "
-                            + claim
-                            + "="
-                            + claimValueStr
-                            + " does not match required regular expression "
-                            + entry.getValue().pattern());
-                  }
-                } else {
-                  return new JWTAuthenticationResponse(
-                      AuthCode.CLAIM_MISMATCH,
-                      "Claim " + claim + " is required but does not exist in 
JWT");
-                }
-              }
+            return new JWTAuthenticationResponse(
+                AuthCode.JWT_PARSE_ERROR, "Failed to parse JWT: " + 
e.getMessage());
+          }
+
+          // Check algorithm allowlist before full processing
+          if (algAllowlist != null
+              && 
!algAllowlist.contains(signedJWT.getHeader().getAlgorithm().getName())) {
+            return new JWTAuthenticationResponse(
+                AuthCode.JWT_VALIDATION_EXCEPTION,
+                new RuntimeException(
+                    "Algorithm "
+                        + signedJWT.getHeader().getAlgorithm()
+                        + " is not a permitted algorithm"));
+          }
+
+          // Extract unverified issuer to pass to key selector via context
+          String tokenIssuer;
+          try {
+            tokenIssuer = signedJWT.getJWTClaimsSet().getIssuer();
+          } catch (ParseException e) {
+            return new JWTAuthenticationResponse(
+                AuthCode.JWT_PARSE_ERROR, "Malformed claim: " + 
e.getMessage());
+          }
+
+          JWTClaimsSet jwtClaims;
+          try {
+            // Use the already-parsed SignedJWT to avoid redundant parsing and 
ParseException
+            jwtClaims =
+                jwtProcessor.process(
+                    signedJWT, new 
IssuerAwareJWSKeySelector.IssuerContext(tokenIssuer));
+          } catch (BadJWSException e) {
+            return new JWTAuthenticationResponse(AuthCode.SIGNATURE_INVALID, 
e);
+          } catch (BadJWTException e) {
+            String msg = e.getMessage();
+            if (msg != null && (msg.startsWith("Expired JWT") || 
msg.contains("expir"))) {
+              return new JWTAuthenticationResponse(
+                  AuthCode.JWT_EXPIRED, "Authentication failed due to expired 
JWT token. " + msg);
             }
-            if (!requiredScopes.isEmpty() && !jwtClaims.hasClaim(CLAIM_SCOPE)) 
{
-              // Fail if we require scopes but they don't exist
+            return new 
JWTAuthenticationResponse(AuthCode.JWT_VALIDATION_EXCEPTION, e);
+          } catch (BadJOSEException e) {
+            return new 
JWTAuthenticationResponse(AuthCode.JWT_VALIDATION_EXCEPTION, e);
+          } catch (JOSEException e) {
+            return new 
JWTAuthenticationResponse(AuthCode.JWT_VALIDATION_EXCEPTION, e);
+          }
+
+          // Validate issuer value against configured issuers (only when iss 
is explicitly
+          // configured)
+          if (requireIssuer
+              && tokenIssuer != null
+              && issuerConfigs.stream().anyMatch(ic -> ic.getIss() != null)) {
+            boolean issuerMatched =
+                issuerConfigs.stream()
+                    .anyMatch(ic -> ic.getIss() != null && 
ic.getIss().equals(tokenIssuer));
+            if (!issuerMatched) {

Review Comment:
   Issuer value checking is currently gated on `requireIssuer` (and 
`tokenIssuer != null`). With `requireIss=false`, a token that *does* contain an 
`iss` claim that doesn't match any configured issuer can still be accepted 
(especially when exactly one issuer is configured, since the key selector falls 
back to that issuer). Previously, `requireIss` meant the claim was optional, 
not that mismatching issuer values should be accepted when present. Consider 
validating `iss` whenever at least one configured issuer has a non-null `iss` 
(regardless of `requireIssuer`), only allowing a missing `iss` when 
`requireIssuer` is false and a single issuer is configured.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to