Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8149626

"javax.security.auth.Subject.SecureSet" is backed with an inappropriate type

XMLWordPrintable

      A DESCRIPTION OF THE REQUEST :
      The field "elements" of the class "javax.security.auth.Subject.SecureSet" is of type "java.util.LinkedList" instead of something more appropriate like "java.util.HashSet".

      JUSTIFICATION :
      When adding huge amounts of "java.security.Principal" objects to a "javax.security.auth.Subject" principal set, the method "java.security.Principal.equals(Object)" is being invoked unnecessarily often.

      For n principals being added to an empty principal set the invocation count is (using http://asciimath.org/):

      sum_(k=1)^n k-1

      In an enterprise application (we are using Java EE 5 and JBoss EAP 4.3) with large call graphs the security context is being propagated very often. And each time the "javax.security.auth.Subject" instance is being copied - unfortunately without the usage of the "copy" constructor.

      We have profiled our application and counted ~1 mio "java.security.Principal.equals(Object)" calls for fairly small call graphs.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      When adding to a set of any kind, the method "java.lang.Object.equals(Object)" should not be invoked n times in a worst case scenario (that is the object to be added is not part of the set yet) where n is the number of elements in the set.

      The set should rather make use of the hashCode-equals-contract.
      ACTUAL -
      "javax.security.auth.Subject.SecureSet.add(E)" checks the backing "java.util.LinkedList" for duplicates of objects to be added.

      ---------- BEGIN SOURCE ----------
      package com.acme;

      import java.security.Principal;
      import java.util.concurrent.atomic.AtomicLong;

      import javax.security.auth.Subject;

      public class MyPrincipal implements Principal {

          private static AtomicLong
              hashCodeInvocationCount = new AtomicLong(),
              equalsInvocationCount = new AtomicLong()
          ;

          private final String name;

          public MyPrincipal(String name) {
              this.name = name;
          }

          @Override
          public String getName() {
              return name;
          }

          @Override
          public int hashCode() {
              hashCodeInvocationCount.incrementAndGet();
              final int prime = 31;
              int result = 1;
              result = prime * result + ((name == null) ? 0 : name.hashCode());
              return result;
          }

          @Override
          public boolean equals(Object obj) {
              equalsInvocationCount.incrementAndGet();
              if (this == obj)
                  return true;
              if (obj == null)
                  return false;
              if (getClass() != obj.getClass())
                  return false;
              MyPrincipal other = (MyPrincipal) obj;
              if (name == null) {
                  if (other.name != null)
                      return false;
              } else if (!name.equals(other.name))
                  return false;
              return true;
          }

          public static void main(String[] args) {
              // This is pretty much a login.
              Subject s = new Subject();
              final int theAmountOfPrincipalsTheUserHas = 100;
              for (int i = 0; i < theAmountOfPrincipalsTheUserHas; ++i) s.getPrincipals().add(new MyPrincipal("Principal #" + i));

              // This is after a cache hit, e.g. in an application server. We're copying the principals.
              final int theAmountOfInvocationsWithCacheHits = 5;
              for (int i = 0; i < theAmountOfInvocationsWithCacheHits; ++i) {
                  Subject copy = new Subject();
                  for (Principal e : s.getPrincipals()) copy.getPrincipals().add(e);
                  for (Object e : s.getPublicCredentials()) copy.getPublicCredentials().add(e);
                  for (Object e : s.getPrivateCredentials()) copy.getPrivateCredentials().add(e);
              }

              System.out.println(hashCodeInvocationCount.get() + " hashCode invocations");
              System.out.println(equalsInvocationCount.get() + " equals invocations");
          }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      One can replace the "javax.security.auth.Subject" implementation via the Java Endorsed Standards Override Mechanism https://docs.oracle.com/javase/7/docs/technotes/guides/standards/

            jnimeh Jamil Nimeh
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: