Monday, April 12, 2010

By Request - Multiple Realms in WebLogic Server

Every once and a while, I see a request for support for multiple security realms in WebLogic Server. Just to clarify, what people want is multiple active realms, specifically with regards to authentication providers. There are multiple realms today in WebLogic server, but there are really for administrative convenience. There are a couple of valid use cases for wanting more than one realm.

The first is that if you are running multiple applications inside a single WebLogic Server domain, and each application has their own set of users in a different directory. This results in the administrator having to make a choice of which provider to put first. You end up with a single provider for each application, each one with the JAAS control flag set to SUFFICIENT. The container goes provider by provider checking to see if the credentials are there. This can lead to performance issues for the applications further down in the list.

Anotehr use case is the handling or mishandling of the weblogic or system users. It is a best practice to leave the weblogic user in the Default Authenticator. This way you can still boot the server even if other directory is down. This is fine, but the consequence is that the other directory needs to consider the "weblogic" user. Assuming that the application's authentication provider is configured first, it will get called with the weblogic username/password at start-up and when administrators use admin tools like the console. It's fine that the user isn't there, but this can create additional, unwanted traffic. I think another issue is that malicious users could lock out the weblogic user temporarily by attempting to log in multiple times with username weblogic and bogus passwords - a possible DOS attack. I guess in theory this is possible if the same users have access to the console application, and probably a good reason to change to have a different administrative user than weblogic.

Finally, the last user cases is that customers are migrating from other application servers and those servers make heavy of JAAS and basically have the ability to associate a JAAS Login Configuration with an application. They got used to this functionality, and expect it to be there in WebLogic Server. WebLogic Server at its core uses JAAS Login modules for its authentication, but wraps the JAAS Login Module into an MBean - the AuthenticationProvider. One this that is really nice about the authentication provider, beyond a simple MBean is that many of authentication provider also implement the optional Authentication SSPI MBeans. This allows from a single library both authentication and management - something JAAS alone does not provide. I want people to understand this point before I go an explain the solution - you're giving up all of the management aspects that WLS provides, and going with the strictly JAAS based - authentication only - approach.

That having been said, once again its the little known ServletAuthenticationFilter to the rescue. The idea is to create a ServletAuthenticationFilter that intercepts the request, and then calls the regular JAAS Login Module. The result is a javax.security.Subject. You can then push this Subject on to the WLS stack using ServletAuthentication.runAs()


public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {

System.out.println("In the filter....");
HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;

String uri = httpServletRequest.getRequestURI();

if (!uri.startsWith(this.URI)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
} else {
System.out.println("Processing "+uri);
this.initalizeMultiRealmConfig();
}

try {


LoginContext lc = new LoginContext(this.jaasLoginEntry,null,new MutliRealmCallbackHandler((HttpServletRequest)servletRequest),this.multiRealmConfig);
lc.login();
Subject subject = lc.getSubject();
System.out.println("The subject is "+subject);
ServletAuthentication.runAs(subject, (HttpServletRequest)servletRequest);

} catch (LoginException le) {
le.printStackTrace();
HttpServletResponse httpResponse = (HttpServletResponse)servletResponse;
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}

filterChain.doFilter(servletRequest, servletResponse);

}


In this example, I did something a little "fancy". I made the javax.sql.Datasource defined in the application available to the login module. This supports the very common use case where applications want to authenticate these application users via the database. The way I did this was through creating my own javax.security.auth.login.Configuration. This configuration makes the DataSource available as an entry in the Option map of the JAAS Login Module. Why do it this way? In this example, I tried to eliminate dependencies between the JAAS Login Modules getting called and the ServletAuthenticationFilter which was calling them. By passing it through the map, this eliminated the need to create a proprietary CallbackHandler. It a little extra code on my side, but it simplifies the implementation for JAAS Login Modules.


public AppConfigurationEntry[] getAppConfigurationEntry(String name) {

AppConfigurationEntry[] theEntries = this.theConfiguration.getAppConfigurationEntry(name);
AppConfigurationEntry[] theNewEntries = new AppConfigurationEntry[theEntries.length];
for (int i=0; i<theEntries.length; i++) {

AppConfigurationEntry entry = theEntries[i];

Map<String,Object> newMap = this.copyMap(entry.getOptions());
newMap.put("DataSource", this.ds);

theNewEntries[i] = new AppConfigurationEntry(entry.getLoginModuleName(),entry.getControlFlag(),newMap);
}

return theNewEntries;

}




This is all well and good, except that WLS needs to know about the principals. If the JAAS Login module is creating instances of WLSUserImpl or WLSGroupImpl then you're good, otherwise you'll nee to use a custom PrincipalValidator. The principal validator that I included in the project is VERY MINIMAL - all principals are trusted. If you have access to the JAAS Login Modules, the simplest thing to do is to modify the principals to extends the weblogic.security.principal.WLSAbstractPrincipal and then you can use the OOTB WebLogic Principal Validator. The details of Principal Validation can be found here.

