15 Understanding WebLogic JMS Security

Learn how to secure WebLogic JMS resources using thread-based and object-based security models.

Securing WebLogic JMS Resources

WebLogic JMS enables you to secure JMS resources by restricting access to JMS destinations.

The default root-level JMS security policy permits the Everyone group. Configure more specific policies on JMS modules, destinations, or destination operations to restrict access. This JMS policy does not bypass authentication, JNDI access controls, or administrative permissions. Ensure that the intended users satisfy the applicable policy conditions. See Overview of Securing WebLogic Resources for security roles and policies, and Java Messaging Service (JMS) Resources for JMS policy targets and operations.

JMS Security Terminology

WebLogic JMS uses either object-based security (OBS) or thread-based security to determine which user is checked when accessing a secured WebLogic JMS destination.

Understand some common terminology used in the context of JMS security before exploring the difference between the two security approaches:

  • Subject: A security identity represented by a set of principals and, where applicable, credentials.

  • Principal: An identity associated with a subject, such as a user or group.

  • Credentials: Information used to authenticate an identity, such as a password. These examples use a user name and password supplied through the applicable JNDI or JMS API.

Thread-based security implies that the subject that a secured WebLogic JMS destination checks is implicitly derived from the current caller's thread. Object-based security implies that the subject is implicitly derived from a subject stored in the object the caller is using to make its JMS call. In general, WebLogic security is thread based. The following sections explore both approaches.

Understanding Thread-Based Security on Clients and Servers

By default, access to secured WebLogic JMS resources leverages thread-based security. This gives WebLogic JMS security behavior parity with Jakarta EE's general security model for EJBs, web applications, and RMI.

With thread-based security, WebLogic JMS send and consume authorization uses the calling thread's subject. Supplying a user name and password to ConnectionFactory.createConnection() or ConnectionFactory.createContext() does not, by itself, replace that subject for subsequent thread-based JMS operations. Object-based security and the server-managed patterns described later in this chapter use different identity-selection rules.

Thread-Based Security for Server Applications

For server-side applications, there are multiple ways to set EJB and Web application thread's subject or role. See Securing Enterprise JavaBeans (EJBs). To override thread-based JMS security checking behavior for server-side WebLogic JMS send and consume calls, see Enabling Object-Based Security on Server Applications.

Thread-Based Security for Client Applications

For standalone clients using weblogic.jndi.WLInitialContextFactory, creating an InitialContext with credentials establishes the context's subject on the calling thread. Specify the credentials using Context.SECURITY_PRINCIPAL and Context.SECURITY_CREDENTIALS. Close the context on the thread that created it; when contexts are nested, close them in reverse creation order to restore the previous subject correctly.

The following method-body fragment uses caller-supplied url, username, and password values. Obtain credentials through the application's configuration mechanism; do not embed real passwords in source.

java.util.Hashtable<String, Object> env = new java.util.Hashtable<>();
env.put(javax.naming.Context.PROVIDER_URL, url);
env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
        "weblogic.jndi.WLInitialContextFactory");
env.put(javax.naming.Context.SECURITY_PRINCIPAL, username);
env.put(javax.naming.Context.SECURITY_CREDENTIALS, password);
javax.naming.InitialContext ic = new javax.naming.InitialContext(env);
try {
    // Perform lookups and thread-based JMS operations here.
} finally {
    ic.close();
}

If a client creates an InitialContext object without specifying credentials, then:

  • The subject already on the current thread is unchanged.
  • If there is no subject on the thread, then it is assumed to be an anonymous subject.

If a client program needs to transfer a thread's subject to a different thread than the thread used to create an InitialContext object, the client program can use security APIs to store the current subject of the thread and then subsequently use this cached subject in a different thread.

Capture the authenticated subject with weblogic.security.Security.getCurrentSubject() while the intended context is active. Pass that captured subject to the helper on the thread that performs the operation. The helper propagates PrivilegedActionException; its cause is the checked exception thrown by the action, not necessarily an authentication error.

import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import javax.security.auth.Subject;
import weblogic.security.Security;

public final class SubjectActionExample {
    public static Object doSomethingAsSubject(Subject subject)
            throws PrivilegedActionException {
        return Security.runAs(subject, (PrivilegedExceptionAction<String>) () -> {
            // Perform the operation using this subject.
            return "OK";
        });
    }
}

This example code pattern can also be used to switch the subject on a thread for the use case where one single JMS client communicates with two domains at the same time, see Programming Pattern for a Single JMS Client Communicating With Two WebLogic Domains.

Sometimes it is useful to get an anonymous subject:
 javax.security.auth.Subject anon = new javax.security.auth.Subject();

To override thread-based JMS security checking behavior for client-side WebLogic JMS send and consume calls, see Enabling Object-Based Security on Clients.

Understanding Object-Based Security

WebLogic JMS clients can optionally use a simpler security model called object-based security (OBS) instead of thread-based security. This option was introduced in WebLogic 12.2.1.3 and is useful for multithreaded clients which otherwise need extra code to transfer thread-based security subjects between threads.

The following sections explain how to enable object-based security:

Enabling Object-Based Security on Clients

Enabling object-based security (OBS) causes message send and consume security checks to be based on credentials specified during JMS client initialization instead of on the calling thread's subject.

Enabling OBS requires using an OBS JNDI initial context. Any WebLogic JMS senders or consumers that are created using an OBS connection factory that is obtained from an OBS initial context will, by default, implicitly use the credential that is associated with the OBS initial context instead of the subject that is associated with the current sender or consumer thread. In addition, if a user name and password credential is passed as parameters to a standard JMS createConnection() or createContext() call on an OBS connection factory, then this new credential supersedes the credential that is associated with the OBS initial context and the new credential will be used for sends or consumes on that connection or JMS context.

Steps to enable OBS on a JMS client's senders and consumers:

  1. Create a javax.naming.InitialContext object:
    1. Specify a Context.INITIAL_CONTEXT_FACTORY property with string value weblogic.jms.WLInitialContextFactory instead of weblogic.jndi.WLInitialContextFactory. This returns an OBS initial context, and is the only step required for the majority of applications that want to use JMS client OBS.
    2. (Optional) Specify user name and password credentials using the standard JNDI Context.SECURITY_PRINCIPAL and Context.SECURITY_CREDENTIALS properties (same as what you do for a non OBS context). This will become the default OBS credential that is associated with the OBS initial context. If these properties are not specified, then the credential associated with the OBS initial context is determined by the weblogic.jndi.securityPolicy setting.
    3. (Optional) Specify an initial context property named weblogic.jndi.securityPolicy with string value ObjectBased or ObjectBasedHybrid. This fine tunes behavior when no user name and password credential is specified using the initial context Context.SECURITY_PRINCIPAL and Context.SECURITY_CREDENTIALS properties.
      • ObjectBased (the default): If no user name is supplied when the OBS initial context is created, use an anonymous subject for its JNDI lookups and, by default, for JMS operations through factories obtained from that context.

      • ObjectBasedHybrid: If no user name is supplied when the OBS initial context is created, capture the current thread's subject for its subsequent JNDI lookups and, by default, for JMS operations through factories obtained from that context.

  2. Use the OBS initial context created in step (1) to look up WebLogic JMS connection factories (same as you look up a connection factory for a non OBS context), which will then implicitly be OBS JMS connection factories. Any JMS senders or consumers that are created using an OBS JMS connection factory will use OBS.
  3. (Optional) Override the OBS credential associated with the OBS JMS connection factory by passing a user name and password into the JMS standard createConnection() or createContext() call that is used to create a JMS connection or context.
Object-Based Security Limitations on Clients

Listed below are some of the limitations of OBS:

  • Use lookup(String) to obtain supported WebLogic JMS connection factories and destinations, and close() to release the OBS context. lookup(Name) and unsupported naming operations throw javax.naming.OperationNotSupportedException. OBS lookups do not support JMS resources referenced through a foreign JMS server. Use a separate non-OBS context for naming operations or resource types that the OBS context does not support.

  • An OBS initial context is supported only on WebLogic clients and is not supported on WebLogic Servers. An exception is thrown when attempting to use such a context in combination with WebLogic JMS server facilities like bridges, MDBs, or resource references. This restriction exists because these server-side JMS facilities already have their own security handling that provides similar semantics to OBS.
  • When an OBS initial context is created, the initial context's user is not placed on the current thread. This differs from a weblogic.jndi.WLInitialContextFactory context.

