Tuesday, April 13, 2010

ADF Security: Playing with Weblogic APIs on Authentication Providers

As promised in my last post, in this post I will share with you how to play with the Weblogic API through JMX to handle tasks such as creating users, creating roles, assigning users to roles, and letting users change their own password. This post will clear out the gray areas in implementing user subscription or registration forms and the generation of salted-hashed passwords that can be recognized by weblogic. This post assumes that you have already properly set-up your SQLAuthenticator provider in weblogic.

Though this post is specific to SQLAuthenticator, the concept demonstrated herein is applicable to the Weblogic API as a whole. Just changing the MBEAN_INTERFACE constant to "weblogic.security.providers.authentication.DefaultAuthenticatorMBean", this post already applies to the DefaultAuthenticator.

Prerequisites

  • Setup the SQLAuthenticator required schema
  • Create a Datasource in weblogic console that points to the schema in step 1.
  • Setup SQLAuthenticator provider in weblogic console.

Implementation

The key to playing with Weblogic APIs is through JMX (Java Management Extensions). I cant tell through experience that understanding JMX is not for the faint-hearted, but if you got it, you will realize how simple it is. Below is a sample adapter class that I device so that I will deal with JMX only once, and the rest of my application that needs to access the weblogic API will just point to my adapter class.
package soadev.adapters;

import java.io.Serializable;
import java.io.IOException;
import java.util.Hashtable;
import javax.management.Descriptor;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.naming.Context;

public class SQLAuthenticatorAdapter implements Serializable {
    private static final String MBEAN_INTERFACE = "weblogic.security.providers.authentication.SQLAuthenticatorMBean";
    private MBeanServerConnection connection;
    private JMXConnector connector;
    private ObjectName providerON;
    
    
    public void createUser(String username, String password,
                           String description) throws Exception {
        connection.invoke(providerON, "createUser",
                          new Object[] { username, password, description },
                          new String[] { "java.lang.String",
                                         "java.lang.String",
                                         "java.lang.String" });
    }
    
    public void createGroup(String groupName, String description) throws Exception {
        connection.invoke(providerON, "createGroup",
                          new Object[] { groupName, description },
                          new String[] { "java.lang.String",
                                         "java.lang.String" });
    }
    
    public void addMemberToGroup(String groupName, String username)throws Exception{
        connection.invoke(providerON, "addMemberToGroup",
                          new Object[] { groupName, username },
                          new String[] { "java.lang.String", "java.lang.String" });
    }

    public void changeUserPassword(String username, String oldPassword,
                                   String newPassword) throws Exception {
        connection.invoke(providerON, "changeUserPassword",
                          new Object[] { username, oldPassword, newPassword },
                          new String[] { "java.lang.String",
                                         "java.lang.String",
                                         "java.lang.String" });
    }
    
    public boolean isMember(String parentGroupName, String memberUserOrGroupName, boolean recursive)throws Exception{
        return (Boolean) connection.invoke(providerON, "isMember",
                          new Object[] { parentGroupName, memberUserOrGroupName, recursive },
                          new String []{"java.lang.String", "java.lang.String", "java.lang.Boolean"});
    }
    
    private ObjectName getAuthenticationProviderObjectName(String type)throws Exception{
       
            ObjectName defaultRealm = getDefaultRealm();
            ObjectName[] atnProviders =
                (ObjectName[])connection.getAttribute(defaultRealm,
                                                      "AuthenticationProviders");
            ObjectName MBTservice =
                new ObjectName("com.bea:Name=MBeanTypeService,Type=weblogic.management.mbeanservers.MBeanTypeService");
            for (int p = 0; atnProviders != null && p < atnProviders.length;
                 p++) {
                ObjectName provider = atnProviders[p];
                ModelMBeanInfo info =
                    (ModelMBeanInfo)connection.getMBeanInfo(provider);
                Descriptor desc = info.getMBeanDescriptor();
                String className =
                    (String)desc.getFieldValue("interfaceClassName");
                String[] mba =
                    (String[])connection.invoke(MBTservice, "getSubtypes",
                                                new Object[] { type },
                                                new String[] { "java.lang.String" });
                for (int i = 0; i < mba.length; i++) {
                    if (mba[i].equals(className)) {
                        return provider;
                    }
                }
            }
            return null;
    }
    private ObjectName getDefaultRealm() throws Exception {
        ObjectName service =
            new ObjectName("com.bea:Name=DomainRuntimeService,Type=weblogic.management.mbeanservers.domainruntime.DomainRuntimeServiceMBean");
        ObjectName domainMBean =
            (ObjectName)connection.getAttribute(service, "DomainConfiguration");
        ObjectName securityConfiguration =
            (ObjectName)connection.getAttribute(domainMBean,
                                                "SecurityConfiguration");
        ObjectName defaultRealm =
            (ObjectName)connection.getAttribute(securityConfiguration,
                                                "DefaultRealm");
        return defaultRealm;
    }
    public void connect(){
        String hostname = "localhost";
        String username = "weblogic";
        String password = "weblogic1";
        int port = 7101;
        connect(hostname, username, password, port);
    }
    