Building and deploying the sample



  • Download the sample from Subversion
  • Modify the MultiRealmFilter.java - specifically the MultiRealmCallbackHandler inner class

    By default, the sample always passes a hardcoded username/password of foo/foo. You'll probably want it to do more. You need to modify it to fetch credentials from the HttpServletRequest and create the Callback that you need for the login modules.


    /**
    * This callback handler gets information from the HttpRequest - cookies, username/password
    * @author jbregman
    *
    */
    class MutliRealmCallbackHandler implements CallbackHandler {

    private HttpServletRequest req;

    MutliRealmCallbackHandler(HttpServletRequest req) {
    super();
    this.req = req;
    }

    @Override
    public void handle(Callback[] callbacks) throws IOException,
    UnsupportedCallbackException {
    // TODO Auto-generated method stub

    for (int i=0; i<callbacks.length; i++) {

    Callback callback = callbacks[i];

    if (callback instanceof NameCallback) {

    NameCallback nc = (NameCallback)callback;

    //In here, you'd go and do something with the request
    System.out.println("The name is foo");

    nc.setName("foo");
    } else if (callback instanceof PasswordCallback) {

    PasswordCallback pc = (PasswordCallback)callback;

    System.out.println("The password is foo");
    pc.setPassword("foo".toCharArray());

    } else {

    System.out.println("Some other callback "+callback);

    }



    }

    }


    }


  • Build the sample
    You'll need to modify the build.xml for your environment
  • Restart your domain, and create an instance of the "MultiRealmIdentityAsserter"
    Under the Provider specific properties, you need to set-up a few values
    JAAS Config Entery - The entry in the jaas-login that you want called

    Principal Class - If you're using the principal validator provided, then this is the base class of all the principals that the login module is creating. If you modified the principals so that they will work with the WebLogic principal validator, then you also updated the MultiRealmIdentityAsserterProviderImpl.java to return null

    URI-The path this ServletAuthenticationFilter applies to.
  • Modify you domain to point to the jaas-login
    Modify the setDomainEnv.cmd/.sh

    set JAVA_OPTIONS=%JAVA_OPTIONS% -Djava.security.auth.login.config=multirealm_jaas.config

    By default WLS will look for the config in the domain's home directory. Here's the very simple login config that I used:

    Sample {
    multirealm.someotherloginmodule.MyLoginModule required debug=true;
    };

  • Make sure the resource in the application is protected by the container
    If the resource isn't then the ServletAuthenticationFilter never gets called.
  • Make sure that the users authenticated by the JAAS Login Modules wind up in the JEE role that is protecting the application.
    For example, if your weblogic.xml looks like this:

    <wls:weblogic-web-app xmlns:wls="http://www.bea.com/ns/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd http://www.bea.com/ns/weblogic/weblogic-web-app http://www.bea.com/ns/weblogic/weblogic-web-app/1.0/weblogic-web-app.xsd">
    <wls:weblogic-version>10.3</wls:weblogic-version>
    <wls:security-role-assignment>
    <wls:role-name>multirealm</wls:role-name>
    <wls:principal-name>multirealm</wls:principal-name>
    </wls:security-role-assignment>
    <wls:context-root>App1</wls:context-root>
    <wls:library-ref>
    <wls:library-name>jstl</wls:library-name>
    <wls:specification-version>1.2</wls:specification-version>
    <wls:exact-match>true</wls:exact-match>
    </wls:library-ref>
    <wls:library-ref>
    <wls:library-name>wls-commonslogging-bridge-war</wls:library-name>
    <wls:specification-version>1.0</wls:specification-version>
    <wls:exact-match>true</wls:exact-match>
    </wls:library-ref>
    <wls:library-ref>
    <wls:library-name>jsf</wls:library-name>
    <wls:specification-version>1.2</wls:specification-version>
    <wls:exact-match>true</wls:exact-match>
    </wls:library-ref>
    <wls:resource-description>
    <wls:res-ref-name>dataSource</wls:res-ref-name>
    <wls:jndi-name>multiRealmDataSource</wls:jndi-name>
    </wls:resource-description>
    </wls:weblogic-web-app>

    Then you need to make sure that the user gets added in a Principal that is named multirealm
  • Restart the server and attempt to access the protected resources.

Summary


What you're basically doing with this approach is re-inventing the wheel. This will not work with the out-of-the-box WLS authentication providers. It will pass the application datasource, but if you're working with LDAP then you're totally on your own. You'll also probably need to modify the MultiRealmFilter further to work with forms based authentication....HTTP redirect to an un-protected page, and then have that page POST back to the resource with the ServletAuthenticationFilter configured. You also lose all of the WebLogic management capabilities - lockouts, password policy composition - etc. I wonder if all of this complexity is worth it. What do you think?

3 comments:

  1. Thanks for the article. I was wondering about the significance of <security> element in weblogic-application.xml
    <security>
    <realm-name>some realm name (not default)</realm-name>
    </security>
    Does this help in letting the Weblogic server know which realm to use? In that way there is no need to write code and without giving up all of the management aspects that WLS provides.

    ReplyDelete
  2. Hi Josh Bregman,
    Great post, exactly what I need.
    But I Cannot reach your source code, cause I don't have permission.
    Is there a way to get this code ?
    Thanks a lot
    Regards

    ReplyDelete
  3. The code is available at https://www.samplecode.oracle.com/sf/projects/mutlirealm-authentication/

    If you don't have access you should be able to request it through that site.

    ReplyDelete

Note: Only a member of this blog may post a comment.