Enabling Object-Based Security on Server Applications

Learn how to enable object-based security (OBS) for inbound and outbound JMS applications:

Object-Based Security for Inbound JMS Applications
Server applications that make inbound WebLogic JMS calls, such as Message Driven Beans, are implicitly object based. Credentials or roles for these applications to access incoming JMS messages are supplied in one of the following ways:
  • Supplied with the application itself.

    Note:

    Oracle does not recommend using this method.
  • Defined on the service itself (Messaging Bridges allow you to configure user name or password).

    Note:

    Oracle does not recommend using this method.
  • Just as for the outbound case, specified using a foreign JMS Server in a JMS system resource module that maps a JMS resource into JNDI.
As a best practice, use the foreign JMS Server method for Message Driven Beans, Messaging Bridges, and outbound JMS, as this:
  • Centralizes credential configuration outside application code. The relevant credential attributes are dynamically configurable; existing connections and resource pools remain subject to their refresh and lifecycle rules.

  • Applies to almost all inbound and outbound use cases so it is useful as a way to centrally manage your JMS credentials.
See FAQs: Integrating Remote JMS Providers and Enhanced Support for Using WebLogic JMS with EJBs and Servlets.
Object-Based Security for Outbound JMS Applications

Server applications that make outbound WebLogic JMS calls can achieve an object-based security pattern that supersedes the current security subject on the current thread. For this to work, ensure the following:

  1. Configure a foreign JMS server and a foreign connection-factory mapping from the remote factory's JNDI name to a local JNDI name. Configure the foreign server's JNDI properties and JNDI credential for the remote naming lookup. If connection authentication requires a user name and password, configure those separately on the foreign connection factory. Ensure that the resulting identity has the required remote permissions.

  2. In the application code, use a JMS resource reference or inject a JMS context that references the local JNDI name of the JMS connection factory.

    Note:

    It is a general best practice for server applications to use resource references or JMS context injection to reference a JMS connection factory regardless of whether you need an object-based security pattern or a thread-based security pattern.

    With this server-managed pattern, WebLogic uses the configured identity when creating and using the managed JMS resources. JNDI lookup credentials and JMS connection credentials serve different purposes; a successful naming lookup alone does not establish authorization for sends or receives.

Understanding Cross-Domain Security

By default, WebLogic Server security is thread-based, which means operations are performed as the "user" that is associated with the current thread. See Understanding Thread-Based Security on Clients and Servers. When an application uses JMS to communicate with multiple domains, the application needs to ensure that the correct user is used when it communicates with each of the domains that are involved.

Application authentication to a remote JMS provider and security for internal communication between WebLogic domains are separate concerns. Configure the inter-domain security required by the participating services and topology. When using WebLogic Cross-Domain Security, enable it and configure the required cross-domain users and credential mappings for the participating domain pairs. See Configuring Cross-Domain Security in Administering Security for Oracle WebLogic Server. The following Cross-Domain Security Guidelines identify service-specific considerations.

Note:

The examples provided are example code for illustrating security patterns, not working code that conforms to all JMS coding best practices. See Configuration Best Practices.

Cross-Domain Security Guidelines

