This is an automated email from the ASF dual-hosted git repository.

gerlowskija pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new bf0b2b0d30c SOLR-15738: Convert v2 security APIs to annotations (#897)
bf0b2b0d30c is described below

commit bf0b2b0d30c69526efdffa7010214e6f7ad3e46d
Author: Jason Gerlowski <[email protected]>
AuthorDate: Tue Jun 21 11:42:51 2022 -0400

    SOLR-15738: Convert v2 security APIs to annotations (#897)
    
    Solr's been in the slow process of moving its v2 APIs away from the
    existing apispec/mapping framework towards one that relies on more
    explicit annotations to specify API properties.
    
    This commit converts the APIs from the cluster.security.authentication
    and cluster.security.authorization apispec files to the "new" framework.
---
 .../src/java/org/apache/solr/api/AnnotatedApi.java |  29 ++++
 solr/core/src/java/org/apache/solr/api/ApiBag.java |  19 ++-
 .../solr/handler/admin/SecurityConfHandler.java    |  28 ++--
 .../admin/api/GetAuthenticationConfigAPI.java      |  50 +++++++
 .../admin/api/GetAuthorizationConfigAPI.java       |  49 +++++++
 .../admin/api/ModifyBasicAuthConfigAPI.java        |  47 +++++++
 .../admin/api/ModifyMultiPluginAuthConfigAPI.java  |  57 ++++++++
 .../api/ModifyNoAuthPluginSecurityConfigAPI.java   |  50 +++++++
 .../api/ModifyNoAuthzPluginSecurityConfigAPI.java  |  50 +++++++
 .../admin/api/ModifyRuleBasedAuthConfigAPI.java    |  64 +++++++++
 .../org/apache/solr/security/MultiAuthPlugin.java  |   7 +-
 .../security/RuleBasedAuthorizationPluginBase.java |   7 +-
 .../security/Sha256AuthenticationProvider.java     |   7 +-
 .../org/apache/solr/cloud/TestConfigSetsAPI.java   |   4 +-
 .../handler/admin/V2SecurityAPIMappingTest.java    | 154 +++++++++++++++++++++
 .../admin/api/ModifyJWTAuthPluginConfigAPI.java    |  39 ++++++
 .../solr/handler/admin/api/package-info.java       |  18 +++
 .../apache/solr/security/jwt/JWTAuthPlugin.java    |   6 +-
 .../admin/api/V2JWTSecurityApiMappingTest.java     |  65 +++++++++
 .../solr/handler/admin/api/package-info.java       |  19 +++
 .../request/beans/DeleteBasicAuthUserPayload.java  |  22 +++
 .../beans/SetRuleBasedAuthPermissionPayload.java   |  43 ++++++
 .../UpdateRuleBasedAuthPermissionPayload.java      |  44 ++++++
 .../handler/admin/api/JWTConfigurationPayload.java |  87 ++++++++++++
 .../solr/handler/admin/api/package-info.java       |  19 +++
 .../cluster.security.BasicAuth.Commands.json       |  23 ---
 .../apispec/cluster.security.JwtAuth.Commands.json |  18 ---
 .../cluster.security.MultiPluginAuth.Commands.json |  27 ----
 .../cluster.security.RuleBasedAuthorization.json   | 129 -----------------
 .../cluster.security.authentication.Commands.json  |  12 --
 .../apispec/cluster.security.authentication.json   |  12 --
 .../cluster.security.authorization.Commands.json   |  13 --
 .../apispec/cluster.security.authorization.json    |  13 --
 .../apache/solr/common/util/JsonValidatorTest.java |  21 ---
 34 files changed, 965 insertions(+), 287 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java 
b/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java
index 905157cf71a..468e5cbec95 100644
--- a/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java
+++ b/solr/core/src/java/org/apache/solr/api/AnnotatedApi.java
@@ -34,6 +34,8 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SpecProvider;
@@ -335,6 +337,33 @@ public class AnnotatedApi extends Api implements 
PermissionNameProvider, Closeab
       }
     }
 
+    public int hashCode() {
+      return new HashCodeBuilder()
+          .append(command)
+          .append(method)
+          .append(obj)
+          .append(paramsCount)
+          .append(parameterClass)
+          .append(isWrappedInPayloadObj)
+          .toHashCode();
+    }
+
+    public boolean equals(Object rhs) {
+      if (null == rhs) return false;
+      if (this == rhs) return true;
+      if (getClass() != rhs.getClass()) return false;
+
+      final Cmd rhsCast = (Cmd) rhs;
+      return new EqualsBuilder()
+          .append(command, rhsCast.command)
+          .append(method, rhsCast.method)
+          .append(obj, rhsCast.obj)
+          .append(paramsCount, rhsCast.paramsCount)
+          .append(parameterClass, rhsCast.parameterClass)
+          .append(isWrappedInPayloadObj, rhsCast.isWrappedInPayloadObj)
+          .isEquals();
+    }
+
     private void checkForErrorInPayload(CommandOperation cmd) {
       if (cmd.hasError()) {
         throw new ApiBag.ExceptionWithErrObject(
diff --git a/solr/core/src/java/org/apache/solr/api/ApiBag.java 
b/solr/core/src/java/org/apache/solr/api/ApiBag.java
index 15ed546add7..3d0337670d1 100644
--- a/solr/core/src/java/org/apache/solr/api/ApiBag.java
+++ b/solr/core/src/java/org/apache/solr/api/ApiBag.java
@@ -25,6 +25,7 @@ import static 
org.apache.solr.common.util.ValidatingJsonMap.NOT_NULL;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
@@ -148,16 +149,28 @@ public class ApiBag {
     private Collection<AnnotatedApi> combinedApis;
 
     protected CommandAggregatingAnnotatedApi(AnnotatedApi api) {
-      super(api.spec, api.getEndPoint(), api.getCommands(), null);
+      super(api.spec, api.getEndPoint(), Maps.newHashMap(api.getCommands()), 
null);
       combinedApis = Lists.newArrayList();
     }
 
     public void combineWith(AnnotatedApi api) {
       // Merge in new 'command' entries
-      getCommands().putAll(api.getCommands());
+      boolean newCommandsAdded = false;
+      for (Map.Entry<String, AnnotatedApi.Cmd> entry : 
api.getCommands().entrySet()) {
+        // Skip registering command if it's identical to an already registered 
command.
+        if (getCommands().containsKey(entry.getKey())
+            && getCommands().get(entry.getKey()).equals(entry.getValue())) {
+          continue;
+        }
+
+        newCommandsAdded = true;
+        getCommands().put(entry.getKey(), entry.getValue());
+      }
 
       // Reference to Api must be saved to to merge uncached values (i.e. 
'spec') lazily
-      combinedApis.add(api);
+      if (newCommandsAdded) {
+        combinedApis.add(api);
+      }
     }
 
     @Override
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
index 3108a2132ef..eda280a98c8 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
@@ -29,6 +29,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import org.apache.solr.api.AnnotatedApi;
 import org.apache.solr.api.Api;
 import org.apache.solr.api.ApiBag;
 import org.apache.solr.api.ApiBag.ReqHandlerToApi;
@@ -41,6 +42,10 @@ import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.RequestHandlerUtils;
+import org.apache.solr.handler.admin.api.GetAuthenticationConfigAPI;
+import org.apache.solr.handler.admin.api.GetAuthorizationConfigAPI;
+import org.apache.solr.handler.admin.api.ModifyNoAuthPluginSecurityConfigAPI;
+import org.apache.solr.handler.admin.api.ModifyNoAuthzPluginSecurityConfigAPI;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.security.AuthenticationPlugin;
@@ -277,20 +282,27 @@ public abstract class SecurityConfHandler extends 
RequestHandlerBase
       synchronized (this) {
         if (apis == null) {
           Collection<Api> apis = new ArrayList<>();
-          final SpecProvider authcCommands =
-              Utils.getSpec("cluster.security.authentication.Commands");
-          final SpecProvider authzCommands =
-              Utils.getSpec("cluster.security.authorization.Commands");
-          apis.add(new ReqHandlerToApi(this, 
Utils.getSpec("cluster.security.authentication")));
-          apis.add(new ReqHandlerToApi(this, 
Utils.getSpec("cluster.security.authorization")));
+          // GET Apis are the same regardless of which plugins are registered
+          apis.addAll(AnnotatedApi.getApis(new 
GetAuthenticationConfigAPI(this)));
+          apis.addAll(AnnotatedApi.getApis(new 
GetAuthorizationConfigAPI(this)));
+
+          // POST Apis come from the specific authc/z plugin registered (with 
a fallback used if
+          // the plugin isn't a SpecProvider).
+          final Api defaultAuthcApi =
+              AnnotatedApi.getApis(new 
ModifyNoAuthPluginSecurityConfigAPI(this)).get(0);
+          final Api defaultAuthzApi =
+              AnnotatedApi.getApis(new 
ModifyNoAuthzPluginSecurityConfigAPI(this)).get(0);
+
           SpecProvider authcSpecProvider =
               () -> {
                 AuthenticationPlugin authcPlugin = 
cores.getAuthenticationPlugin();
                 return authcPlugin != null && authcPlugin instanceof 
SpecProvider
                     ? ((SpecProvider) authcPlugin).getSpec()
-                    : authcCommands.getSpec();
+                    : defaultAuthcApi.getSpec();
               };
 
+          // TODO Can we remove this extra ReqHandlerToApi wrapping - nothing 
but the schema from
+          // the POST authc/authz is getting used.
           apis.add(
               new ReqHandlerToApi(this, authcSpecProvider) {
                 @Override
@@ -309,7 +321,7 @@ public abstract class SecurityConfHandler extends 
RequestHandlerBase
                 AuthorizationPlugin authzPlugin = 
cores.getAuthorizationPlugin();
                 return authzPlugin != null && authzPlugin instanceof 
SpecProvider
                     ? ((SpecProvider) authzPlugin).getSpec()
-                    : authzCommands.getSpec();
+                    : defaultAuthzApi.getSpec();
               };
           apis.add(
               new ApiBag.ReqHandlerToApi(this, authzSpecProvider) {
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/GetAuthenticationConfigAPI.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/GetAuthenticationConfigAPI.java
new file mode 100644
index 00000000000..9a7b7947689
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/GetAuthenticationConfigAPI.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.SECURITY_READ_PERM;
+
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.handler.admin.SecurityConfHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+/**
+ * V2 API for fetching the authentication section from Solr's security.json 
configuration.
+ *
+ * <p>This API (GET /v2/cluster/security/authentication) is analogous to the 
v1 `GET
+ * /solr/admin/authentication` API.
+ */
+public class GetAuthenticationConfigAPI {
+
+  private final SecurityConfHandler securityConfHandler;
+
+  public GetAuthenticationConfigAPI(SecurityConfHandler securityConfHandler) {
+    this.securityConfHandler = securityConfHandler;
+  }
+
+  @EndPoint(
+      path = {"/cluster/security/authentication"},
+      method = GET,
+      permission = SECURITY_READ_PERM)
+  public void fetchAuthenticationConfig(SolrQueryRequest req, 
SolrQueryResponse rsp)
+      throws Exception {
+    securityConfHandler.handleRequestBody(req, rsp);
+  }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/GetAuthorizationConfigAPI.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/GetAuthorizationConfigAPI.java
new file mode 100644
index 00000000000..aa4edbdf1e2
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/GetAuthorizationConfigAPI.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.SECURITY_READ_PERM;
+
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.handler.admin.SecurityConfHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+/**
+ * V2 API for fetching the authorization section of Solr's security.json 
configuration.
+ *
+ * <p>This API (GET /v2/cluster/security/authorization) is analogous to the v1 
`GET
+ * /solr/admin/authorization` API.
+ */
+public class GetAuthorizationConfigAPI {
+  private final SecurityConfHandler securityConfHandler;
+
+  public GetAuthorizationConfigAPI(SecurityConfHandler securityConfHandler) {
+    this.securityConfHandler = securityConfHandler;
+  }
+
+  @EndPoint(
+      path = {"/cluster/security/authorization"},
+      method = GET,
+      permission = SECURITY_READ_PERM)
+  public void fetchAuthorizationConfig(SolrQueryRequest req, SolrQueryResponse 
rsp)
+      throws Exception {
+    securityConfHandler.handleRequestBody(req, rsp);
+  }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyBasicAuthConfigAPI.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyBasicAuthConfigAPI.java
new file mode 100644
index 00000000000..dd7e488b43d
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyBasicAuthConfigAPI.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.SECURITY_EDIT_PERM;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.solr.api.Command;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.api.PayloadObj;
+
+/** V2 API to modify configuration for Solr's {@link 
org.apache.solr.security.BasicAuthPlugin} */
+@EndPoint(
+    path = {"/cluster/security/authentication"},
+    method = POST,
+    permission = SECURITY_EDIT_PERM)
+public class ModifyBasicAuthConfigAPI {
+
+  @Command(name = "set-user")
+  public void createUsers(PayloadObj<Map<String, String>> 
usersToCreatePayload) {
+    // Method stub used only to produce v2 API spec; implementation/body empty.
+    throw new IllegalStateException();
+  }
+
+  @Command(name = "delete-user")
+  public void deleteUsers(PayloadObj<List<String>> usersToDeletePayload) {
+    // Method stub used only to produce v2 API spec; implementation/body empty.
+    throw new IllegalStateException();
+  }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyMultiPluginAuthConfigAPI.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyMultiPluginAuthConfigAPI.java
new file mode 100644
index 00000000000..b9a19eb1ce1
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyMultiPluginAuthConfigAPI.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.SECURITY_EDIT_PERM;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.solr.api.Command;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.api.PayloadObj;
+import org.apache.solr.security.MultiAuthPlugin;
+
+/** V2 API to modify configuration options for the {@link MultiAuthPlugin}. */
+@EndPoint(
+    path = {"/cluster/security/authentication"},
+    method = POST,
+    permission = SECURITY_EDIT_PERM)
+public class ModifyMultiPluginAuthConfigAPI {
+
+  @Command(name = "set-user")
+  public void createUsers(PayloadObj<Map<String, String>> 
usersToCreatePayload) {
+    // Method stub used only to produce v2 API spec; implementation/body empty.
+    throw new IllegalStateException();
+  }
+
+  @Command(name = "delete-user")
+  public void deleteUsers(PayloadObj<List<String>> usersToDeletePayload) {
+    // Method stub used only to produce v2 API spec; implementation/body empty.
+    throw new IllegalStateException();
+  }
+
+  // TODO This is a good candidate to create an actual Payload class instead 
of relying on the
+  // generic Map, if someone understands what syntax this API actually takes.  
The apispec
+  // this code was mirrored from is vague, and the ref-guide has no examples 
afaict.
+  @Command(name = "set-property")
+  public void setProperties(PayloadObj<Map<String, Object>> propertyPayload) {
+    // Method stub used only to produce v2 API spec; implementation/body empty.
+    throw new IllegalStateException();
+  }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyNoAuthPluginSecurityConfigAPI.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyNoAuthPluginSecurityConfigAPI.java
new file mode 100644
index 00000000000..031410e2a46
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyNoAuthPluginSecurityConfigAPI.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.SECURITY_EDIT_PERM;
+
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.handler.admin.SecurityConfHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+/**
+ * V2 API for POST requests received when no authentication plugin is active.
+ *
+ * <p>Solr's security APIs only supports authc config modifications once an 
Authentication plugin is
+ * in place. So this API serves solely as a placeholder that allows {@link 
SecurityConfHandler} to
+ * return a helpful error message (instead of the opaque 404 that users would 
get without this API).
+ */
+public class ModifyNoAuthPluginSecurityConfigAPI {
+  private final SecurityConfHandler securityConfHandler;
+
+  public ModifyNoAuthPluginSecurityConfigAPI(SecurityConfHandler 
securityConfHandler) {
+    this.securityConfHandler = securityConfHandler;
+  }
+
+  @EndPoint(
+      path = {"/cluster/security/authentication"},
+      method = POST,
+      permission = SECURITY_EDIT_PERM)
+  public void updateAuthenticationConfig(SolrQueryRequest req, 
SolrQueryResponse rsp)
+      throws Exception {
+    securityConfHandler.handleRequestBody(req, rsp);
+  }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyNoAuthzPluginSecurityConfigAPI.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyNoAuthzPluginSecurityConfigAPI.java
new file mode 100644
index 00000000000..c90c5fd2b02
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyNoAuthzPluginSecurityConfigAPI.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.SECURITY_EDIT_PERM;
+
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.handler.admin.SecurityConfHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+/**
+ * V2 API for POST requests received when no authorization plugin is active.
+ *
+ * <p>Solr's security APIs only supports authz config modifications once an 
Authorization plugin is
+ * in place. So this API serves solely as a placeholder that allows {@link 
SecurityConfHandler} to
+ * return a helpful error message (instead of the opaque 404 that users would 
get without this API).
+ */
+public class ModifyNoAuthzPluginSecurityConfigAPI {
+  private final SecurityConfHandler securityConfHandler;
+
+  public ModifyNoAuthzPluginSecurityConfigAPI(SecurityConfHandler 
securityConfHandler) {
+    this.securityConfHandler = securityConfHandler;
+  }
+
+  @EndPoint(
+      path = {"/cluster/security/authorization"},
+      method = POST,
+      permission = SECURITY_EDIT_PERM)
+  public void updateAuthorizationConfig(SolrQueryRequest req, 
SolrQueryResponse rsp)
+      throws Exception {
+    securityConfHandler.handleRequestBody(req, rsp);
+  }
+}
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyRuleBasedAuthConfigAPI.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyRuleBasedAuthConfigAPI.java
new file mode 100644
index 00000000000..03ba29bae73
--- /dev/null
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyRuleBasedAuthConfigAPI.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.SECURITY_EDIT_PERM;
+
+import java.util.Map;
+import org.apache.solr.api.Command;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.api.PayloadObj;
+import 
org.apache.solr.client.solrj.request.beans.SetRuleBasedAuthPermissionPayload;
+import 
org.apache.solr.client.solrj.request.beans.UpdateRuleBasedAuthPermissionPayload;
+
+/**
+ * V2 API to modify configuration options for the {@link
+ * org.apache.solr.security.RuleBasedAuthorizationPlugin}.
+ */
+@EndPoint(
+    path = {"/cluster/security/authorization"},
+    method = POST,
+    permission = SECURITY_EDIT_PERM)
+public class ModifyRuleBasedAuthConfigAPI {
+
+  @Command(name = "set-permission")
+  public void setPermission(PayloadObj<SetRuleBasedAuthPermissionPayload> 
permissionPayloadObj) {
+    // Method stub used only to produce v2 API spec; implementation/body empty.
+    throw new IllegalStateException();
+  }
+
+  @Command(name = "update-permission")
+  public void updatePermission(
+      PayloadObj<UpdateRuleBasedAuthPermissionPayload> permissionPayloadObj) {
+    // Method stub used only to produce v2 API spec; implementation/body empty.
+    throw new IllegalStateException();
+  }
+
+  @Command(name = "delete-permission")
+  public void deletePermission(PayloadObj<Integer> indexToDeletePayloadObj) {
+    // Method stub used only to produce v2 API spec; implementation/body empty.
+    throw new IllegalStateException();
+  }
+
+  @Command(name = "set-user-role")
+  public void setUserRole(PayloadObj<Map<String, Object>> 
userRoleMapPayloadObj) {
+    // Method stub used only to produce v2 API spec; implementation/body empty.
+    throw new IllegalStateException();
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java 
b/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
index e82f8b9c549..9281f88023c 100644
--- a/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/MultiAuthPlugin.java
@@ -29,14 +29,16 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.http.HttpRequest;
 import org.apache.http.protocol.HttpContext;
 import org.apache.lucene.util.ResourceLoader;
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.SpecProvider;
 import org.apache.solr.common.StringUtils;
 import org.apache.solr.common.util.CommandOperation;
-import org.apache.solr.common.util.Utils;
 import org.apache.solr.common.util.ValidatingJsonMap;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.handler.admin.api.ModifyMultiPluginAuthConfigAPI;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.eclipse.jetty.client.api.Request;
 
@@ -266,7 +268,8 @@ public class MultiAuthPlugin extends AuthenticationPlugin
 
   @Override
   public ValidatingJsonMap getSpec() {
-    return 
Utils.getSpec("cluster.security.MultiPluginAuth.Commands").getSpec();
+    final List<Api> apis = AnnotatedApi.getApis(new 
ModifyMultiPluginAuthConfigAPI());
+    return apis.get(0).getSpec();
   }
 
   @Override
diff --git 
a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java
 
b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java
index 73ad414f2e8..fc917cad38a 100644
--- 
a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java
+++ 
b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java
@@ -32,10 +32,12 @@ import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
 import org.apache.solr.common.SpecProvider;
 import org.apache.solr.common.util.CommandOperation;
-import org.apache.solr.common.util.Utils;
 import org.apache.solr.common.util.ValidatingJsonMap;
+import org.apache.solr.handler.admin.api.ModifyRuleBasedAuthConfigAPI;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -397,6 +399,7 @@ public abstract class RuleBasedAuthorizationPluginBase
 
   @Override
   public ValidatingJsonMap getSpec() {
-    return Utils.getSpec("cluster.security.RuleBasedAuthorization").getSpec();
+    final List<Api> apis = AnnotatedApi.getApis(new 
ModifyRuleBasedAuthConfigAPI());
+    return apis.get(0).getSpec();
   }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java 
b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
index ba7dd6e2968..e1aebf28c55 100644
--- 
a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
+++ 
b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
@@ -30,9 +30,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
 import org.apache.solr.common.util.CommandOperation;
-import org.apache.solr.common.util.Utils;
 import org.apache.solr.common.util.ValidatingJsonMap;
+import org.apache.solr.handler.admin.api.ModifyBasicAuthConfigAPI;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -172,7 +174,8 @@ public class Sha256AuthenticationProvider
 
   @Override
   public ValidatingJsonMap getSpec() {
-    return Utils.getSpec("cluster.security.BasicAuth.Commands").getSpec();
+    final List<Api> apis = AnnotatedApi.getApis(new 
ModifyBasicAuthConfigAPI());
+    return apis.get(0).getSpec();
   }
 
   // TODO make private?
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java 
b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
index e4b00b88be1..45c33f19d9a 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
@@ -66,6 +66,7 @@ import org.apache.http.entity.ByteArrayEntity;
 import org.apache.http.message.BasicHeader;
 import org.apache.http.util.EntityUtils;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.api.AnnotatedApi;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.BaseHttpSolrClient;
@@ -94,6 +95,7 @@ import org.apache.solr.common.util.ValidatingJsonMap;
 import org.apache.solr.core.ConfigSetProperties;
 import org.apache.solr.core.ConfigSetService;
 import org.apache.solr.core.TestSolrConfigHandler;
+import org.apache.solr.handler.admin.api.ModifyBasicAuthConfigAPI;
 import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.security.AuthorizationPlugin;
 import org.apache.solr.security.AuthorizationResponse;
@@ -1880,7 +1882,7 @@ public class TestConfigSetsAPI extends SolrCloudTestCase {
 
         @Override
         public ValidatingJsonMap getSpec() {
-          return 
Utils.getSpec("cluster.security.BasicAuth.Commands").getSpec();
+          return AnnotatedApi.getApis(new 
ModifyBasicAuthConfigAPI()).get(0).getSpec();
         }
 
         @Override
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/V2SecurityAPIMappingTest.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/V2SecurityAPIMappingTest.java
new file mode 100644
index 00000000000..1cc621a74f4
--- /dev/null
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/V2SecurityAPIMappingTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin;
+
+import java.util.HashMap;
+import java.util.Set;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.apache.solr.handler.admin.api.GetAuthenticationConfigAPI;
+import org.apache.solr.handler.admin.api.GetAuthorizationConfigAPI;
+import org.apache.solr.handler.admin.api.ModifyBasicAuthConfigAPI;
+import org.apache.solr.handler.admin.api.ModifyMultiPluginAuthConfigAPI;
+import org.apache.solr.handler.admin.api.ModifyNoAuthPluginSecurityConfigAPI;
+import org.apache.solr.handler.admin.api.ModifyNoAuthzPluginSecurityConfigAPI;
+import org.apache.solr.handler.admin.api.ModifyRuleBasedAuthConfigAPI;
+import org.junit.Before;
+import org.junit.Test;
+
+public class V2SecurityAPIMappingTest extends SolrTestCaseJ4 {
+
+  private ApiBag apiBag;
+  private SecurityConfHandler mockHandler;
+
+  @Before
+  public void setupApiBag() {
+    apiBag = new ApiBag(false);
+    mockHandler = null;
+  }
+
+  // Test the GET /cluster/security/[authentication|authorization] APIs 
registered regardless of
+  // security plugin
+  @Test
+  public void testMappingForUniversalGetApis() {
+    apiBag.registerObject(new GetAuthenticationConfigAPI(mockHandler));
+    apiBag.registerObject(new GetAuthorizationConfigAPI(mockHandler));
+
+    assertAnnotatedApiExistsFor("GET", "/cluster/security/authentication");
+    assertAnnotatedApiExistsFor("GET", "/cluster/security/authorization");
+  }
+
+  // Test the POST /cluster/security/[authentication|authorization] APIs 
registered when no plugins
+  // are configured.
+  @Test
+  public void testDefaultPostAPIs() {
+    apiBag.registerObject(new 
ModifyNoAuthPluginSecurityConfigAPI(mockHandler));
+    apiBag.registerObject(new 
ModifyNoAuthzPluginSecurityConfigAPI(mockHandler));
+
+    // Authc API
+    final AnnotatedApi updateAuthcConfig =
+        assertAnnotatedApiExistsFor("POST", 
"/cluster/security/authentication");
+    assertEquals(1, updateAuthcConfig.getCommands().size());
+    // empty-string is the placeholder for POST APIs without an explicit 
command.
+    assertEquals("", 
updateAuthcConfig.getCommands().keySet().iterator().next());
+
+    // Authz API
+    final AnnotatedApi updateAuthzConfig =
+        assertAnnotatedApiExistsFor("POST", "/cluster/security/authorization");
+    assertEquals(1, updateAuthzConfig.getCommands().size());
+    // empty-string is the placeholder for POST APIs without an explicit 
command.
+    assertEquals("", 
updateAuthzConfig.getCommands().keySet().iterator().next());
+  }
+
+  @Test
+  public void testBasicAuthApiMapping() {
+    apiBag.registerObject(new ModifyBasicAuthConfigAPI());
+
+    final AnnotatedApi updateAuthcConfig =
+        assertAnnotatedApiExistsFor("POST", 
"/cluster/security/authentication");
+    assertEquals(2, updateAuthcConfig.getCommands().size());
+    final Set<String> commandNames = updateAuthcConfig.getCommands().keySet();
+    assertTrue(
+        "Expected 'set-user' to be in the command list: " + commandNames,
+        commandNames.contains("set-user"));
+    assertTrue(
+        "Expected 'delete-user' to be in the command list: " + commandNames,
+        commandNames.contains("delete-user"));
+  }
+
+  @Test
+  public void testMultiAuthApiMapping() {
+    apiBag.registerObject(new ModifyMultiPluginAuthConfigAPI());
+
+    final AnnotatedApi updateAuthcConfig =
+        assertAnnotatedApiExistsFor("POST", 
"/cluster/security/authentication");
+    assertEquals(3, updateAuthcConfig.getCommands().size());
+    final Set<String> commandNames = updateAuthcConfig.getCommands().keySet();
+    assertTrue(
+        "Expected 'set-user' to be in the command list: " + commandNames,
+        commandNames.contains("set-user"));
+    assertTrue(
+        "Expected 'delete-user' to be in the command list: " + commandNames,
+        commandNames.contains("delete-user"));
+    assertTrue(
+        "Expected 'set-property' to be in the command list: " + commandNames,
+        commandNames.contains("set-property"));
+  }
+
+  @Test
+  public void testRuleBasedAuthzApiMapping() {
+    apiBag.registerObject(new ModifyRuleBasedAuthConfigAPI());
+
+    final AnnotatedApi updateAuthzConfig =
+        assertAnnotatedApiExistsFor("POST", "/cluster/security/authorization");
+    assertEquals(4, updateAuthzConfig.getCommands().size());
+    final Set<String> commandNames = updateAuthzConfig.getCommands().keySet();
+    assertTrue(
+        "Expected 'set-permission' to be in the command list: " + commandNames,
+        commandNames.contains("set-permission"));
+    assertTrue(
+        "Expected 'update-permission' to be in the command list: " + 
commandNames,
+        commandNames.contains("update-permission"));
+    assertTrue(
+        "Expected 'delete-permission' to be in the command list: " + 
commandNames,
+        commandNames.contains("delete-permission"));
+    assertTrue(
+        "Expected 'set-user-role' to be in the command list: " + commandNames,
+        commandNames.contains("set-user-role"));
+  }
+
+  private AnnotatedApi assertAnnotatedApiExistsFor(String method, String path) 
{
+    final HashMap<String, String> parts = new HashMap<>();
+    final Api api = apiBag.lookup(path, method, parts);
+    if (api == null) {
+      fail("Expected to find API for path [" + path + "], but no API mapping 
found.");
+    }
+    if (!(api instanceof AnnotatedApi)) {
+      fail(
+          "Expected AnnotatedApi for path ["
+              + path
+              + "], but found non-annotated API ["
+              + api
+              + "]");
+    }
+
+    return (AnnotatedApi) api;
+  }
+}
diff --git 
a/solr/modules/jwt-auth/src/java/org/apache/solr/handler/admin/api/ModifyJWTAuthPluginConfigAPI.java
 
b/solr/modules/jwt-auth/src/java/org/apache/solr/handler/admin/api/ModifyJWTAuthPluginConfigAPI.java
new file mode 100644
index 00000000000..ea24208c66e
--- /dev/null
+++ 
b/solr/modules/jwt-auth/src/java/org/apache/solr/handler/admin/api/ModifyJWTAuthPluginConfigAPI.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.SECURITY_EDIT_PERM;
+
+import org.apache.solr.api.Command;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.api.PayloadObj;
+
+/** V2 API for modifying configuration for Solr's JWTAuthPlugin. */
+@EndPoint(
+    path = {"/cluster/security/authentication"},
+    method = POST,
+    permission = SECURITY_EDIT_PERM)
+public class ModifyJWTAuthPluginConfigAPI {
+
+  @Command(name = "set-property")
+  public void setProperties(PayloadObj<JWTConfigurationPayload> 
propertyPayload) {
+    // Method stub used only to produce v2 API spec; implementation/body empty.
+    throw new IllegalStateException();
+  }
+}
diff --git 
a/solr/modules/jwt-auth/src/java/org/apache/solr/handler/admin/api/package-info.java
 
b/solr/modules/jwt-auth/src/java/org/apache/solr/handler/admin/api/package-info.java
new file mode 100644
index 00000000000..f16f5f29a4e
--- /dev/null
+++ 
b/solr/modules/jwt-auth/src/java/org/apache/solr/handler/admin/api/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/** API endpoint and associated files for the JWT Authentication Plugin */
+package org.apache.solr.handler.admin.api;
diff --git 
a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
 
b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
index 0b468063d77..e5d42ca8f81 100644
--- 
a/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
+++ 
b/solr/modules/jwt-auth/src/java/org/apache/solr/security/jwt/JWTAuthPlugin.java
@@ -49,6 +49,8 @@ import org.apache.http.HttpHeaders;
 import org.apache.http.HttpRequest;
 import org.apache.http.client.protocol.HttpClientContext;
 import org.apache.http.protocol.HttpContext;
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
 import org.apache.solr.client.solrj.impl.Http2SolrClient;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SpecProvider;
@@ -57,6 +59,7 @@ import org.apache.solr.common.util.CommandOperation;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.common.util.ValidatingJsonMap;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.handler.admin.api.ModifyJWTAuthPluginConfigAPI;
 import org.apache.solr.security.AuthenticationPlugin;
 import org.apache.solr.security.ConfigEditablePlugin;
 import 
org.apache.solr.security.jwt.JWTAuthPlugin.JWTAuthenticationResponse.AuthCode;
@@ -705,7 +708,8 @@ public class JWTAuthPlugin extends AuthenticationPlugin
 
   @Override
   public ValidatingJsonMap getSpec() {
-    return Utils.getSpec("cluster.security.JwtAuth.Commands").getSpec();
+    final List<Api> apis = AnnotatedApi.getApis(new 
ModifyJWTAuthPluginConfigAPI());
+    return apis.get(0).getSpec();
   }
 
   /**
diff --git 
a/solr/modules/jwt-auth/src/test/org/apache/solr/handler/admin/api/V2JWTSecurityApiMappingTest.java
 
b/solr/modules/jwt-auth/src/test/org/apache/solr/handler/admin/api/V2JWTSecurityApiMappingTest.java
new file mode 100644
index 00000000000..252f8b9f953
--- /dev/null
+++ 
b/solr/modules/jwt-auth/src/test/org/apache/solr/handler/admin/api/V2JWTSecurityApiMappingTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import java.util.HashMap;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.api.AnnotatedApi;
+import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
+import org.junit.Before;
+import org.junit.Test;
+
+public class V2JWTSecurityApiMappingTest extends SolrTestCaseJ4 {
+
+  private ApiBag apiBag;
+
+  @Before
+  public void setupApiBag() {
+    apiBag = new ApiBag(false);
+  }
+
+  @Test
+  public void testJwtConfigApiMapping() {
+    apiBag.registerObject(new ModifyJWTAuthPluginConfigAPI());
+
+    // Authc API
+    final AnnotatedApi updateAuthcConfig =
+        assertAnnotatedApiExistsFor("POST", 
"/cluster/security/authentication");
+    assertEquals(1, updateAuthcConfig.getCommands().size());
+    assertEquals("set-property", 
updateAuthcConfig.getCommands().keySet().iterator().next());
+  }
+
+  private AnnotatedApi assertAnnotatedApiExistsFor(String method, String path) 
{
+    final HashMap<String, String> parts = new HashMap<>();
+    final Api api = apiBag.lookup(path, method, parts);
+    if (api == null) {
+      fail("Expected to find API for path [" + path + "], but no API mapping 
found.");
+    }
+    if (!(api instanceof AnnotatedApi)) {
+      fail(
+          "Expected AnnotatedApi for path ["
+              + path
+              + "], but found non-annotated API ["
+              + api
+              + "]");
+    }
+
+    return (AnnotatedApi) api;
+  }
+}
diff --git 
a/solr/modules/jwt-auth/src/test/org/apache/solr/handler/admin/api/package-info.java
 
b/solr/modules/jwt-auth/src/test/org/apache/solr/handler/admin/api/package-info.java
new file mode 100644
index 00000000000..563aabcd128
--- /dev/null
+++ 
b/solr/modules/jwt-auth/src/test/org/apache/solr/handler/admin/api/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Tests for the API(s) bundled with the jwt-auth module. */
+package org.apache.solr.handler.admin.api;
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/DeleteBasicAuthUserPayload.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/DeleteBasicAuthUserPayload.java
new file mode 100644
index 00000000000..72ca11d69d2
--- /dev/null
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/DeleteBasicAuthUserPayload.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj.request.beans;
+
+import org.apache.solr.common.util.ReflectMapWriter;
+
+public class DeleteBasicAuthUserPayload implements ReflectMapWriter {}
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/SetRuleBasedAuthPermissionPayload.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/SetRuleBasedAuthPermissionPayload.java
new file mode 100644
index 00000000000..7ada44aee09
--- /dev/null
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/SetRuleBasedAuthPermissionPayload.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj.request.beans;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.solr.common.annotation.JsonProperty;
+import org.apache.solr.common.util.ReflectMapWriter;
+
+public class SetRuleBasedAuthPermissionPayload implements ReflectMapWriter {
+  @JsonProperty public String name;
+
+  // TODO Do we support enum's: only acceptable values here are GET, POST, 
DELETE, and PUT
+  @JsonProperty public String method;
+
+  @JsonProperty public List<String> collection;
+
+  @JsonProperty public List<String> path;
+
+  @JsonProperty public Integer index;
+
+  @JsonProperty public Integer before;
+
+  @JsonProperty public Map<String, Object> params;
+
+  @JsonProperty(required = true)
+  public List<String> role;
+}
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/UpdateRuleBasedAuthPermissionPayload.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/UpdateRuleBasedAuthPermissionPayload.java
new file mode 100644
index 00000000000..f002a08250f
--- /dev/null
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/UpdateRuleBasedAuthPermissionPayload.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj.request.beans;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.solr.common.annotation.JsonProperty;
+import org.apache.solr.common.util.ReflectMapWriter;
+
+public class UpdateRuleBasedAuthPermissionPayload implements ReflectMapWriter {
+  @JsonProperty public String name;
+
+  // TODO Do we support enum's: only acceptable values here are GET, POST, 
DELETE, and PUT
+  @JsonProperty public String method;
+
+  @JsonProperty public List<String> collection;
+
+  @JsonProperty public List<String> path;
+
+  @JsonProperty(required = true)
+  public Integer index;
+
+  @JsonProperty public Integer before;
+
+  @JsonProperty public Map<String, Object> params;
+
+  @JsonProperty(required = true)
+  public List<String> role;
+}
diff --git 
a/solr/solrj/src/java/org/apache/solr/handler/admin/api/JWTConfigurationPayload.java
 
b/solr/solrj/src/java/org/apache/solr/handler/admin/api/JWTConfigurationPayload.java
new file mode 100644
index 00000000000..07f1c63bd80
--- /dev/null
+++ 
b/solr/solrj/src/java/org/apache/solr/handler/admin/api/JWTConfigurationPayload.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.solr.common.annotation.JsonProperty;
+import org.apache.solr.common.util.ReflectMapWriter;
+
+public class JWTConfigurationPayload implements ReflectMapWriter {
+
+  @JsonProperty public Boolean blockUnknown;
+
+  @JsonProperty public String principalClaim;
+
+  @JsonProperty public Boolean requireExp;
+
+  @JsonProperty public List<String> algAllowlist;
+
+  @JsonProperty public Long jwkCacheDur;
+
+  @JsonProperty public Map<String, Object> claimsMatch;
+
+  // TODO Should this be a List<String> instead of a whitespace delimited 
string?
+  @JsonProperty public String scope;
+
+  @JsonProperty public String realm;
+
+  // TODO Should this be a List<String> instead of a whitespace delimited 
string?
+  @JsonProperty public String rolesClaim;
+
+  @JsonProperty public String adminUiScope;
+
+  @JsonProperty public List<String> redirectUris;
+
+  @JsonProperty public Boolean requireIss;
+
+  @JsonProperty public List<Issuer> issuers;
+
+  @JsonProperty public String trustedCertsFile;
+
+  @JsonProperty public List<String> trustedCerts;
+
+  // Prior to supporting an array of issuers, legacy syntax supported 
properties for a single issuer
+  // at the top-level.
+  @JsonProperty public String name;
+  @JsonProperty public List<String> jwksUrl;
+  @JsonProperty public Map<String, Object> jwk;
+  @JsonProperty public String iss;
+  @JsonProperty public String aud;
+  @JsonProperty public String wellKnownUrl;
+  @JsonProperty public String authorizationEndpoint;
+  @JsonProperty public String clientId;
+
+  public static class Issuer implements ReflectMapWriter {
+    @JsonProperty public String name;
+
+    @JsonProperty public String wellKnownUrl;
+
+    @JsonProperty public String clientId;
+
+    @JsonProperty public List<String> jwksUrl;
+
+    @JsonProperty public Map<String, Object> jwk;
+
+    @JsonProperty public String iss;
+
+    @JsonProperty public String aud;
+
+    @JsonProperty public String authorizationEndpoint;
+  }
+}
diff --git 
a/solr/solrj/src/java/org/apache/solr/handler/admin/api/package-info.java 
b/solr/solrj/src/java/org/apache/solr/handler/admin/api/package-info.java
new file mode 100644
index 00000000000..993c5b4c4ed
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/handler/admin/api/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Payload implementations for APIs associated with the jwt-auth optional 
module */
+package org.apache.solr.handler.admin.api;
diff --git 
a/solr/solrj/src/resources/apispec/cluster.security.BasicAuth.Commands.json 
b/solr/solrj/src/resources/apispec/cluster.security.BasicAuth.Commands.json
deleted file mode 100644
index ba03e03324b..00000000000
--- a/solr/solrj/src/resources/apispec/cluster.security.BasicAuth.Commands.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-  "documentation": 
"https://solr.apache.org/guide/basic-authentication-plugin.html";,
-  "description": "Modifies the configuration of Basic authentication, allowing 
you to add or remove users and their passwords.",
-  "methods": [
-    "POST"
-  ],
-  "url": {
-    "paths": [
-      "/cluster/security/authentication"
-    ]
-  },
-  "commands": {
-    "set-user": {
-      "type":"object",
-      "description": "The set-user command allows you to add users and change 
their passwords. Usernames and passwords are expressed as key-value pairs in a 
JSON object.",
-      "additionalProperties": true
-    },
-    "delete-user": {
-      "description": "Delete a user or a list of users. Passwords do not need 
to be provided, simply list the users in a JSON array, separated by colons.",
-      "type":"string"
-    }
-  }
-}
diff --git 
a/solr/solrj/src/resources/apispec/cluster.security.JwtAuth.Commands.json 
b/solr/solrj/src/resources/apispec/cluster.security.JwtAuth.Commands.json
deleted file mode 100644
index d4f43dfd11f..00000000000
--- a/solr/solrj/src/resources/apispec/cluster.security.JwtAuth.Commands.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "documentation": 
"https://solr.apache.org/guide/jwt-authentication-plugin.html";,
-  "description": "Modifies the configuration of JWT token authentication.",
-  "methods": [
-    "POST"
-  ],
-  "url": {
-    "paths": [
-      "/cluster/security/authentication"
-    ]
-  },
-  "commands": {
-    "set-property": {
-      "type":"object",
-      "description": "The set-property command lets you set any of the 
configuration parameters supported by this plugin"
-    }
-  }
-}
diff --git 
a/solr/solrj/src/resources/apispec/cluster.security.MultiPluginAuth.Commands.json
 
b/solr/solrj/src/resources/apispec/cluster.security.MultiPluginAuth.Commands.json
deleted file mode 100644
index e77f693c8a8..00000000000
--- 
a/solr/solrj/src/resources/apispec/cluster.security.MultiPluginAuth.Commands.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
-  "documentation": 
"https://solr.apache.org/guide/basic-authentication-plugin.html";,
-  "description": "Modifies the configuration of the multi-auth plugin. Each 
command should be wrapped in a single key object that identifies the scheme. 
The embedded command is then passed to the scheme specific plugin.",
-  "methods": [
-    "POST"
-  ],
-  "url": {
-    "paths": [
-      "/cluster/security/authentication"
-    ]
-  },
-  "commands": {
-    "set-user": {
-      "type":"object",
-      "description": "The set-user command allows you to add users and change 
their passwords. Usernames and passwords are expressed as key-value pairs in a 
JSON object.",
-      "additionalProperties": true
-    },
-    "delete-user": {
-      "description": "Delete a user or a list of users. Passwords do not need 
to be provided, simply list the users in a JSON array, separated by colons.",
-      "type":"object"
-    },
-    "set-property": {
-      "type":"object",
-      "description": "The set-property command lets you set any of the 
configuration parameters supported by this plugin"
-    }
-  }
-}
diff --git 
a/solr/solrj/src/resources/apispec/cluster.security.RuleBasedAuthorization.json 
b/solr/solrj/src/resources/apispec/cluster.security.RuleBasedAuthorization.json
deleted file mode 100644
index ad4aac1d9e3..00000000000
--- 
a/solr/solrj/src/resources/apispec/cluster.security.RuleBasedAuthorization.json
+++ /dev/null
@@ -1,129 +0,0 @@
-{
-  "documentation": 
"https://solr.apache.org/guide/rule-based-authorization-plugin.html";,
-  "description": "Defines roles for accessing Solr, and assigns users to those 
roles. Use this API to change user authorizations to each of Solr's 
components.",
-  "methods": [
-    "POST"
-  ],
-  "url": {
-    "paths": [
-      "/cluster/security/authorization"
-    ]
-  },
-  "commands": {
-    "set-permission": {
-      "type":"object",
-      "description": "Create a new permission, overwrite an existing 
permission definition, or assign a pre-defined permission to a role.",
-      "properties": {
-        "name":{
-          "type":"string",
-          "description": "The name of the permission. The name will be used to 
update or delete the permission later."
-        },
-        "method":{
-          "type":"string",
-          "enum":["GET", "POST", "DELETE","PUT"],
-          "description": "HTTP methods that are allowed for this permission. 
You could allow only GET requests, or have a role that allows PUT and POST 
requests. The method values that are allowed for this property are GET, POST, 
PUT, DELETE and HEAD."
-        },
-
-        "collection":{
-          "type":"array",
-          "items": {
-            "type": "string"
-          },
-          "description":"The collection or collections the permission will 
apply to. When the path that will be allowed is collection-specific, such as 
when setting permissions to allow use of the Schema API, omitting the 
collection property will allow the defined path and/or method for all 
collections. However, when the path is one that is non-collection-specific, 
such as the Collections API, the collection value must be null. In this case, 
two permissions may need to be created; one fo [...]
-        },
-
-        "path":{
-          "type":"array",
-          "items": {
-            "type": "string"
-          },
-          "description":"A request handler name, such as /update or /select. A 
wild card is supported, to allow for all paths as appropriate (such as, 
/update/*)."
-        },
-        "index": {
-          "type": "integer",
-          "description": "The index of the permission you wish to overwrite. 
Skip this if it is a new permission that should be created."
-        },
-        "before":{
-          "type": "integer",
-          "description":"This property allows ordering of permissions. The 
value for this property is the name of the permission that this new permission 
should be placed before in security.json."
-        },
-        "params":{
-          "type":"object",
-          "additionalProperties":true,
-          "description": "The names and values of request parameters. This 
property can be omitted if all request parameters are allowed, but will 
restrict access only to the values provided if defined."
-        },
-        "role": {
-          "type": "array",
-          "items": {
-            "type": "string",
-            "description": "The name of the role(s) to give this permission. 
This name will be used to map user IDs to the role to grant these permissions. 
The value can be wildcard such as (*), which means that any user is OK, but no 
user is NOT OK."
-          }
-        }
-      },
-      "required": [
-        "role"
-      ]
-    },
-    "update-permission": {
-      "type":"object",
-      "properties": {
-        "name": {
-          "type": "string",
-          "description": "The name of the permission. The name will be used to 
update or delete the permission later."
-        },
-        "method": {
-          "type": "string",
-          "description": "HTTP methods that are allowed for this permission. 
You could allow only GET requests, or have a role that allows PUT and POST 
requests. The method values that are allowed for this property are GET, POST, 
PUT, DELETE and HEAD."
-        },
-        "collection": {
-          "type":"array",
-          "items": {
-            "type": "string"
-          },
-          "description": "The collection or collections the permission will 
apply to. When the path that will be allowed is collection-specific, such as 
when setting permissions to allow use of the Schema API, omitting the 
collection property will allow the defined path and/or method for all 
collections. However, when the path is one that is non-collection-specific, 
such as the Collections API, the collection value must be null. In this case, 
two permissions may need to be created; one f [...]
-        },
-        "path": {
-          "type":"array",
-          "items": {
-            "type": "string"
-          },
-          "description": "A request handler name, such as /update or /select. 
A wild card is supported, to allow for all paths as appropriate (such as, 
/update/*)."
-        },
-        "index": {
-          "type": "integer",
-          "description": "The index of the permission you wish to overwrite."
-        },
-        "before": {
-          "type": "integer",
-          "description": "This property allows ordering of permissions. The 
value for this property is the index of the permission that this new permission 
should be placed before in security.json."
-        },
-        "role": {
-          "type": "array",
-          "items": {
-            "type": "string",
-            "description": "The name of the role(s) to give this permission. 
This name will be used to map user IDs to the role to grant these permissions. 
The value can be wildcard such as (*), which means that any user is OK, but no 
user is NOT OK."
-          }
-        },
-        "params": {
-          "type": "object",
-          "additionalProperties": true,
-          "description": "The names and values of request parameters. This 
property can be omitted if all request parameters are allowed, but will 
restrict access only to the values provided if defined."
-        }
-      },
-      "required": [
-        "role",
-        "index"
-      ]
-    },
-    "delete-permission":{
-      "description":"delete a permission by its index",
-      "type":"integer"
-    },
-    "set-user-role": {
-      "type":"object",
-      "description": "A single command allows roles to be mapped to users. To 
remove a user's permission, you should set the role to null. The key is always 
a user id and the value is one or more role names.",
-      "additionalProperties":true
-
-    }
-  }
-}
diff --git 
a/solr/solrj/src/resources/apispec/cluster.security.authentication.Commands.json
 
b/solr/solrj/src/resources/apispec/cluster.security.authentication.Commands.json
deleted file mode 100644
index b71911cf9e4..00000000000
--- 
a/solr/solrj/src/resources/apispec/cluster.security.authentication.Commands.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "documentation": "https://solr.apache.org/guide/securing-solr.html";,
-  "description":"This is a placeholder output when no authentication is 
configured",
-  "methods": [
-    "POST"
-  ],
-  "url": {
-    "paths": [
-      "/cluster/security/authentication"
-    ]
-  }
-}
diff --git 
a/solr/solrj/src/resources/apispec/cluster.security.authentication.json 
b/solr/solrj/src/resources/apispec/cluster.security.authentication.json
deleted file mode 100644
index d912f147961..00000000000
--- a/solr/solrj/src/resources/apispec/cluster.security.authentication.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "documentation": 
"https://solr.apache.org/guide/authentication-and-authorization-plugins.html";,
-  "description": "Shows the configuration for authentication, including users, 
classes (type of authentication) and other parameters.",
-  "methods": [
-    "GET"
-  ],
-  "url": {
-    "paths": [
-      "/cluster/security/authentication"
-    ]
-  }
-}
diff --git 
a/solr/solrj/src/resources/apispec/cluster.security.authorization.Commands.json 
b/solr/solrj/src/resources/apispec/cluster.security.authorization.Commands.json
deleted file mode 100644
index dfa47b552a0..00000000000
--- 
a/solr/solrj/src/resources/apispec/cluster.security.authorization.Commands.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "documentation": "https://solr.apache.org/guide/securing-solr.html";,
-  "description":"This is a placeholder output when no authorization is 
configured",
-  "methods": [
-    "POST"
-  ],
-  "url": {
-    "paths": [
-      "/cluster/security/authorization"
-    ]
-  }
-
-}
diff --git 
a/solr/solrj/src/resources/apispec/cluster.security.authorization.json 
b/solr/solrj/src/resources/apispec/cluster.security.authorization.json
deleted file mode 100644
index 798f80453ad..00000000000
--- a/solr/solrj/src/resources/apispec/cluster.security.authorization.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "documentation": 
"https://solr.apache.org/guide/authentication-and-authorization-plugins.html";,
-  "description":"Shows the configuration for authorization, including the 
classes (type of authorization), permissions, user-roles, and other 
parameters.",
-  "methods": [
-    "GET"
-  ],
-  "url": {
-    "paths": [
-      "/cluster/security/authorization"
-    ]
-  }
-
-}
diff --git 
a/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java 
b/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java
index d6e6361f3e9..225474a68e0 100644
--- a/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java
+++ b/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java
@@ -17,16 +17,10 @@
 package org.apache.solr.common.util;
 
 import java.util.List;
-import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
 
 public class JsonValidatorTest extends SolrTestCaseJ4 {
 
-  public void testSchema() {
-    checkSchema("cluster.security.BasicAuth.Commands");
-    checkSchema("cluster.security.RuleBasedAuthorization");
-  }
-
   public void testSchemaValidation() {
     final JsonSchemaValidator personSchemaValidator =
         new JsonSchemaValidator(
@@ -187,19 +181,4 @@ public class JsonValidatorTest extends SolrTestCaseJ4 {
         
mutuallyExclusivePropertiesValidator.validateJson(Utils.fromJSONString("" + 
"{'a':'val'}"));
     assertNull(errs);
   }
-
-  private void checkSchema(String name) {
-    ValidatingJsonMap spec = Utils.getSpec(name).getSpec();
-    @SuppressWarnings({"rawtypes"})
-    Map commands = (Map) spec.get("commands");
-    for (Object o : commands.entrySet()) {
-      @SuppressWarnings({"rawtypes"})
-      Map.Entry cmd = (Map.Entry) o;
-      try {
-        JsonSchemaValidator validator = new JsonSchemaValidator((Map) 
cmd.getValue());
-      } catch (Exception e) {
-        throw new RuntimeException("Error in command  " + cmd.getKey() + " in 
schema " + name, e);
-      }
-    }
-  }
 }

Reply via email to