You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ja...@apache.org on 2018/12/12 11:28:04 UTC

lucene-solr:master: SOLR-12791: Add Metrics reporting for AuthenticationPlugin

Repository: lucene-solr
Updated Branches:
  refs/heads/master 280f67927 -> ef2f0cd88


SOLR-12791: Add Metrics reporting for AuthenticationPlugin


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/ef2f0cd8
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/ef2f0cd8
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/ef2f0cd8

Branch: refs/heads/master
Commit: ef2f0cd88c6eb4b662aea06eaeb3b933288b23eb
Parents: 280f679
Author: Jan Høydahl <ja...@apache.org>
Authored: Wed Dec 12 11:33:32 2018 +0100
Committer: Jan Høydahl <ja...@apache.org>
Committed: Wed Dec 12 11:33:32 2018 +0100

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   2 +
 .../org/apache/solr/core/CoreContainer.java     |   6 +
 .../java/org/apache/solr/core/SolrInfoBean.java |   2 +-
 .../solr/security/AuthenticationPlugin.java     |  88 ++++++++++++++-
 .../apache/solr/security/BasicAuthPlugin.java   |  73 +++++++-----
 .../apache/solr/security/HadoopAuthPlugin.java  |  18 +++
 .../solr/security/PKIAuthenticationPlugin.java  |  10 +-
 .../apache/solr/servlet/SolrDispatchFilter.java |   2 +-
 .../solr/security/BasicAuthIntegrationTest.java |  49 +++++++-
 .../solr/security/BasicAuthStandaloneTest.java  |   4 +-
 .../PKIAuthenticationIntegrationTest.java       |   6 +-
 .../security/TestPKIAuthenticationPlugin.java   |   8 +-
 .../TestSolrCloudWithHadoopAuthPlugin.java      |   8 +-
 .../solr/cloud/SolrCloudAuthTestCase.java       | 112 +++++++++++++++++++
 14 files changed, 336 insertions(+), 52 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index bfa3666..a01c16e 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -79,6 +79,8 @@ New Features
 
 * SOLR-12593: The default configSet now includes an "ignored_*" dynamic field.  (David Smiley)
 
+* SOLR-12791: Add Metrics reporting for AuthenticationPlugin (janhoy)
+
 Bug Fixes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/java/org/apache/solr/core/CoreContainer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 1be03a8..3051dbf 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -804,6 +804,12 @@ public class CoreContainer {
     SecurityConfHandler.SecurityConfig securityConfig = securityConfHandler.getSecurityConfig(false);
     initializeAuthorizationPlugin((Map<String, Object>) securityConfig.getData().get("authorization"));
     initializeAuthenticationPlugin((Map<String, Object>) securityConfig.getData().get("authentication"));
+    if (authenticationPlugin != null) {
+      authenticationPlugin.plugin.initializeMetrics(metricManager, SolrInfoBean.Group.node.toString(), metricTag, "/authentication");
+    }
+    if (pkiAuthenticationPlugin != null && pkiAuthenticationPlugin.getMetricRegistry() == null) {
+      pkiAuthenticationPlugin.initializeMetrics(metricManager, SolrInfoBean.Group.node.toString(), metricTag, "/authentication/pki");
+    }
   }
 
   private static void checkForDuplicateCoreNames(List<CoreDescriptor> cds) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java b/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java
index 38ffc99..bfb3428 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrInfoBean.java
@@ -34,7 +34,7 @@ public interface SolrInfoBean {
    * Category of Solr component.
    */
   enum Category { CONTAINER, ADMIN, CORE, QUERY, UPDATE, CACHE, HIGHLIGHTER, QUERYPARSER, SPELLCHECKER,
-    SEARCHER, REPLICATION, TLOG, INDEX, DIRECTORY, HTTP, OTHER }
+    SEARCHER, REPLICATION, TLOG, INDEX, DIRECTORY, HTTP, SECURITY, OTHER }
 
   /**
    * Top-level group of beans or metrics for a subsystem.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java b/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
index a9d112a..48b073d 100644
--- a/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/AuthenticationPlugin.java
@@ -20,16 +20,42 @@ import javax.servlet.FilterChain;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import java.io.Closeable;
+import java.util.Arrays;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import org.apache.solr.core.SolrInfoBean;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricProducer;
 
 /**
  * 
  * @lucene.experimental
  */
