SOLR-14799: JWT authentication plugin only requires sub claim when principalClaim=sub
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index ad5c581..fa4ac2e 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -48,6 +48,8 @@
 
 * SOLR-14722: Track timeAllowed from earlier in the request lifecycle. (David Smiley)
 
+* SOLR-14799: JWT authentication plugin only requires "sub" claim when principalClaim=sub. (Erik Hatcher)
+
 Optimizations
 ---------------------
 
diff --git a/solr/core/src/java/org/apache/solr/security/JWTAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/JWTAuthPlugin.java
index 90c6561..238ac60 100644
--- a/solr/core/src/java/org/apache/solr/security/JWTAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/JWTAuthPlugin.java
@@ -74,7 +74,6 @@
 public class JWTAuthPlugin extends AuthenticationPlugin implements SpecProvider, ConfigEditablePlugin {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   private static final String PARAM_BLOCK_UNKNOWN = "blockUnknown";
-  private static final String PARAM_REQUIRE_SUBJECT = "requireSub";
   private static final String PARAM_REQUIRE_ISSUER = "requireIss";
   private static final String PARAM_PRINCIPAL_CLAIM = "principalClaim";
   private static final String PARAM_ROLES_CLAIM = "rolesClaim";
@@ -95,7 +94,7 @@
   static final String PRIMARY_ISSUER = "PRIMARY";
 
   private static final Set<String> PROPS = ImmutableSet.of(PARAM_BLOCK_UNKNOWN,
-      PARAM_REQUIRE_SUBJECT, PARAM_PRINCIPAL_CLAIM, PARAM_REQUIRE_EXPIRATIONTIME, PARAM_ALG_WHITELIST,
+      PARAM_PRINCIPAL_CLAIM, PARAM_REQUIRE_EXPIRATIONTIME, PARAM_ALG_WHITELIST,
       PARAM_JWK_CACHE_DURATION, PARAM_CLAIMS_MATCH, PARAM_SCOPE, PARAM_REALM, PARAM_ROLES_CLAIM,
       PARAM_ADMINUI_SCOPE, PARAM_REDIRECT_URIS, PARAM_REQUIRE_ISSUER, PARAM_ISSUERS,
       // These keys are supported for now to enable PRIMARY issuer config through top-level keys
@@ -140,10 +139,6 @@
     blockUnknown = Boolean.parseBoolean(String.valueOf(pluginConfig.getOrDefault(PARAM_BLOCK_UNKNOWN, false)));
     requireIssuer = Boolean.parseBoolean(String.valueOf(pluginConfig.getOrDefault(PARAM_REQUIRE_ISSUER, "true")));
     requireExpirationTime = Boolean.parseBoolean(String.valueOf(pluginConfig.getOrDefault(PARAM_REQUIRE_EXPIRATIONTIME, "true")));
-    if (pluginConfig.get(PARAM_REQUIRE_SUBJECT) != null) {
-      log.warn("Parameter {} is no longer used and may generate error in a later version. A subject claim is now always required",
-          PARAM_REQUIRE_SUBJECT);
-    }
     principalClaim = (String) pluginConfig.getOrDefault(PARAM_PRINCIPAL_CLAIM, "sub");
 
     rolesClaim = (String) pluginConfig.get(PARAM_ROLES_CLAIM);
@@ -511,7 +506,6 @@
     } else {
       jwtConsumerBuilder.setSkipDefaultAudienceValidation();
     }
-    jwtConsumerBuilder.setRequireSubject();
     if (requireExpirationTime)
       jwtConsumerBuilder.setRequireExpirationTime();
     if (algWhitelist != null)
diff --git a/solr/core/src/test/org/apache/solr/security/JWTAuthPluginTest.java b/solr/core/src/test/org/apache/solr/security/JWTAuthPluginTest.java
index 95cc59e..cc6a86d 100644
--- a/solr/core/src/test/org/apache/solr/security/JWTAuthPluginTest.java
+++ b/solr/core/src/test/org/apache/solr/security/JWTAuthPluginTest.java
@@ -93,6 +93,7 @@
     claims.unsetClaim("iss");
     claims.unsetClaim("aud");
     claims.unsetClaim("exp");
+    claims.setSubject(null);
     jws.setPayload(claims.toJson());
     String slimJwt = jws.getCompactSerialization();
     slimHeader = "Bearer" + " " + slimJwt;
@@ -127,6 +128,7 @@
 
     testConfig = new HashMap<>();
     testConfig.put("class", "org.apache.solr.security.JWTAuthPlugin");
+    testConfig.put("principalClaim", "customPrincipal");
     testConfig.put("jwk", testJwk);
     plugin.init(testConfig);
     
@@ -216,11 +218,25 @@
   public void authenticateOk() {
     JWTAuthPlugin.JWTAuthenticationResponse resp = plugin.authenticate(testHeader);
     assertTrue(resp.isAuthenticated());
-    assertEquals("solruser", resp.getPrincipal().getName());
+    assertEquals("custom", resp.getPrincipal().getName()); // principalClaim = customPrincipal, not sub here
   }
 
   @Test
   public void authFailedMissingSubject() {
+    minimalConfig.put("principalClaim","sub");  // minimalConfig has no subject specified
+    plugin.init(minimalConfig);
+    JWTAuthPlugin.JWTAuthenticationResponse resp = plugin.authenticate(testHeader);
+    assertFalse(resp.isAuthenticated());
+    assertEquals(JWTAuthPlugin.JWTAuthenticationResponse.AuthCode.JWT_VALIDATION_EXCEPTION, resp.getAuthCode());
+
+    testConfig.put("principalClaim","sub");  // testConfig has subject = solruser
+    plugin.init(testConfig);
+    resp = plugin.authenticate(testHeader);
+    assertTrue(resp.isAuthenticated());
+  }
+
+  @Test
+  public void authFailedMissingIssuer() {
     testConfig.put("iss", "NA");
     plugin.init(testConfig);
     JWTAuthPlugin.JWTAuthenticationResponse resp = plugin.authenticate(testHeader);