Follow these guidelines while configuring cross-domain security:

  • If your Message-Driven Bean (MDB) deployment and the JMS destination that the MDB listens on, are in different WebLogic domains, you need to consider configuring cross-domain security between the two domains in combination with a foreign JMS server. For more details, see Using MDBs With Cross Domain Security in Developing Message-Driven Beans for Oracle WebLogic Server.

  • For applications or messaging bridges participating in global transactions across WebLogic domains, configure compatible transaction-security settings in all participating domains. The JTA configuration supports Cross Domain Security or the applicable Security Interoperability Mode; follow the JTA guide for the required combination. This applies to messaging bridges using exactly-once QoS across WebLogic domains. See Configuring Cross Domain Security in Developing JTA Applications for Oracle WebLogic Server.

  • If your application in one WebLogic domain accesses WebLogic Server JMS distributed destinations in another WebLogic domain, you need to consider configuring cross-domain security between the two domains. In addition, the application should use a foreign JMS server in combination with a standard Jakarta EE resource reference to reference the remote destination.

  • If your application uses WebLogic Server JMS store-and-forward to forward messages from one WebLogic domain to another WebLogic domain, you need to consider configuring cross-domain security between the two domains. See SAF and Cross Domain Security in Administering the Store-and-Forward Service for Oracle WebLogic Server.

  • If a single JMS client communicates with multiple domains at the same time, the application code may need to manage switching users back and forth corresponding to the domain that the application talks to using the same thread. See the runAs helper method described in Thread-Based Security for Client Applications.

  • If using a foreign JMS server in a cross-domain scenario without combining this feature with a Jakarta EE resource reference, MDB, or messaging bridge, a built-in helper API can be used to correctly handle security credential propagation.

    Note:

    It is a best practice to use a Jakarta EE resource reference, MDB, or messaging bridge instead of a helper API.

Programming Pattern for a Single JMS Client Communicating With Two WebLogic Domains

A single JMS client that is not running on a WebLogic Server may need to communicate with two (or more) WebLogic domains at the same time while specifying a valid thread-based security subject for each.

The following pseudocode illustrates the identity-selection error. A thread-based client must use the appropriate subject for each domain.

Create a thread-based naming context for domain 1.
Create the request consumer while domain 1's subject is current.
Create a thread-based naming context for domain 2 on the same thread.
Attempt to receive from domain 1 while domain 2's subject is current.

The receive can fail if the current subject is not authorized in domain 1. The problem is the selected identity, not simply the fact that two domains are involved.

Capture each domain's authenticated subject while its naming context is active, and use Security.runAs for operations in that domain. Creating JMS resources under a subject does not eliminate the need to use the appropriate subject for subsequent thread-based JMS operations. See Thread-Based Security for Client Applications. The following standalone-client example handles one TextMessage request and sends an illustrative text response. It uses AUTO_ACKNOWLEDGE and does not make request receipt and response sending atomic; it is not an exactly-once forwarding solution. The caller supplies correctly configured naming environments and JNDI names. Messaging failures propagate to the caller.

import jakarta.jms.ConnectionFactory;
import jakarta.jms.Destination;
import jakarta.jms.JMSConsumer;
import jakarta.jms.JMSContext;
import jakarta.jms.TextMessage;
import java.security.PrivilegedExceptionAction;
import java.util.Hashtable;
import javax.naming.InitialContext;
import javax.security.auth.Subject;
import weblogic.security.Security;

public final class TwoDomainExample {
    // env1 and env2 contain independently configured JNDI URLs and credentials,
    // and use weblogic.jndi.WLInitialContextFactory (thread-based security).
    public static void exchangeOnce(
            Hashtable<String, Object> env1, Hashtable<String, Object> env2,
            String requestFactoryName, String requestDestinationName,
            String responseFactoryName, String responseDestinationName)
            throws Exception {
        InitialContext naming1 = new InitialContext(env1);
        try {
            Subject subject1 = Security.getCurrentSubject();
            ConnectionFactory factory1 =
                    (ConnectionFactory) naming1.lookup(requestFactoryName);
            Destination requestDestination =
                    (Destination) naming1.lookup(requestDestinationName);
            try (JMSContext jms1 = factory1.createContext();
                 JMSConsumer consumer = jms1.createConsumer(requestDestination)) {
                InitialContext naming2 = new InitialContext(env2);
                try {
                    Subject subject2 = Security.getCurrentSubject();
                    ConnectionFactory factory2 =
                            (ConnectionFactory) naming2.lookup(responseFactoryName);
                    Destination responseDestination =
                            (Destination) naming2.lookup(responseDestinationName);
                    try (JMSContext jms2 = factory2.createContext()) {
                        String requestText = (String) Security.runAs(subject1,
                                (PrivilegedExceptionAction<String>) () -> {
                                    TextMessage message =
                                            (TextMessage) consumer.receive(1000);
                                    return message == null ? null : message.getText();
                                });
                        if (requestText != null) {
                            Security.runAs(subject2,
                                    (PrivilegedExceptionAction<Void>) () -> {
                                        jms2.createProducer().send(responseDestination,
                                                "Response: " + requestText);
                                        return null;
                                    });
                        }
                    }
                } finally {
                    naming2.close();
                }
            }
        } finally {
            naming1.close();
        }
    }
}

Programming Patterns for Using a Foreign JMS Server Between Two WebLogic Domains

Note:

Use the managed credential handling provided by Jakarta EE resource references, messaging bridges, or MDBs where applicable. The manual pattern below is for applications that do not use those facilities. Configure application permissions and any required inter-domain service security separately.

A foreign JMS server maps local JNDI names to remote JMS resources. Credentials used to resolve the foreign JNDI reference apply to that lookup; do not assume that they become the calling thread's identity for later JMS operations. A direct lookup outside managed resource-reference, MDB, or messaging-bridge handling can therefore require explicit identity management. Subsequent operations can fail if the effective subject is not authorized for the remote operation, even when the lookup credentials are correct.

The following bounded standalone-client example waits for an initial destination-availability notification before using RegistrationHandle.runAs(). The supplied JNDI environment identifies the domain containing the foreign mappings; the destination and connection-factory names must use the same foreign-server credential configuration. The 30-second wait is an example application timeout, not a product default. An availability notification establishes initial readiness only; the destination can become unavailable afterward, and subsequent operations can fail. For ongoing availability and recovery handling, see Advanced Programming with Distributed Destinations Using the JMS Destination Availability Helper API.

For this example, the identity established by the foreign server’s JNDI credentials must be authorized to create the remote JMS context and send to the destination. The sample does not pass separate JMS connection credentials or automatically apply a different username and password configured on the foreign connection factory.

import jakarta.jms.ConnectionFactory;
import jakarta.jms.Destination;
import jakarta.jms.JMSContext;
import jakarta.jms.JMSProducer;
import java.security.PrivilegedExceptionAction;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.naming.InitialContext;
import weblogic.jms.extensions.DestinationAvailabilityListener;
import weblogic.jms.extensions.DestinationDetail;
import weblogic.jms.extensions.JMSDestinationAvailabilityHelper;
import weblogic.jms.extensions.RegistrationHandle;

public final class ForeignDestinationExample {
    // Both names map through the same foreign server/security configuration.
    public static void send(Hashtable<String, Object> environment,
            String destinationName, String factoryName,
            String text, int messageCount) throws Exception {
        CountDownLatch ready = new CountDownLatch(1);
        AtomicReference<Exception> failure = new AtomicReference<>();
        DestinationAvailabilityListener listener =
                new DestinationAvailabilityListener() {
                    public void onDestinationsAvailable(String name,
                            List<DestinationDetail> destinations) {
                        if (!destinations.isEmpty()) ready.countDown();
                    }
                    public void onDestinationsUnavailable(String name,
                            List<DestinationDetail> destinations) {
                        // This bounded example does not implement recovery.
                    }
                    public void onFailure(String name, Exception exception) {
                        failure.compareAndSet(null, exception);
                        ready.countDown();
                    }
                };
        RegistrationHandle handle = JMSDestinationAvailabilityHelper.getInstance()
                .register(environment, destinationName, listener);
        try {
            if (!ready.await(30, TimeUnit.SECONDS)) {
                throw new IllegalStateException("Timed out waiting for the destination");
            }
            if (failure.get() != null) {
                throw new IllegalStateException("Destination registration failed",
                        failure.get());
            }
            InitialContext naming = new InitialContext(environment);
            try {
                Destination destination = (Destination) naming.lookup(destinationName);
                ConnectionFactory factory = (ConnectionFactory) naming.lookup(factoryName);
                handle.runAs((PrivilegedExceptionAction<Void>) () -> {
                    try (JMSContext context = factory.createContext()) {
                        JMSProducer producer = context.createProducer();
                        for (int i = 0; i < messageCount; i++) {
                            producer.send(destination, text + i);
                        }
                    }
                    return null;
                });
            } finally {
                naming.close();
            }
        } finally {
            handle.unregister();
        }
    }
}