Custom Realms and Groups in Glassfish

Glassfish  provides support for Custom Realms and Custom Login Modules that are based on the JAAS framework. This post explains how to write a simple Realm class and its corresponding LoginModule, configure them with an illustration of a simple web application that uses this realm.

Custom Realm

The Custom Realm should extend com.sun.appserv.security.AppservRealm. The Realm class is basically meant to provide user and group-related information. The methods to be implemented are

i) public void init(Properties properties )throws BadRealmException, NoSuchRealmException

This method is invoked during server startup when the realm is initially loaded.  The realm can do any initialization it needs in this method. The Properties is a set of key-value pairs configured while creating the Realm and are present in domain.xml. Among the other custom properties, there is a property jaas-context (which is explained later in this post). This property should be set using the call setProperty method implemented in the parent class. If the method returns without throwing an exception, the Enterprise Server assumes that the realm is ready to service authentication requests. If an exception is thrown, the realm is disabled.

ii) public String getAuthType() - This method returns a descriptive string representing the type of authentication done by this realm.

iii) public Enumeration getGroupNames(String user) throws InvalidOperationException, NoSuchUserException -
This method returns the group names the user belongs to as an Enumeration of Strings.

Custom LoginModule

The Custom LoginModule should extend com.sun.appserv.security.AppservPasswordLoginModule. This class should override the method

abstract protected void authenticateUser() throws LoginException

This method performs the actual custom authentication, by either using a database, or LDAP or a file or even a simple Hashtable as illustrated in the attached sample code. The custom login module must not implement any of the other methods, such as login(), logout(), abort(), commit(), or initialize(). Default implementations are provided in AppservPasswordLoginModule which hook into the Enterprise Server infrastructure.

The custom login module can access the following protected object fields, which it inherits from AppservPasswordLoginModule. These contain the user name, password of the user to be authenticated and the currentRealm class.

protected String _username;

protected String _password;

protected com.sun.enterprise.security.auth.realm.Realm  _currentRealm;

The authenticateUser() method should end with a call to the commitUserAuthentication(String[] authenticatedGroupList) method where the authenticatedGroupList is the list of groups the user belongs to.

As can be observed, the realm class is isolated from the LoginModule. The Realm is capable of capturing arbitrary configuration information and can help in obtaining the Group information. The Group information from the Realm can be  populated into the authenticated JAAS subject during commit() phase following a  successful LoginContext.login() call on the  authentication module. This populated group information is then used by the container in its authorization policy decisions.

Attached here is the source code of a simple sample realm class and the custom module. In this example, the Realm class stores the user-group information in a hashtable. The LoginModule class stores the user-password information in a hashtable and performs authentication. It obtains the authenticatedGroupList from the Realm class' getGroups(username) method.

To test this sample(it works with both GF v2 and v3), download and install Glassfish v3 from here, drop the binaries of this realm and custom module in <GF-ROOT>/domains/domain1/lib/, start the server and create the realm using the Admin console. The realm classname should be specified as com.samplerealm.SampleRealm.

An additional realm property jaas-context should be specified to say sampleRealm. This value should refer to the SampleLoginModule class in the

<GF-ROOT>/domains/domain1/config/login.conf

file as follows:

sampleRealm {
       com.samplerealm.SampleLoginModule required;
};

where sampleRealm refers to the value defined in the jaas-context property.

As can be seen from the source files, the users configured in this realm are userA, userB whose corresponding passwords are abc123, xyz123. userA has been configured in the group devGroup, while userB belongs to testGroup. To test this realm, this web-application can be used.

Observe that the web.xml of the web-app contains the following :

<login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>SampleRealm</realm-name>
     </login-config>

where  SampleRealm in the <login-conf><realm-name> element points to the name of the configured Realm as can be seen in the server's domain.xml

        <auth-realm classname="com.samplerealm.SampleRealm" name="SampleRealm">
          <property name="jaas-context" value="sampleRealm"></property>
        </auth-realm>

To access the web-app using the group names of the user, the following mapping between the role and group is required in sun-web.xml:

  <security-role-mapping>
    <role-name>tester</role-name>
    <group-name>devgroup</group-name>
  </security-role-mapping>

where the role-name matches the configured role in the auth-constraint of web.xml

        <auth-constraint>
            <description/>
            <role-name>tester</role-name>
         </auth-constraint>

and group-name is the corresponding group the user belongs to as defined in the custom realm class. So if the application is accessed using the user/password: userA/abc123, the user is authorized to view the pages, since he belongs to the devGroup, but not userB/xyz123 (who belongs to testGroup).


Comments:

I am reading the AppservPasswordLoginModule, the protected String _currentRealm variable should be a type of Realm, not a String class name. Maybe something you can verify for us:

// should that be "protected Realm _currentRealm"?
protected String _currentRealm;

Posted by Jeff on December 29, 2008 at 01:06 PM SCT #

Yes, it should be a Realm class instance. This has been corrected in the original post. Thanks a lot!!

Posted by Nithya Subramanian on December 30, 2008 at 03:21 AM SCT #

The link to the sourcecode is not correct (gives a 404). You're missing an 'e' in the link, it should be http://blogs.sun.com/nithya/resource/samplerealm.zip.

Posted by Johan on January 02, 2009 at 04:31 AM SCT #

Yes, changed it, thanks!!

Posted by Nithya Subramanian on January 04, 2009 at 12:42 AM SCT #

Does this work for v3 prelude? If I follow the instruction I get ClassNotFoundException for com.samplerealm.SampleReam.

Posted by paulbrickell on May 26, 2009 at 09:47 AM SCT #

Hello...
Thanks for the great post. I am having an issue, however, because glassfish(2) is not finding my custom classes. i have tried jarring them up and putting them in the /lib of both the root install dir and the domain lib. i also set the classpath-suffix in the domain.xml. i just keep getting the BadRealmException.
any words of wisdom? please? thanks ahead of time.

Posted by Paul on May 28, 2009 at 12:30 PM SCT #

I did get it to initialize putting the jar of the Login Module & the Realm in the glassfish install lib dir - not the domain lib. My problem was I wasnt jarring the classes correctly - jar them at the dir below the com.etc.etc ... duh. Thanks for the blog.

Posted by Paul on May 29, 2009 at 12:40 AM SCT #

I can't get this to work. I believe I have everything configured properly. This is the error I see:

[#|2009-10-08T13:39:54.545-0600|INFO|sun-appserver2.1|javax.enterprise.system.core.security|_ThreadID=31;_ThreadName=httpSSLWorkerThread-8080-1;userA;|SEC5046: Audit: Authentication refused for [userA].|#]

[#|2009-10-08T13:39:54.545-0600|WARNING|sun-appserver2.1|javax.enterprise.system.container.web|_ThreadID=31;_ThreadName=httpSSLWorkerThread-8080-1;_RequestID=2ecce6d2-b426-4c10-a6b3-7fe20ddbe48d;|Web login failed: Login failed: javax.security.auth.login.LoginException: Invalid null input: name|#]

Posted by Jim on October 08, 2009 at 04:41 PM SCT #

Oops. I forgot to add the additional "jaas-context" property. Now it works.

Posted by Jim on October 08, 2009 at 04:52 PM SCT #

The SampleRealms does not work with GlassFish v3 (build 74.2).

I can not add the realm in the Admin console (Message=An Error occurred).

The server log file does not help much:

org.jvnet.hk2.config.TransactionFailure: Unexpected exception during isValid call
Can't create children

Caused by: java.lang.NullPointerException
at com.sun.enterprise.config.serverbeans.customvalidators.FileRealmPropertyCheckValidator.isValid(FileRealmPropertyCheckValidator.java:61)
at com.sun.enterprise.config.serverbeans.customvalidators.FileRealmPropertyCheckValidator.isValid(FileRealmPropertyCheckValidator.java:49)
at org.hibernate.validator.engine.ConstraintTree.validateSingleConstraint(ConstraintTree.java:141)
... 74 more

Posted by Sascha on January 05, 2010 at 09:34 AM SCT #

I just wanted to add that I tried to reuse my self developed Realm/LoginModule currently working under Glassfish 2.1 under Glassfish 3.

But with Glassfish 3 I get the same stacktrace as Sascha - nowhere in the stacktrace is mentioned one of my classes so I think its a bug in Glassfish v3 ....:

[#|2010-02-03T21:03:07.002+0100|INFO|glassfishv3.0|javax.enterprise.system.tools.admin.org.glassfish.server|_ThreadID=25;_ThreadName=Thread-1;|Can't create children
org.jvnet.hk2.config.TransactionFailure: Unexpected exception during isValid call
at org.jvnet.hk2.config.ConfigSupport._apply(ConfigSupport.java:194)
at org.jvnet.hk2.config.ConfigSupport.apply(ConfigSupport.java:130)
at org.glassfish.admin.amx.impl.config.AMXConfigImpl.createChildren(AMXConfigImpl.java:518)
at org.glassfish.admin.amx.impl.config.AMXConfigImpl.createChild(AMXConfigImpl.java:620)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

Posted by Marcel Pokrandt on February 03, 2010 at 04:31 PM SCT #

@Marcel Pokrandt

It seems there is an issue with the glassfish admin console, if you use the asadmin create-auth-realm it should create without problems.

Posted by Federico Vela on February 03, 2010 at 05:35 PM SCT #

@Federico

Thanks for your help. You were right. Even configuring additional parameters for an existant realm doesn´t work in the web interface. Manually configuring the Realm worked for me.
This really, really helped me a lot because I wanted to start using EJB3.1 and need this Realm.

Posted by Marcel on February 05, 2010 at 06:06 PM SCT #

Thank you for the example code for implementing custom realms, it has helped tremendously! I have a question that goes a little further than this post and hope someone has a good recommendation. I'm sure the answer will help others out there as well.

I have implemented a custom realm based on this post and it works wonderfully. My intention is to expand it to let the realm take care of user management. My question is, is there any way to pass anything back to the web container after authentication, via some property of the Principal or other means that can act as a flag?

For instance, our user's password expires after X number of days. Is there anyway I can pass back some sort of parameter to the web application that signifies this state and the web container can act on it as needed. Another scenario is the user needs to accept terms and agreements.

Since these fields are stored in LDAP, we'd have to share the code between the realm and the web app that looks up LDAP attributes making the user management portion of the realm obsolete, as the LDAP library could handle it directly. Not to mention, the extra LDAP lookup on every login regardless of whether it's needed or not.

A few options we're toying with but don't find very elegant are to add a special group for each condition and check for these groups after request.login(username, password); is called. Another idea is to set flags in an application database for the user.

These feel more like workarounds than actual solutions. Is there a more elegant want of accomplishing this sort of requirment?

Thanks again for the post!

Posted by Jon Redfield on March 02, 2010 at 06:36 PM SCT #

Apologies for the late response.To understand your requirement correctly,is it that you would like to have a Map associated with the authenticated Principal that you would like to use in your application to check?Also can you explain a little more on the 'flag' that you mention as a requirement?

Thanks
Nithya

Posted by Nithya Subramanian on July 15, 2010 at 08:03 AM SCT #

Excuse me for my bad english. I have created CustomLogin JAAS:
---------------------------------------------------------------------------------------
import it.ised.j118.jaas.Messaggi;
import it.ised.j118.jaas.glassfish.Realm;
import javax.security.auth.login.LoginException;
import com.sun.appserv.security.AppservPasswordLoginModule;

public class LoginModule extends AppservPasswordLoginModule {
@Override
protected void authenticateUser() throws LoginException {
if (!authenticate(_username, _password)) {
//Login fails
throw new LoginException((new StringBuilder()).append("Login Failed for: ").append(_username).toString());
}
String[] groups = getGroupNames(_username);
commitUserAuthentication(groups);
}

private boolean authenticate(String username, String password) throws LoginException {
// A JDBC user must have a name not null so check here.
if (!((Realm) _currentRealm).convalidaUtente(username, password)) { // JAAS behavior
throw new LoginException(Messaggi.getString("LoginModule.messaggio.1")); //$NON-NLS-1$
}
return true;
}

private String[] getGroupNames(String username) {
return ((Realm) _currentRealm).leggiGruppiUtente(username);
}
}
---------------------------------------------------------------------------------------
I have put JAAS.jar in path ../lib.
When I run Glassfish 2.1.1 in log I have this error:
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
[#|2010-11-03T19:43:21.939+0100|WARNING|sun-appserver2.1|javax.enterprise.system.core.security|_ThreadID=10;_ThreadName=main;_RequestID=6e95b1a0-5a4a-433f-85e3-c3a6c3d07d7b;|SEC1000: Caught exception.
com.sun.enterprise.security.auth.realm.BadRealmException: java.lang.ClassCastException: it.ised.j118.jaas.glassfish.LoginModule cannot be cast to com.sun.enterprise.security.auth.realm.Realm
at com.sun.enterprise.security.auth.realm.Realm.doInstantiate(Realm.java:239)
at com.sun.enterprise.security.auth.realm.Realm.instantiate(Realm.java:165)
at com.sun.enterprise.security.RealmConfig.createRealms(RealmConfig.java:93)
at com.sun.enterprise.security.RealmConfig.createRealms(RealmConfig.java:163)
at com.sun.enterprise.security.SecurityLifecycle.onInitialization(SecurityLifecycle.java:113)
at com.sun.enterprise.server.ApplicationServer.onInitialization(ApplicationServer.java:265)
at com.sun.enterprise.server.ondemand.OnDemandServer.onInitialization(OnDemandServer.java:103)
at com.sun.enterprise.server.PEMain.run(PEMain.java:399)
at com.sun.enterprise.server.PEMain.main(PEMain.java:336)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.sun.enterprise.server.PELaunch.main(PELaunch.java:415)
Caused by: java.lang.ClassCastException: it.ised.j118.jaas.glassfish.LoginModule cannot be cast to com.sun.enterprise.security.auth.realm.Realm
at com.sun.enterprise.security.auth.realm.Realm.doInstantiate(Realm.java:231)
... 13 more
|#]
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
Thanks for all
Salvatore

Posted by Salvatore on November 04, 2010 at 10:54 AM SCT #

I can confirm that this example does not work with the following configuration:

GlassFish Server Open Source Edition 3.0.1 (build 22)
JRE (1.6.0)

I also get the error:

java.lang.ClassCastException: com.samplerealm.SampleRealm cannot be cast to com.sun.enterprise.security.auth.realm.Realm

Posted by Daniel M. on January 18, 2011 at 10:49 PM SCT #

Hi Salvatore, make sure you put the file SampleRealm.jar in the directory <GF-ROOT>/domains/domain1/lib/ and not in the ext, classes or applibs directories. This was my problem, as soon as I moved the file to the lib directory the realm worked without a problem. I still have not learned to read all the documentation carefully :-( since it seems that I tend to learn better by example (visually) :-).

Posted by Daniel M. on January 19, 2011 at 04:56 PM SCT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

nitkal

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
Bookmarks