-public abstract class AuthenticationPlugin implements Closeable {
+public abstract class AuthenticationPlugin implements Closeable, SolrInfoBean, SolrMetricProducer {
 
   final public static String AUTHENTICATION_PLUGIN_PROP = "authenticationPlugin";
 
+  // Metrics
+  private Set<String> metricNames = ConcurrentHashMap.newKeySet();
+  private MetricRegistry registry;
+
+  protected String registryName;
+  protected SolrMetricManager metricManager;
+  protected Meter numErrors = new Meter();
+  protected Counter requests = new Counter();
+  protected Timer requestTimes = new Timer();
+  protected Counter totalTime = new Counter();
+  protected Counter numAuthenticated = new Counter();
+  protected Counter numPassThrough = new Counter();
+  protected Counter numWrongCredentials = new Counter();
+  protected Counter numMissingCredentials = new Counter();
+
   /**
    * This is called upon loading up of a plugin, used for setting it up.
    * @param pluginConfig Config parameters, possibly from a ZK source
@@ -52,6 +78,23 @@ public abstract class AuthenticationPlugin implements Closeable {
   public abstract boolean doAuthenticate(ServletRequest request, ServletResponse response,
       FilterChain filterChain) throws Exception;
 
+  /**
+   * This method is called by SolrDispatchFilter in order to initiate authentication.
+   * It does some standard metrics counting.
+   */
+  public final boolean authenticate(ServletRequest request, ServletResponse response, FilterChain filterChain) throws Exception {
+    Timer.Context timer = requestTimes.time();
+    requests.inc();
+    try {
+      return doAuthenticate(request, response, filterChain);
+    } catch(Exception e) {
+      numErrors.mark();
+      throw e;
+    } finally {
+      long elapsed = timer.stop();
+      totalTime.inc(elapsed);
+    }
+  }
 
   /**
    * Cleanup any per request  data
@@ -59,4 +102,47 @@ public abstract class AuthenticationPlugin implements Closeable {
   public void closeRequest() {
   }
 
+  @Override
+  public void initializeMetrics(SolrMetricManager manager, String registryName, String tag, final String scope) {
+    this.metricManager = manager;
+    this.registryName = registryName;
+    // Metrics
+    registry = manager.registry(registryName);
+    numErrors = manager.meter(this, registryName, "errors", getCategory().toString(), scope);
+    requests = manager.counter(this, registryName, "requests", getCategory().toString(), scope);
+    numAuthenticated = manager.counter(this, registryName, "authenticated", getCategory().toString(), scope);
+    numPassThrough = manager.counter(this, registryName, "passThrough", getCategory().toString(), scope);
+    numWrongCredentials = manager.counter(this, registryName, "failWrongCredentials", getCategory().toString(), scope);
+    numMissingCredentials = manager.counter(this, registryName, "failMissingCredentials", getCategory().toString(), scope);
+    requestTimes = manager.timer(this, registryName, "requestTimes", getCategory().toString(), scope);
+    totalTime = manager.counter(this, registryName, "totalTime", getCategory().toString(), scope);
+    metricNames.addAll(Arrays.asList("errors", "requests", "authenticated", "passThrough",
+        "failWrongCredentials", "failMissingCredentials", "requestTimes", "totalTime"));
+  }
+  
+  @Override
+  public String getName() {
+    return this.getClass().getName();
+  }
+
+  @Override
+  public String getDescription() {
+    return "Authentication Plugin " + this.getClass().getName();
+  }
+
+  @Override
+  public Category getCategory() {
+    return Category.SECURITY;
+  }
+  
+  @Override
+  public Set<String> getMetricNames() {
+    return metricNames;
+  }
+
+  @Override
+  public MetricRegistry getMetricRegistry() {
+    return registry;
+  }
+  
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
index 1212452..7ac5a7c 100644
--- a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
@@ -127,44 +127,57 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
       if (st.hasMoreTokens()) {
         String basic = st.nextToken();
         if (basic.equalsIgnoreCase("Basic")) {
-          try {
-            String credentials = new String(Base64.decodeBase64(st.nextToken()), "UTF-8");
-            int p = credentials.indexOf(":");
-            if (p != -1) {
-              final String username = credentials.substring(0, p).trim();
-              String pwd = credentials.substring(p + 1).trim();
-              if (!authenticate(username, pwd)) {
-                log.debug("Bad auth credentials supplied in Authorization header");
-                authenticationFailure(response, isAjaxRequest, "Bad credentials");
+          if (st.hasMoreTokens()) {
+            try {
+              String credentials = new String(Base64.decodeBase64(st.nextToken()), "UTF-8");
+              int p = credentials.indexOf(":");
+              if (p != -1) {
+                final String username = credentials.substring(0, p).trim();
+                String pwd = credentials.substring(p + 1).trim();
+                if (!authenticate(username, pwd)) {
+                  numWrongCredentials.inc();
+                  log.debug("Bad auth credentials supplied in Authorization header");
+                  authenticationFailure(response, isAjaxRequest, "Bad credentials");
+                  return false;
+                } else {
+                  HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
+                    @Override
+                    public Principal getUserPrincipal() {
+                      return new BasicUserPrincipal(username);
+                    }
+                  };
+                  numAuthenticated.inc();
+                  filterChain.doFilter(wrapper, response);
+                  return true;
+                }
               } else {
-                HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
-                  @Override
-                  public Principal getUserPrincipal() {
-                    return new BasicUserPrincipal(username);
-                  }
-                };
-                filterChain.doFilter(wrapper, response);
-                return true;
+                numErrors.mark();
+                authenticationFailure(response, isAjaxRequest, "Invalid authentication token");
+                return false;
               }
-
-            } else {
-              authenticationFailure(response, isAjaxRequest, "Invalid authentication token");
+            } catch (UnsupportedEncodingException e) {
+              throw new Error("Couldn't retrieve authentication", e);
             }
-          } catch (UnsupportedEncodingException e) {
-            throw new Error("Couldn't retrieve authentication", e);
+          } else {
+            numErrors.mark();
+            authenticationFailure(response, isAjaxRequest, "Malformed Basic Auth header");
+            return false;
           }
         }
       }
+    }
+    
+    // No auth header OR header empty OR Authorization header not of type Basic, i.e. "unknown" user
+    if (blockUnknown) {
+      numMissingCredentials.inc();
+      authenticationFailure(response, isAjaxRequest, "require authentication");
+      return false;
     } else {
-      if (blockUnknown) {
-        authenticationFailure(response, isAjaxRequest, "require authentication");
-      } else {
-        request.setAttribute(AuthenticationPlugin.class.getName(), authenticationProvider.getPromptHeaders());
-        filterChain.doFilter(request, response);
-        return true;
-      }
+      numPassThrough.inc();
+      request.setAttribute(AuthenticationPlugin.class.getName(), authenticationProvider.getPromptHeaders());
+      filterChain.doFilter(request, response);
+      return true;
     }
-    return false;
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
index 28352f3..f0e0fdc 100644
--- a/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/HadoopAuthPlugin.java
@@ -244,6 +244,24 @@ public class HadoopAuthPlugin extends AuthenticationPlugin {
     };
     authFilter.doFilter(request, rspCloseShield, filterChain);
 
+    switch (frsp.getStatus()) {
+      case HttpServletResponse.SC_UNAUTHORIZED:
+        // Cannot tell whether the 401 is due to wrong or missing credentials
+        numWrongCredentials.inc();
+        break;
+
+      case HttpServletResponse.SC_FORBIDDEN:
+        // Are there other status codes which should also translate to error?
+        numErrors.mark();
+        break;
+      default:
+        if (frsp.getStatus() >= 200 && frsp.getStatus() <= 299) {
+          numAuthenticated.inc();
+        } else {
+          numErrors.mark();
+        }
+    }
+     
     if (TRACE_HTTP) {
       log.info("----------HTTP Response---------");
       log.info("Status : {}", frsp.getStatus());

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java b/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java
index 54d09d8..fe1ae7b 100644
--- a/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/PKIAuthenticationPlugin.java
@@ -90,6 +90,7 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
 
     String requestURI = ((HttpServletRequest) request).getRequestURI();
     if (requestURI.endsWith(PublicKeyHandler.PATH)) {
+      numPassThrough.inc();
       filterChain.doFilter(request, response);
       return true;
     }
@@ -98,6 +99,7 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
     if (header == null) {
       //this must not happen
       log.error("No SolrAuth header present");
+      numMissingCredentials.inc();
       filterChain.doFilter(request, response);
       return true;
     }
@@ -105,6 +107,7 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
     List<String> authInfo = StrUtils.splitWS(header, false);
     if (authInfo.size() < 2) {
       log.error("Invalid SolrAuth Header {}", header);
+      numErrors.mark();
       filterChain.doFilter(request, response);
       return true;
     }
@@ -115,19 +118,22 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
     PKIHeaderData decipher = decipherHeader(nodeName, cipher);
     if (decipher == null) {
       log.error("Could not decipher a header {} . No principal set", header);
+      numMissingCredentials.inc();
       filterChain.doFilter(request, response);
       return true;
     }
     if ((receivedTime - decipher.timestamp) > MAX_VALIDITY) {
       log.error("Invalid key request timestamp: {} , received timestamp: {} , TTL: {}", decipher.timestamp, receivedTime, MAX_VALIDITY);
-        filterChain.doFilter(request, response);
-        return true;
+      numErrors.mark();
+      filterChain.doFilter(request, response);
+      return true;
     }
 
     final Principal principal = "$".equals(decipher.userName) ?
         SU :
         new BasicUserPrincipal(decipher.userName);
 
+    numAuthenticated.inc();
     filterChain.doFilter(getWrapper((HttpServletRequest) request, principal), response);
     return true;
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
index 1a8b14e..956af17 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
@@ -478,7 +478,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
       try {
         log.debug("Request to authenticate: {}, domain: {}, port: {}", request, request.getLocalName(), request.getLocalPort());
         // upon successful authentication, this should call the chain's next filter.
-        requestContinues = authenticationPlugin.doAuthenticate(request, response, (req, rsp) -> {
+        requestContinues = authenticationPlugin.authenticate(request, response, (req, rsp) -> {
           isAuthenticated.set(true);
           wrappedRequest.set((HttpServletRequest) req);
         });

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
index 6b6b4af..db47d37 100644
--- a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
@@ -30,6 +30,7 @@ import java.util.Objects;
 import java.util.Random;
 import java.util.function.Predicate;
 
+import com.codahale.metrics.MetricRegistry;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpGet;
@@ -47,13 +48,15 @@ import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.request.RequestWriter.StringPayloadContentWriter;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.request.V2Request;
-import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.cloud.SolrCloudAuthTestCase;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.Base64;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.StrUtils;
@@ -67,7 +70,7 @@ import org.slf4j.LoggerFactory;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Collections.singletonMap;
 
-public class BasicAuthIntegrationTest extends SolrCloudTestCase {
+public class BasicAuthIntegrationTest extends SolrCloudAuthTestCase {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
@@ -116,7 +119,10 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
       
       baseUrl = randomJetty.getBaseUrl().toString();
       verifySecurityStatus(cl, baseUrl + authcPrefix, "authentication/class", "solr.BasicAuthPlugin", 20);
-
+      assertNumberOfMetrics(16); // Basic auth metrics available
+      assertAuthMetricsMinimums(1, 0, 1, 0, 0, 0);
+      assertPkiAuthMetricsMinimums(0, 0, 0, 0, 0, 0);
+      
       String command = "{\n" +
           "'set-user': {'harry':'HarryIsCool'}\n" +
           "}";
@@ -134,7 +140,9 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
         cluster.getSolrClient().request(genericReq);
       });
       assertEquals(401, exp.code());
-
+      assertAuthMetricsMinimums(2, 0, 2, 0, 0, 0);
+      assertPkiAuthMetricsMinimums(0, 0, 0, 0, 0, 0);
+      
       command = "{\n" +
           "'set-user': {'harry':'HarryIsUberCool'}\n" +
           "}";
@@ -148,6 +156,8 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
       int statusCode = r.getStatusLine().getStatusCode();
       Utils.consumeFully(r.getEntity());
       assertEquals("proper_cred sent, but access denied", 200, statusCode);
+      assertPkiAuthMetricsMinimums(0, 0, 0, 0, 0, 0);
+      assertAuthMetricsMinimums(4, 1, 3, 0, 0, 0);
 
       baseUrl = cluster.getRandomJetty(random()).getBaseUrl().toString();
 
@@ -157,6 +167,7 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
           "}";
 
       executeCommand(baseUrl + authzPrefix, cl,command, "solr", "SolrRocks");
+      assertAuthMetricsMinimums(6, 2, 4, 0, 0, 0);
 
       baseUrl = cluster.getRandomJetty(random()).getBaseUrl().toString();
       verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/user-role/harry", NOT_NULL_PREDICATE, 20);
@@ -167,10 +178,12 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
               "role", "dev"))), "harry", "HarryIsUberCool" );
 
       verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[1]/collection", "x", 20);
+      assertAuthMetricsMinimums(9, 3, 6, 0, 0, 0);
 
       executeCommand(baseUrl + authzPrefix, cl,Utils.toJSONString(singletonMap("set-permission", Utils.makeMap
           ("name", "collection-admin-edit", "role", "admin"))), "harry", "HarryIsUberCool"  );
       verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[2]/name", "collection-admin-edit", 20);
+      assertAuthMetricsMinimums(11, 4, 7, 0, 0, 0);
 
       CollectionAdminRequest.Reload reload = CollectionAdminRequest.reloadCollection(COLLECTION);
 
@@ -197,7 +210,7 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
             .setBasicAuthCredentials("harry", "Cool12345"));
         fail("This should not succeed");
       } catch (HttpSolrClient.RemoteSolrException e) {
-
+        assertAuthMetricsMinimums(15, 5, 9, 1, 0, 0);
       }
 
       executeCommand(baseUrl + authzPrefix, cl,"{set-permission : { name : update , role : admin}}", "harry", "HarryIsUberCool");
@@ -214,6 +227,7 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
       executeCommand(baseUrl + authcPrefix, cl, "{set-property : { blockUnknown: true}}", "harry", "HarryIsUberCool");
       verifySecurityStatus(cl, baseUrl + authcPrefix, "authentication/blockUnknown", "true", 20, "harry", "HarryIsUberCool");
       verifySecurityStatus(cl, baseUrl + "/admin/info/key", "key", NOT_NULL_PREDICATE, 20);
+      assertAuthMetricsMinimums(18, 8, 9, 1, 0, 0);
 
       String[] toolArgs = new String[]{
           "status", "-solr", baseUrl};
@@ -232,6 +246,24 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
         log.error("RunExampleTool failed due to: " + e +
             "; stdout from tool prior to failure: " + baos.toString(StandardCharsets.UTF_8.name()));
       }
+
+      SolrParams params = new MapSolrParams(Collections.singletonMap("q", "*:*"));
+      // Query that fails due to missing credentials
+      exp = expectThrows(HttpSolrClient.RemoteSolrException.class, () -> {
+        cluster.getSolrClient().query(COLLECTION, params);
+      });
+      assertEquals(401, exp.code());
+      assertAuthMetricsMinimums(20, 8, 9, 1, 2, 0);
+      assertPkiAuthMetricsMinimums(4, 4, 0, 0, 0, 0);
+
+      // Query that succeeds
+      GenericSolrRequest req = new GenericSolrRequest(SolrRequest.METHOD.GET, "/select", params);
+      req.setBasicAuthCredentials("harry", "HarryIsUberCool");
+      cluster.getSolrClient().request(req, COLLECTION);
+      
+      assertAuthMetricsMinimums(21, 9, 9, 1, 2, 0);
+      assertPkiAuthMetricsMinimums(7, 7, 0, 0, 0, 0);
+
       executeCommand(baseUrl + authcPrefix, cl, "{set-property : { blockUnknown: false}}", "harry", "HarryIsUberCool");
     } finally {
       if (cl != null) {
@@ -240,6 +272,13 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
     }
   }
 
+  private void assertNumberOfMetrics(int num) {
+    MetricRegistry registry0 = cluster.getJettySolrRunner(0).getCoreContainer().getMetricManager().registry("solr.node");
+    assertNotNull(registry0);
+
+    assertEquals(num, registry0.getMetrics().entrySet().stream().filter(e -> e.getKey().startsWith("SECURITY")).count());
+  }
+
   public static void executeCommand(String url, HttpClient cl, String payload, String user, String pwd)
       throws IOException {
     HttpPost httpPost;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/test/org/apache/solr/security/BasicAuthStandaloneTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/BasicAuthStandaloneTest.java b/solr/core/src/test/org/apache/solr/security/BasicAuthStandaloneTest.java
index da77b22..1cfd681 100644
--- a/solr/core/src/test/org/apache/solr/security/BasicAuthStandaloneTest.java
+++ b/solr/core/src/test/org/apache/solr/security/BasicAuthStandaloneTest.java
@@ -29,6 +29,7 @@ import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.ByteArrayEntity;
 import org.apache.http.message.AbstractHttpMessage;
 import org.apache.http.message.BasicHeader;
+import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.HttpClientUtil;
@@ -41,8 +42,6 @@ import org.apache.solr.common.util.Base64;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.handler.admin.SecurityConfHandler;
 import org.apache.solr.handler.admin.SecurityConfHandlerLocalForTesting;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.util.LogLevel;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -84,7 +83,6 @@ public class BasicAuthStandaloneTest extends SolrTestCaseJ4 {
   }
 
   @Test
-  @LogLevel("org.apache.solr=DEBUG")
   public void testBasicAuth() throws Exception {
 
     String authcPrefix = "/admin/authentication";

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/test/org/apache/solr/security/PKIAuthenticationIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/PKIAuthenticationIntegrationTest.java b/solr/core/src/test/org/apache/solr/security/PKIAuthenticationIntegrationTest.java
index 1f85f3a..aa70d05 100644
--- a/solr/core/src/test/org/apache/solr/security/PKIAuthenticationIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/security/PKIAuthenticationIntegrationTest.java
@@ -25,7 +25,7 @@ import org.apache.http.client.HttpClient;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.QueryRequest;
-import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.cloud.SolrCloudAuthTestCase;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.Utils;
@@ -39,7 +39,7 @@ import static java.util.Collections.singletonMap;
 import static org.apache.solr.common.util.Utils.makeMap;
 import static org.apache.solr.security.TestAuthorizationFramework.verifySecurityStatus;
 
-public class PKIAuthenticationIntegrationTest extends SolrCloudTestCase {
+public class PKIAuthenticationIntegrationTest extends SolrCloudAuthTestCase {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
@@ -96,7 +96,7 @@ public class PKIAuthenticationIntegrationTest extends SolrCloudTestCase {
     QueryRequest query = new QueryRequest(params);
     query.process(cluster.getSolrClient(), "collection");
     assertTrue("all nodes must get the user solr , no:of nodes got solr : " + count.get(), count.get() > 2);
-
+    assertPkiAuthMetricsMinimums(2, 2, 0, 0, 0, 0);
   }
 
   @After

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java b/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java
index e6a04cf..e6dc790 100644
--- a/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java
+++ b/solr/core/src/test/org/apache/solr/security/TestPKIAuthenticationPlugin.java
@@ -99,7 +99,7 @@ public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
     final AtomicReference<ServletRequest> wrappedRequestByFilter = new AtomicReference<>();
     HttpServletRequest mockReq = createMockRequest(header);
     FilterChain filterChain = (servletRequest, servletResponse) -> wrappedRequestByFilter.set(servletRequest);
-    mock.doAuthenticate(mockReq, null, filterChain);
+    mock.authenticate(mockReq, null, filterChain);
 
     assertNotNull(((HttpServletRequest) wrappedRequestByFilter.get()).getUserPrincipal());
     assertNotNull(wrappedRequestByFilter.get());
@@ -112,7 +112,7 @@ public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
     request = new BasicHttpRequest("GET", "http://localhost:56565");
     mock.setHeader(request);
     assertNull(request.getFirstHeader(PKIAuthenticationPlugin.HEADER));
-    mock.doAuthenticate(mockReq, null, filterChain);
+    mock.authenticate(mockReq, null, filterChain);
     assertNotNull(wrappedRequestByFilter.get());
     assertNull(((HttpServletRequest) wrappedRequestByFilter.get()).getUserPrincipal());
 
@@ -129,7 +129,7 @@ public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
     assertNotNull(header.get());
     assertTrue(header.get().getValue().startsWith(nodeName));
 
-    mock.doAuthenticate(mockReq, null, filterChain);
+    mock.authenticate(mockReq, null, filterChain);
     assertNotNull(wrappedRequestByFilter.get());
     assertEquals("$", ((HttpServletRequest) wrappedRequestByFilter.get()).getUserPrincipal().getName());
 
@@ -146,7 +146,7 @@ public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
       }
     };
 
-    mock1.doAuthenticate(mockReq, null,filterChain );
+    mock1.authenticate(mockReq, null,filterChain );
     assertNotNull(wrappedRequestByFilter.get());
     assertEquals("$", ((HttpServletRequest) wrappedRequestByFilter.get()).getUserPrincipal().getName());
     mock1.close();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java b/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java
index 118dc47..1842da5 100644
--- a/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java
+++ b/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java
@@ -27,14 +27,14 @@ import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.cloud.AbstractDistribZkTestBase;
 import org.apache.solr.cloud.KerberosTestServices;
-import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.cloud.SolrCloudAuthTestCase;
 import org.apache.solr.common.SolrInputDocument;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
 // commented 20-July-2018 @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 12-Jun-2018
-public class TestSolrCloudWithHadoopAuthPlugin extends SolrCloudTestCase {
+public class TestSolrCloudWithHadoopAuthPlugin extends SolrCloudAuthTestCase {
   protected static final int NUM_SERVERS = 1;
   protected static final int NUM_SHARDS = 1;
   protected static final int REPLICATION_FACTOR = 1;
@@ -119,11 +119,14 @@ public class TestSolrCloudWithHadoopAuthPlugin extends SolrCloudTestCase {
     CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName, "conf1",
         NUM_SHARDS, REPLICATION_FACTOR);
     create.process(solrClient);
+    // The metrics counter for wrong credentials here really just means  
+    assertAuthMetricsMinimums(6, 3, 0, 3, 0, 0);
 
     SolrInputDocument doc = new SolrInputDocument();
     doc.setField("id", "1");
     solrClient.add(collectionName, doc);
     solrClient.commit(collectionName);
+    assertAuthMetricsMinimums(10, 5, 0, 5, 0, 0);
 
     SolrQuery query = new SolrQuery();
     query.setQuery("*:*");
@@ -134,6 +137,7 @@ public class TestSolrCloudWithHadoopAuthPlugin extends SolrCloudTestCase {
     deleteReq.process(solrClient);
     AbstractDistribZkTestBase.waitForCollectionToDisappear(collectionName,
         solrClient.getZkStateReader(), true, true, 330);
+    assertAuthMetricsMinimums(16, 8, 0, 8, 0, 0);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ef2f0cd8/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java b/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java
new file mode 100644
index 0000000..3966426
--- /dev/null
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/SolrCloudAuthTestCase.java
@@ -0,0 +1,112 @@
+/*
+ * 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.cloud;
+
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base test class for cloud tests wanting to track authentication metrics.
+ * The assertions provided by this base class require a *minimum* count, not exact count from metrics.
+ * Warning: Make sure that your test case does not break when beasting. 
+ */
+public class SolrCloudAuthTestCase extends SolrCloudTestCase {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  private static final List<String> AUTH_METRICS_KEYS = Arrays.asList("errors", "requests", "authenticated", 
+      "passThrough", "failWrongCredentials", "failMissingCredentials", "requestTimes", "totalTime");
+  private static final List<String> AUTH_METRICS_METER_KEYS = Arrays.asList("errors");
+  private static final List<String> AUTH_METRICS_TIMER_KEYS = Collections.singletonList("requestTimes");
+  private static final String METRICS_PREFIX_PKI = "SECURITY./authentication/pki.";
+  private static final String METRICS_PREFIX = "SECURITY./authentication.";
+  
+  /**
+   * Used to check metric counts for PKI auth
+   */
+  protected void assertPkiAuthMetricsMinimums(int requests, int authenticated, int passThrough, int failWrongCredentials, int failMissingCredentials, int errors) {
+    assertAuthMetricsMinimums(METRICS_PREFIX_PKI, requests, authenticated, passThrough, failWrongCredentials, failMissingCredentials, errors);
+  }
+  
+  /**
+   * Used to check metric counts for the AuthPlugin in use (except PKI)
+   */
+  protected void assertAuthMetricsMinimums(int requests, int authenticated, int passThrough, int failWrongCredentials, int failMissingCredentials, int errors) {
+    assertAuthMetricsMinimums(METRICS_PREFIX, requests, authenticated, passThrough, failWrongCredentials, failMissingCredentials, errors);
+  }  
+  
+  /**
+   * Common test method to be able to check security from any authentication plugin
+   * @param prefix the metrics key prefix, currently "SECURITY./authentication." for basic auth and "SECURITY./authentication/pki." for PKI 
+   */
+  private void assertAuthMetricsMinimums(String prefix, int requests, int authenticated, int passThrough, int failWrongCredentials, int failMissingCredentials, int errors) {
+    List<Map<String, Metric>> metrics = new ArrayList<>();
+    cluster.getJettySolrRunners().forEach(r -> {
+      MetricRegistry registry = r.getCoreContainer().getMetricManager().registry("solr.node");
+      assertNotNull(registry);
+      metrics.add(registry.getMetrics());
+    });
+
+    Map<String,Long> counts = new HashMap<>();
+    AUTH_METRICS_KEYS.forEach(k -> {
+      counts.put(k, sumCount(prefix, k, metrics));
+    });
+    
+    // check each counter
+    assertExpectedMetrics(requests, "requests", counts);
+    assertExpectedMetrics(authenticated, "authenticated", counts);
+    assertExpectedMetrics(passThrough, "passThrough", counts);
+    assertExpectedMetrics(failWrongCredentials, "failWrongCredentials", counts);
+    assertExpectedMetrics(failMissingCredentials, "failMissingCredentials", counts);
+    assertExpectedMetrics(errors, "errors", counts);
+    if (counts.get("requests") > 0) {
+      assertTrue("requestTimes count not > 1", counts.get("requestTimes") > 1);
+      assertTrue("totalTime not > 0", counts.get("totalTime") > 0);
+    }
+  }
+
+  // Check that the actual metric is equal to or greater than the expected value, never less
+  private void assertExpectedMetrics(int expected, String key, Map<String, Long> counts) {
+    long cnt = counts.get(key);
+    log.debug("Asserting that auth metrics count ({}) > expected ({})", cnt, expected);
+    assertTrue("Expected " + key + " metric count to be " + expected + " or higher, but got " + cnt, 
+        cnt >= expected);
+  }
+
+  // Have to sum the metrics from all three shards/nodes
+  private long sumCount(String prefix, String key, List<Map<String, Metric>> metrics) {
+    assertTrue("Metric " + prefix + key + " does not exist", metrics.get(0).containsKey(prefix + key)); 
+    if (AUTH_METRICS_METER_KEYS.contains(key))
+      return metrics.stream().mapToLong(l -> ((Meter)l.get(prefix + key)).getCount()).sum();
+    else if (AUTH_METRICS_TIMER_KEYS.contains(key))
+      return (long) ((long) 1000 * metrics.stream().mapToDouble(l -> ((Timer)l.get(prefix + key)).getMeanRate()).average().orElse(0.0d));
+    else
+      return metrics.stream().mapToLong(l -> ((Counter)l.get(prefix + key)).getCount()).sum();
+  }
+}