    public void connect(String hostname, String username, String password, int port){
        try {       
            String protocol = "t3";
            String jndi =
                "/jndi/weblogic.management.mbeanservers.domainruntime";
            JMXServiceURL serviceURL =
                new JMXServiceURL(protocol, hostname, port, jndi);
            Hashtable env = new Hashtable();
            env.put(Context.SECURITY_PRINCIPAL, username);
            env.put(Context.SECURITY_CREDENTIALS, password);
            env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES,
                    "weblogic.management.remote");
            env.put("jmx.remote.x.request.waiting.timeout", new Long(10000));
            connector = JMXConnectorFactory.connect(serviceURL, env);
            connection = connector.getMBeanServerConnection();
            providerON = getAuthenticationProviderObjectName(MBEAN_INTERFACE);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
    public void close(){
        try {
            connector.close();
        } catch (IOException ioe) {
            throw new RuntimeException(ioe);
        }
    }
}

Please update the connect() method with your own credentials and port information if necessary.

Below is a sample java client that utilize this adapter:
package soadev.client;

import soadev.adapters.SQLAuthenticatorAdapter;

public class SQLAuthenticatorAdapterClient {
    private SQLAuthenticatorAdapter adapter = new SQLAuthenticatorAdapter();
    public static void main(String[] args) {
    SQLAuthenticatorAdapterClient client = new SQLAuthenticatorAdapterClient();
       try {
           client.connect();
            client.testCreateUser();
            client.testCreateGroup();
            client.testAddMemberTopGroup();
            client.close();
        } catch (Exception e) {
            // TODO: Add catch code
            client.close();
            e.printStackTrace();
        }
    }
    
    public void testCreateUser()throws Exception{
        String username = "pino";
        String password = "password1";
        String displayName = "Pino SOADEV";
        adapter.createUser(username, password, displayName);
    }
    
    public void testCreateGroup()throws Exception{
        String groupName = "SOADevGroup";
        String description = "This is a especial group created through SQLAuthenticatorAdapter";
        adapter.createGroup(groupName, description);
    }
    
    public void testAddMemberTopGroup()throws Exception{
        String username = "pino";
        String groupName = "SOADevGroup";
        adapter.addMemberToGroup(groupName, username);
    }
    
    public void close(){
        adapter.close();
    }
    public void connect(){
        adapter.connect();
    }
}

Be sure that "Weblogic 10.3 Thin-Client" is included in the libraries and classpath of your client project.

After running the client above, you will see that user "pino", group "SOADevGroup, and the assignment of user "pino" to group "SOADevGroup" were persisted respectively in the USERS, GROUPS, and GROUPMEMBERS tables.

Prototype

//TODO

Conclusion

This post demonstrates how easy it is to play with Weblogic API so that you have something as reference to jump-start your own implementation. More interfaces can be implemented by referring to the resources below. Be wary the examples above do not exemplify proper exception and concurrency handling.

Cheers!

Pino

Resources

26 comments:

  1. Hi, thanks for the great articles with code. Can you provide an end-to-end example (with code) regarding how to call pl/sql procedures, get the resultset and access the data from backing bean in adf?

    ReplyDelete
  2. Pino,
    Thanks for the great tutorial!

    I have a problem on my login screen (SQLAuthenticator using Java Tutorial from Oracle)
    Article: Enabling ADF Security in a Fusion Web Application
    Topic: 30.7.2.1 Creating Login Code for the Backing Bean



    The problem is, I have 2 SQLAuthenticator Providers and the Subject mysubject is using the first one in the Weblogic topology, I think this is a bug, I need to set what Authenticator Provider I will be using when logging on my application...

    Can you help me out with that???

    Thanks!

    ReplyDelete
  3. Hi Renan,
    You don't need to modify anything on the login code except for line number 12 where you need to replace the "...success_url=/faces" with your actual home page like "...success_url=/faces/home.jspx". The configuration changes needs to be done on the weblogic console, where you will set your SQLAuthenticators as "SUFFICIENT" and set the DefaultAuthenticator to a value other than "REQUIRED".

    Pino

    ReplyDelete
  4. And if I got two SQLAuthenticators with "SUFFICIENT"?
    Who Weblogic will choose?

    ReplyDelete
  5. It will depend on the order you listed them on the list of providers.

    ReplyDelete
  6. This is my problem in the past 2 weeks, Weblogic simply doesn't work with 2 applications running on different Providers...
    It will get confused when you got 2 users with same name running on different providers.
    I tried to use 2 servers too but each server uses the same realm and the problem persist.

    ReplyDelete
  7. http://fusionsecurity.blogspot.com/2010/04/by-request-multiple-realms-in-weblogic.html

    Does this help? I did this for a customer, but it seems to be a common use case

    ReplyDelete
  8. Hi Pino,

    I am getting the following error after implementing the above:

    javax.management.RuntimeMBeanException: weblogic.security.providers.authentication.DBMSSQLAuthenticatorDelegateException: [Security:090286]Error adding user pino

    ReplyDelete
  9. hi AjAin,

    Were you able to successfully set-up SQLAuthenticator and create users manually in weblogic console? Ensure first that you can add users manually, before you test the programmatic way.
    Regards,
    pino

    ReplyDelete
  10. hi,

    i got things sorted out...i was using SQLAuthenticatorMBean insted of DefaultAuthenticatorMBean.

    now user and groups are getting created but when i check them in tables in database..user is not showing there...also when i again run the client again it gives me error saying user already exist. My SQLAuthenticator name is TestAuth2.
    and also there are two other providers existing just above mine:
    DefaultAuthenticator(WebLogic Authentication Provider) and DefaultIdentityAsserter (WebLogic Identity Assertion provider)

    is there any issue with that.

    ReplyDelete
  11. Hi Pino,

    You are correct...i am not able to create users manually in weblogic console.but i am not able to understand why i cannot create user as i have created sqlauthenticator as told in the given post(http://biemond.blogspot.com/2008/12/using-database-tables-as-authentication.html)...also i am able to insert values in database, using insert statement defined in sqlauthenticator.

    Error!!!
    Console encountered the following error weblogic.security.providers.authentication.DBMSSQLAuthenticatorDelegateException: [Security:090286]Error adding user mukgupta


    Please Help!!!

    ReplyDelete
  12. Hi AjAin,

    Were you able to read this post: http://soadev.blogspot.com/2010/04/sqlauthenticator-simply-best.html

    I suggest also that you post this on OTN Forum.
    regards,
    Pino

    ReplyDelete
  13. Hi Pino,

    Thanks for your prompt replies,I got the answer to the above question...the above problem was due to the rights were not there to the schema owner.

    also one thing i want to know is that sql authenticator is the right approach to create and validate users ? As in case of huge applications there would be thousands of users and it may burden the application sever as users will get created on application server as well.

    Or i should go for custom authentication which is done directly by database.

    Thanks in Advance!!!

    ReplyDelete
  14. Hi,
    Thanks a lot for the use full post... We are really exited when we find this after a two days struggle...

    Now, we are struggling in resetting the user's password.
    The scenario is ..
    We ase using SQLAuthenticator...And Hashed Passwords(encrypted)...But how can we reset...If User has forgotten the oldPassword...
    Can you please help us?

    ReplyDelete
  15. Hi Pino,
    Thanks for the great article on Weblogic security.

    I followed all the step in the above article.
    When I am trying to run the SQLAunthenticatorAdapterClient.Java in Jdeveloper, It is giving the following error:

    Exception in thread "main" java.lang.NullPointerException at com.cloudebs.usersapi.view.SQLAuthenticatorAdapter.close(SQLAuthenticatorAdapter.java:111) at com.cloudebs.usersapi.view.SQLAuthenticatorAdapterClient.close(SQLAuthenticatorAdapterClient.java:43) at com.cloudebs.usersapi.view.SQLAuthenticatorAdapterClient.main(SQLAuthenticatorAdapterClient.java:18)
    Process exited with exit code 1.

    Any help will be appreciated.
    Regards

    ReplyDelete
    Replies
    1. you have not included weblogic 10.3 in your library class path first include and your problem will be solved

      Delete
  16. thanks a lot this artical is realy use full for me i have successfully configured all these things my user is created I am very happy

    ReplyDelete
  17. hi
    when i Run SQLAuthenticatorAdapterClient
    show this error

    java.lang.RuntimeException: java.lang.ClassCastException: [Ljavax.management.ObjectName; cannot be cast to javax.management.ObjectName

    Is anyone help me?

    ReplyDelete
    Replies
    1. This is a class loading issue... be sure to have the proper libraries set.

      Delete
  18. Hi Pino,
    Thanks for the great article...it is Really Useful..
    I have successfully configured all the settings, and successfully created the users..but I want to know if there is any way to call those methods from a BPM process..

    I appreciate your help..
    Thanks & Best Regards,,

    ReplyDelete
    Replies
    1. Hi Ola,
      Since the above classes are just POJOs then you can use Spring Context in Oracle SOA Suite.
      http://docs.oracle.com/cd/E15586_01/integration.1111/e10224/sca_spring.htm

      Delete
  19. Hi Pino,,
    Thanks a lot for your concern & reply..
    Is there any way to use those POJOs (may be as a JAR file) directly in a BPM Process, without using Spring Context..

    Thanks in Advance..

    ReplyDelete
    Replies
    1. The question then is how would you pass parameters like username from the BPM process considering that the data are represented as xml in BPM... unless you are using other BPM solutions aside from Oracle BPM.

      Delete
  20. why not only edit the database tables ?

    ReplyDelete
  21. Thanks for your invaluable help! Four years old code and still working!! hehe

    ReplyDelete