Search This Blog

Saturday, April 11, 2009

Weblogic JMS Standalone Multi-threaded Client Security

I have been building a helper library that provides some convenience functionalities and all of a sudden it started failing with authentication exceptions. Not on obtaining Jms artifacts but during the send of a message. Jeez! One part of me says let go, deal with it on Monday, the other part is hungry to find out why. No prizes for guessing which part of me won :-)
Weblogic client runtime stores credentials in a ThreadLocal of the Thread that creates an InitialContext to obtain a jms resource, i.e., equivalent of saying the thread that has the credentials can execute privileged operations on certain JMS resource but as other threads do not have the credentials and thus, they cannot. Weblogic's security will obtain the information from the ThreadLocal and use the credentials therein for send/receive operations.
Now, in a Jms environment, one would like to create multiple javax.jms.Session, javax.jms.MessageProducer and use multi-threading where appropriate, sharing the same JMS connection. Think of a Servlet container that has multiple threads. If Jms artifacts (Connection factories, Destinations) are obtained only once using a Singleton by one of the Container's thread's, then it is only this thread that can send/receive messages from a JMS destination if the destination has security enabled on the same. The operation of creating a Context places a javax.security.auth.Subject in the ThreadLocal of the thread creating the Context. If the context is closed, the Subject is removed.
Consider the following example where the jms artifacts are obtained in a single thread of execution and a message is sent in the same thread:

public void sameThreadCreateSend() throws Exception {
    InitialContext ctx = getInitialContext();
    ConnectionFactory connectionFactory = (ConnectionFactory) ctx
        .lookup("Connectionfactory");
    Destination destination = (Destination) ctx.lookup("test_queue");

    // Closing the context causes the send authentication exception
    ctx.close();

    Connection connection = connectionFactory.createConnection();

    connection.start();
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    MessageProducer producer = session.createProducer(destination);

    // Fails to send message as closing of the context removes the security information

    // from the Thread local. If the context is kept open, then it works great.
    producer.send(session.createTextMessage());
    System.out.println("Sent message when send was called on thread that created context...");
}

In the above example, the message gets sent successfully, What about a case where the Jms artifacts are created on one thread and a thread created from the creator thread tries to send a message? Will the permissions get propagated to the child thread? Look at the code below:

public void testInheritableThreadSend() throws Exception {
    InitialContext ctx = getInitialContext();

    // Current Thread has the security information
    final ConnectionFactory connectionFactory = (ConnectionFactory) ctx
        .lookup("connectionfactory");

    final Destination destination = (Destination) ctx.lookup("test_queue");

    // This thread will have the security as well due to Inheritable thread local
    Thread t = new Thread() {

      @Override public void run() {
        try {
          final Connection connection = connectionFactory.createConnection();
          connection.start();
          Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
          MessageProducer producer = session.createProducer(destination);
          producer.send(session.createTextMessage());
          System.out.println("Sent message using inheritable thread local...");
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }
    };

    t.start();
    t.join();
}

Yes the above code sent a message successfully as the credentials in the parent are propagated to the child thread via Inheritable thread local.
Now what about a case where the creation of the Jms artifacts are created on a thread that is not the parent of the thread that sends the message using those artifacts? Look at the sample below:

public void createOnSeparateThread() throws Exception {
    // First thread opens a context and had the credentials
    ResourceCreator c = new ResourceCreator();

    c.start();
    c.join();

    // Second thread is not a child of the first, thus does not have the security
    NonCreatorThreadRunner n = new NonCreatorThreadRunner(c);
    n.start();
    n.join();
  }

  private class ResourceCreator extends Thread {
    InitialContext ctx;
    Destination destination;
    Connection connection;

    @Override public void run() {
      try {
        ctx = Example.this.getInitialContext();
        connection = ((ConnectionFactory) ctx.lookup("connectionfactory"))
            .createConnection();
        destination = (Destination) ctx.lookup("test_queue");
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  private class NonCreatorThreadRunner extends Thread {
    private final ResourceCreator resourceCreator;

    public NonCreatorThreadRunner(ResourceCreator resourceCreator) {
      this.resourceCreator = resourceCreator;
    }

    @Override public void run() {
      try {
        resourceCreator.connection.start();
        Session session = resourceCreator.connection.createSession(false,
          Session.AUTO_ACKNOWLEDGE);
        MessageProducer producer = session.createProducer(resourceCreator.destination);
        producer.send(session.createTextMessage());
        System.out.println("Sent message when context was created on a separate thread...huh ???");
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
 }

So the above send operation failed. Why? Because the second thread does not have the security credentials in its ThreadLocal. Now what if we could use the security information from the first thread on the second thread? We can, using weblogic security classes and using the javax.security.auth.Subject to execute a javax.security.PrivilidgedAction.

 private class SubjectHoldingResourceCreator extends ResourceCreator {
    Subject subject;

     // Obtain the Subject from the thread
    @Override 
    public void run() {
      super.run();
      subject = weblogic.security.Security.getCurrentSubject();
    }
  }

  private class SubjectUsingNonCreatorThreadRunner extends Thread {

    private final SubjectHoldingResourceCreator creator;
    public SubjectUsingNonCreatorThreadRunner(SubjectHoldingResourceCreator creator) {
      this.creator = creator;
    }

    @Override public void run() {
      try {
        final Session session = creator.connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        final MessageProducer producer = session.createProducer(creator.destination);
        // Execute a secure action using the Subject from the previous thread.
        weblogic.security.Security.runAs(creator.subject, new PrivilegedAction<Void>() {
          public Void run() {
            try {
              producer.send(session.createTextMessage());
              System.out.println("Sent using Subject created on separate thread....");
            }
            catch (JMSException e) {
              e.printStackTrace();
            }
            return null;
          }
        });
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
}

And a test for the same:

public void testCreationOnSeparateUsingSubject() throws Exception {
    SubjectHoldingResourceCreator c = new SubjectHoldingResourceCreator();
    c.start();
    c.join();
    SubjectUsingNonCreatorThreadRunner r = new SubjectUsingNonCreatorThreadRunner(c);

    r.start();
    r.join();
}

The above example worked and sent the message. I have seen others facing a similar issue when using Spring classes such as JmsTemplate and DefaultMessageListenerContainer with Weblogic where this security issue bites them. Some folks have taken the route of initializing the Context before every send/receive of a message. This is clearly not optimal as the creation of an InitialContext is not cheap and can lead to degradation of performance.
My recommendation is to use the Proxy Pattern and have proxied versions of javax.jms.MessageProducer(s) and javax.jms.MessageConsumer(s) which ensure that calls to send/receive are executed within a PrivilidgedAction.
Look at my posting on Weblogic Jms Template to see how Proxies can be used.
The javax.security.auth.Subject can be obtained once in a multi-threaded environment for a particular user authenticator (userName/pass) and then be held for use by all the threads in the environment.
public Subject createSingleSubject(String providerUrl, String userName, String passsword) {
    Subject subject = new Subject();
    // Weblogic env class
    Environment env = new Environment();

    env.setProviderUrl(providerUrl);
    env.setSecurityPrincipal(userName);
    env.setSecurityCredentials(password);

    try {
      // Weblogic Authenticate class will populate and Seal the subject 
      // Always use the same subject in further calls, regardless of thread
      Authenticate.authenticate(env, subject);

      return subject;
    }
    catch (LoginException e) {
      throw new RuntimeException("Unable to Authenticate User", e);
    }
    catch (Exception e) {
      throw new RuntimeException("Error authenticating user", e);
    }
}

If the above method is invoked once to set up a Subject, then the same Subject can be used by Proxies to invoke PrivilidgedActions.
One thing I don't like is that on closing of the obtained Context, the ThreadLocal object is not cleared of all allocated permission related objects. It is only the Subject that is detached. I am looking for a way prevent the caching of security information in the ThreadLocal by WebLogic.
Some Links:
1. Spring JIRA
2. Weblogic Security Documentation

No comments: