2015/07/05
5 Jul, 2015

[Article] Introducing WSO2 Identity Server as Identity Provider for ActiveMQ

  • Mohanadarshan Vivekanandalingam
  • Technical Lead - WSO2

Contents

  1. Introduction
  2. An Introduction to WSO2 Identity Server
  3. Apache ActiveMQ Server
  4. High Level Design
  5. How to Communicate with Identity Server
  6. Writing Custom Plugin for ActiveMQ
  7. Configuring the Custom Plugin
  8. Tryout
  9. Conclusion

Prerequisites

Preliminary knowledge in WSO2 Identity Server and ActiveMQ server.

Introduction

ActiveMQ provides many ways to achieve security from various providers; this includes JAAS custom module, custom plugin and default authorization mechanism using a simple XML configuration file. For more information about ActiveMQ security mechanisms refer to link [2]. This custom plugin approach provides more flexibility and extensibility. With this approach a user integrate an Identity provider by intercepting the publisher-subscriber operations of an Apache ActiveMQ server. Then, we can use WSO2 Identity Server as an Identity Provider for ActiveMQ server due to its identity management features, extensibility, and stability. This article provides more advanced knowledge about ActiveMQ server, and explains how a user can implement a custom plugin for ActiveMQ and how to integrate with WSO2 Identity Server.

An Introduction to WSO2 Identity Server

WSO2 Identity Server enables enterprise architects and developers to improve the user’s experience by reducing identity provisioning time, guaranteeing secure online interactions, and delivering a reduced single sign-on (SSO) environment. It also decreases the burden of identity management and entitlement management by including role-based access control, attribute-based access control, fine-grain policy-based access control, and SSO bridging. Featuring full native multi-tenancy, WSO2 Identity Server can run on servers, in a private cloud, public cloud or hybrid cloud environment - all from the same software.

Apache ActiveMQ Server

Apache ActiveMQ is an open source message broker that is commonly used for messaging purposes. Apache ActiveMQ supports many cross language clients and protocols, and comes with easy-to-use enterprise integration patterns. Refer to link [1] for more information about Apache ActiveMQ.

High Level Design

Figure 01

Figure 1 illustrates a high-level design of a use case where WSO2 Identity Server is used as the identity provider for a third-party message broker like the ActiveMQ server. As shown here, the publisher sends messages to the message broker and subscribers (assume topic-based subscription) consume the messages. Here, users needs to authenticate and authorize to publish and subscribe messages. WSO2 Identity Server acts as an identity provider for ActiveMQ server. As shown above, when publishing or subscribing events, the ActiveMQ server (with the help of a custom plugin) connects to WSO2 Identity Server and carried out necessary authentication and authorization tasks.

How to Communicate with Identity Server

There are two main options; however, this article will focus on approach 2 below as discussed below:

  • Implement a custom plugin for handling security [3] - ActiveMQ provides a very flexible generic plugin mechanism. You can create your own custom plugins for just about anything, including the creation of a custom security plugin. Therefore, if you have requirements that cannot be met by implementing a JAAS module, writing a custom plugin is the way to go.
  • Implement a Java Authentication and Authorization Service (JAAS) login module [4] - The chances are that you’re already using JAAS in your Java applications. In this case, it’s only natural that you would try to reuse all that work for securing the ActiveMQ broker.

In this instance, however, we are going to implement a custom plugin to provide authentication and authorization through WSO2 Identity Server.

BrokerFilter class allows to intercept broker operation so features such as security can be implemented as a pluggable filter. You could find more information about BrokerFilter class in [5]. You can override other methods and improve your functionalities as well. As mentioned above, BrokerFilter class is a convenience class that implements the Broker interface. This interface defines all the main operations (e.g. addConnection, addSession, etc.) that your implementation can intercept. The class that extends BrokerFilter overrides any of the methods that are defined in the Broker interface so it can intercept the corresponding core engine's operations. This class allows to perform many broker-level operations, such as

  • Adding consumers to the broker
  • Adding producers to the broker
  • Committing transactions in the broker
  • Adding connections to the broker
  • Removing connections from the broker

Writing Custom Plugin to ActiveMQ

  1. Initially, we need to create a java project to write necessary logic for the custom ActiveMQ plugin. Create a maven project and include the following dependencies (as provided in attached pom.xml file below) to communicate with WSO2 Identity Server.
      
    <dependencies>        
            <dependency>
                <groupId>org.apache.axis2.wso2</groupId>
                <artifactId>axis2</artifactId>
                <version>1.6.1.wso2v4</version>
            </dependency>
            <dependency>
                <groupId>org.wso2.carbon</groupId>
                <artifactId>org.wso2.carbon.um.ws.api.stub</artifactId>
                <version>4.2.1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.ws.commons.axiom.wso2</groupId>
                <artifactId>axiom</artifactId>
                <version>1.2.11.wso2v1</version>
            </dependency>
            <dependency>
                <groupId>commons-codec.wso2</groupId>
                <artifactId>commons-codec</artifactId>
                <version>1.3.0.wso2v1</version>
            </dependency>
            <dependency>
                <groupId>commons-httpclient.wso2</groupId>
                <artifactId>commons-httpclient</artifactId>
                <version>3.1.0.wso2v1</version>
            </dependency>
    
            <dependency>
                <groupId>org.apache.httpcomponents.wso2</groupId>
                <artifactId>httpcore</artifactId>
                <version>4.3.0.wso2v1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.neethi.wso2</groupId>
                <artifactId>neethi</artifactId>
                <version>2.0.4.wso2v3</version>
            </dependency>
            <dependency>
                <groupId>org.wso2.securevault</groupId>
                <artifactId>org.wso2.securevault</artifactId>
                <version>1.0.0</version>
            </dependency>
            <dependency>
                <groupId>wsdl4j.wso2</groupId>
                <artifactId>wsdl4j</artifactId>
                <version>1.6.2.wso2v2</version>
            </dependency>
            <dependency>
                <groupId>org.apache.ws.commons.schema.wso2</groupId>
                <artifactId>XmlSchema</artifactId>
                <version>1.4.7.wso2v1</version>
            </dependency>
    </dependencies>
    
  2. Then, we need an implementation of the BrokerPlugin.class, which is used to expose the configuration of a plug-in and to install the plug-in into the ActiveMQ broker.

    Implementation of Plugin Logic

      
    import org.apache.activemq.broker.Broker;
    import org.apache.activemq.broker.BrokerPlugin;
    
    public class UserAuthenticationPlugin implements BrokerPlugin {
    
        String serverUrl;
        String username;
        String password;
        String keystoreFileLocation;
    
    
        public Broker installPlugin(Broker broker) throws Exception {
            return new UserAuthenticationBroker(broker, serverUrl, username, password, keystoreFileLocation);
        }
    
        public String getServerUrl() {
            return serverUrl;
        }
    
        public void setServerUrl(String serverUrl) {
            this.serverUrl = serverUrl;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getKeystoreFileLocation() {
            return keystoreFileLocation;
        }
    
        public void setKeystoreFileLocation(String keystoreFileLocation) {
            this.keystoreFileLocation = keystoreFileLocation;
        }
    }
    

    The installPlugin() method is used to instantiate the plug-in and return a new intercepted broker for the next plug-in in the chain. The UserAuthenticationPlugin class also contains getter and setter methods used to configure the UserAuthenticationBroker. These setter and getter methods are available via a Spring beans–style XML configuration in the ActiveMQ XML configuration file.

  3. Now, we need to implement the plugin logic. This involves overriding the methods of BrokerFilter class. BrokerFilter class provides huge flexibility with its many options for overriding, but we are only going to focus on some important methods.
     
    public class UserAuthenticationBroker extends BrokerFilter {
    
        Logger log = Logger.getLogger(UserAuthenticationBroker.class);
    
        //Necessary default properties required
        final String serverUrl;
        final String username;
        final String password;
        final String keystoreFileLocation;
        final String publisherKey = "publisher";
        final String subscriberKey = "subscriber";
        final String adminKey = "admin";
    
        //RemoteStoreManager Clients
        String remoteUserStoreManagerAuthCookie = "";
        ServiceClient remoteUserStoreManagerServiceClient;
        RemoteUserStoreManagerServiceStub remoteUserStoreManagerServiceStub;
    
         
        public UserAuthenticationBroker(Broker next, String serverUrl, String username,
                                        String password, String keystoreFileLocation) {
    
                }
    
    
        public void addConnection(ConnectionContext context, ConnectionInfo info)
                throws Exception {
    
               }
    
    
        public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
    
               }
    
        public void send(ProducerBrokerExchange producerExchange, Message messageSend)
                throws Exception {
             }
    }
    
  4. In UserAuthenticationBroker class, first we need to implement the constructor. Here, constructor helps to initiate necessary prerequisites like WSO2 Identity Server connection creation.
     
    public UserAuthenticationBroker(Broker next, String serverUrl, String username,
                                        String password, String keystoreFileLocation) {
            super(next);
            this.serverUrl = serverUrl;
            this.username = username;
            this.password = password;
            this.keystoreFileLocation = keystoreFileLocation;
            createAdminClients();
        }
    
  5. Then, let’s implement the overridden addConnection method. This method is called when a client is establishing a connection with the broker. This method is called when a publisher connects to the ActiveMQ server to publish events or when a subscriber connects to message broker to consume events. We can implement the necessary authentication scenarios inside this method, which means this plugin connects to WSO2 Identity Server and checks whether the connected user is a valid one or not.
  6.  
    public void addConnection(ConnectionContext context, ConnectionInfo info)
                throws Exception {
    
            boolean isValidUser = remoteUserStoreManagerServiceStub.authenticate(info.getUserName(), info.getPassword());
            if (isValidUser) {
                log.info("Valid user connection : " + info.getUserName());
                super.addConnection(context, info);
            } else {
                throw new SecurityException("Not a valid user connection");
            }
        }
    
  7. Above, we have implemented the addConnection method. Now, let’s implement the addConsumer method. This method is called when a subscriber tried to consume events.
    public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
            String[] roleNames;
            roleNames = remoteUserStoreManagerServiceStub.getRoleListOfUser(context.getUserName());
    
            for (String role : roleNames) {
                if (role.equalsIgnoreCase(subscriberKey) || role.equalsIgnoreCase(adminKey)) {
                    log.info("Valid subscriber registered : " + context.getUserName());
                    return super.addConsumer(context, info);
                }
            }
            throw new SecurityException("Not a valid user to subscribe or invalid topic name");
    }
    

    In this method, we connects to WSO2 Identity Server and get the role information related to the corresponding user. Then we check whether that connected user has the necessary role (“subscriber”) to consume events.

  8. Then, we focus on implementing the send method. This method is called when a publisher tries to publish events to the message broker. This method sends a message to the broker to use the specified destination. The destination specified in the message does not need to match the destination the message is sent to.
    public void send(ProducerBrokerExchange producerExchange, Message messageSend)
                throws Exception {
    
            String[] roleNames;
            String userName = producerExchange.getConnectionContext().getUserName();
    
            roleNames = remoteUserStoreManagerServiceStub.getRoleListOfUser(userName);
    
            for (String role : roleNames) {
                if (role.equalsIgnoreCase(publisherKey) || role.equalsIgnoreCase(adminKey)) {
                    super.send(producerExchange, messageSend);
                    return;
                }
            }
            throw new SecurityException("Not a valid user to publish events");
        }
    
  9. Finally, let’s discuss how we are going to communicate with WSO2 Identity Server. All WSO2 products expose their back-end functionalities as admin services [6]. These admin services are used by the UI components to communicate with the back-end as well. Then, we can also use these admin services to communicate with WSO2 Identity Server.

    In this scenario, we need to access the information related to the user. Then, we need to access the RemoteUserStoreManagerService [7]. We are going to use the corresponding RemoteUserStoreManagerServiceStub for this purpose. The source code for admin client code which we used earlier can be found here:

    private void createAdminClients() {
    
    /**
    * trust store path.  this must contains server's  certificate or Server's CA chain
    */
    
    String trustStore = keystoreFileLocation + File.separator + "wso2carbon.jks";
    
    /**
    * Call to https://localhost:9443/services/   uses HTTPS protocol.
    * Therefore we to validate the server certificate or CA chain. The server certificate is looked up in the
    * trust store.
    * Following code sets what trust-store to look for and its JKs password.
    */
    
    System.setProperty("javax.net.ssl.trustStore", trustStore);
    System.setProperty("javax.net.ssl.trustStorePassword", "wso2carbon");
    
    /**
    * Axis2 configuration context
    */
     ConfigurationContext configContext;
    
    try {
    /**
                 * Create a configuration context. A configuration context contains information for
                 * axis2 environment. This is needed to create an axis2 service client
                 */
                configContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null);
    
                /**
                 * end point url with service name
                 */
                String remoteUserStoreManagerServiceEndPoint = serverUrl + "/services/" + "RemoteUserStoreManagerService";
    
                /**
                 * create stub and service client
                 */
                remoteUserStoreManagerServiceStub = new RemoteUserStoreManagerServiceStub(configContext, remoteUserStoreManagerServiceEndPoint);
                ServiceClient remoteUserStoreManagerServiceClient = remoteUserStoreManagerServiceStub._getServiceClient();
                Options option = remoteUserStoreManagerServiceClient.getOptions();
    
                /**
                 * Setting a authenticated cookie that is received from Carbon server.
                 * If you have authenticated with Carbon server earlier,you can use that cookie, if
                 * it has not been expired
                 */
                option.setProperty(HTTPConstants.COOKIE_STRING, null);
    
                /**
                 * Setting basic auth headers for authentication for carbon server
                 */
                HttpTransportProperties.Authenticator auth = new HttpTransportProperties.Authenticator();
                auth.setUsername(username);
                auth.setPassword(password);
                auth.setPreemptiveAuthentication(true);
                option.setProperty(HTTPConstants.AUTHENTICATE, auth);
                option.setManageSession(true);
    
                remoteUserStoreManagerAuthCookie = (String) remoteUserStoreManagerServiceStub._getServiceClient().getServiceContext()
                        .getProperty(HTTPConstants.COOKIE_STRING);
    
            } catch (Exception e) {
                log.error(e);
            }
        }
    

Now, we have completed the necessary implementations related to custom ActiveMQ extension. As you may understand, we have overridden three methods to perform authentication and authorization. When a publisher or consumer connects to the message broker (ActiveMQ server), we connect to WSO2 Identity Server and validate whether it is a valid user and has the necessary roles to publish and consume messages.

To make this work in the above use case, we need to create users in the identity server and provide necessary permissions accordingly. As per the above implementation, a user would need to have a “publisher” or “admin” role to publish events to message broker. Same as consumer events, a user needs to have “subscriber” or “admin” roles.

Configuring the Custom Plugin

Configuring the created custom plugin to the ActiveMQ server is very simple and straightforward. It involves the following few steps:

  1. Build the created ActiveMQ plugin and create the plugin jar with dependency files.
  2. Copy the jar into the <ActiveMQ-Server>/lib folder
  3. Then edit the activemq.xml file which is in <ActiveMQ-Server>/conf and add below entries. These entries are used by the source code that were written above.
    <broker xmlns="https://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" useJmx="true">
    
    <plugins>
    	<bean id="userAuthenticationPlugin" class="UserAuthenticationPlugin" xmlns="https://www.springframework.org/schema/beans">
    		<property name="serverUrl">
    			<value>https://localhost:9445</value>
    		</property>
    		<property name="username">
    			<value>admin</value>
    		</property>
    		<property name="password">
    			<value>admin</value>
    		</property>
    		<property name="keystoreFileLocation">
    			<value>/home/mohan/Documents/msc/SPD/Priviledge_Code/wso2is-5.0.0/repository/resources/security</value>
    		</property>
    	</bean>
    </plugins> 
    ..
    ..
    ..
    
    </broker>
    
  4. Next, restart the ActiveMQ server to apply the changes.

Tryout

We have now completed all the necessary implementation and configurations. Follow the steps below to test the scenarios.

  1. Build the ActiveMQ plugin source code and copy the jar (with dependencies) to the <ActiveMQ-Server>/lib folder
  2. Copy the activemq.xml file to the /conf folder and update necessary entries like Keystore file path, WSO2 IS URL, etc.
  3. Start-up the ActiveMQ server
  4. Now, start the WSO2 Identity Server, create the necessary users, and provide corresponding roles (publisher, subscriber or admin) to them
  5. You can use the below attached sample, MQTT-based event publisher and consumer to publish and consume events from message broker. Note that to provide valid username and password for the user to connect to ActiveMQ

Conclusion

As an award winning identity solution, WSO2 Identity Server provides sophisticated security and identity management for various applications, services, and APIs. This article described WSO2 Identity Server’s user management aspect, admin APIs that are exposed by the identity server to perform user management tasks, and how it can be used in third-party servers like ActiveMQ. The step-by-step instructions will help you to write a custom plugin for ActiveMQ Server, provide necessary implementation logic for security, and help you understand how it can be integrated with ActiveMQ message broker. By following the same approach (admin services) discussed in this article, a user can integrate WSO2 Identity Server with other third-party servers or message brokers as an identity provider. The sample scenario discussed here can be improvised to match your use case and to implement a suitable solution.

References

[1] https://activemq.apache.org/

[2] https://activemq.apache.org/security.html

[3] https://mariuszprzydatek.com/2014/01/04/ip-based-authentication-plugin-for-activemq/

[4] https://docs.oracle.com/javase/7/docs/technotes/guides/security/jaas/JAASRefGuide.html

[5] https://activemq.apache.org/maven/apidocs/org/apache/activemq/broker/BrokerFilter.html

[6] https://docs.wso2.com/display/IS500/Calling+Admin+Services

[7] https://soasecurity.org/2013/12/10/user-role-management-with-wso2-identity-server-apis/

Article related files,

https://svn.wso2.org/repos/wso2/people/mohan/activq-article-artifacts

 

About Author

  • Mohanadarshan Vivekanandalingam
  • Technical Lead
  • WSO2