Creating Custom Login Modules In JBoss AS 7 (and Earlier)
JBoss AS 7 is neat but the documentation is still quite lacking (and error messages not as useful as they could be). This post summarizes how you can create your own JavaEE-compliant login module for authenticating users of your webapp deployed on JBoss AS. A working elementary username-password module provided.
It works as follows:
The abstract UsernamePasswordLoginModule (source code) works by comparing the password provided by the user for equality with the password returned from the method getUsersPassword, implemented by a subclass. You can use the method getUsername to obtain the user name of the user attempting login.
Alternatively you could override validatePassword(String inputPassword, String expectedPassword) to do whatever conversion on the password before comparison or even do a different type of comparison than equality.
The code attribute should contain the fully qualified name of your login module class and the security-domain's name must match the declaration in jboss-web.xml:
Why to use Java EE standard authentication?
Java EE security primer
A part of the Java EE specification is security for web and EE applications, which makes it possible both to specify declarative constraints in your web.xml (such as "role X is required to access resources at URLs "/protected/*") and to control it programatically, i.e. verifying that the user has a particular role (see HttpServletRequest.isUserInRole).It works as follows:
- You declare in your web.xml:
- Login configuration - primarily whether to use browser prompt (basic) or a custom login form and a name for the login realm
- The custom form uses "magic" values for the post action and the fields, starting with j_, which are intercepted and processed by the server
- The roles used in your application (typically you'd something like "user" and perhaps "admin")
- What roles are required for accessing particular URL patterns (default: none)
- Whether HTTPS is required for some parts of the application
- Login configuration - primarily whether to use browser prompt (basic) or a custom login form and a name for the login realm
- You tell your application server how to authenticate users for that login realm, usually by associating its name with one of the available login modules in the configuration (the modules ranging from simple file-based user list to LDAP and Kerberos support). Only rarely do you need to create your own login module, the topic of this post.
Why to bother?
- Declarative security is nicely decoupled from the business code
- It's easy to propagate security information between a webapp and for example EJBs (where you can protect a complete bean or a particular method declaratively via xml or via annotations such as @RolesAllowed)
- It's easy to switch to a different authentication mechanism such as LDAP and it's more likely that SSO will be supported
Custom login module implementation options
If one of the login modules (a.k.a. security domains) provided out of the box with JBoss, such as UsersRoles, Ldap, Database, Certificate, isn't sufficient for you then you can adjust one of them or implement your own. You can:- Extend one of the concrete modules, overriding one or some of its methods to ajdust to your needs - see f.ex. how to override the DatabaseServerLoginModule to specify your own encryption of the stored passwords. This should be your primary choice, of possible.
- Subclass UsernamePasswordLoginModule
- Implement javax.security.auth.spi.LoginModule if you need maximal flexibility and portability (this is a part of Java EE, namely JAAS, and is quite complex)
Example: Custom UsernamePasswordLoginModule subclass
See the source code of MySimpleUsernamePasswordLoginModule that extends JBoss' UsernamePasswordLoginModule.The abstract UsernamePasswordLoginModule (source code) works by comparing the password provided by the user for equality with the password returned from the method getUsersPassword, implemented by a subclass. You can use the method getUsername to obtain the user name of the user attempting login.
Implement abstract methods
getUsersPassword()
Implement getUsersPassword() to lookup the user's password wherever you have it. If you do not store passwords in plain text then read how to customize the behavior via other methods belowgetRoleSets()
Implement getRoleSets() (from AbstractServerLoginModule) to return at least one group named "Roles" and containing 0+ roles assigned to the user, see the implementation in the source code for this post. Usually you'd lookup the roles for the user somewhere (instead of returning hardcoded "user_role" role).Optionally extend initialize(..) to get access to module options etc.
Usually you will also want to extend initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) (called for each authentication attempt),- To get values of properties declared via the <module-option ..> element in the security-domain configuration - see JBoss 5 custom module example
- To do other initialization, such as looking up a data source via JNDI - see the DatabaseServerLoginModule
Optionally override other methods to customize the behavior
If you do not store passwords in plain text (a wise choice!) and your hashing method isn't supported out of the box then you can override createPasswordHash(String username, String password, String digestOption) to hash/encrypt the user-supplied password before comparison with the stored password.Alternatively you could override validatePassword(String inputPassword, String expectedPassword) to do whatever conversion on the password before comparison or even do a different type of comparison than equality.
Custom login module deployment options
In JBoss AS you can- Deploy your login module class in a JAR as a standalone module, independently of the webapp, under <JBoss AS 7>/modules/, together with a module.xml - described at JBossAS7SecurityCustomLoginModules
- Deploy your login module class as a part of your webapp (no module.xml required)
- In a JAR inside WEB-INF/lib/
- Directly under WEB-INF/classes
<security-domain name="form-auth" cache-type="default">
<authentication>
<login-module code="custom.MySimpleUsernamePasswordLoginModule" flag="required">
<!--module-option name="exampleProperty" value="exampleValue"/-->
</login-module>
</authentication>
</security-domain>
The code attribute should contain the fully qualified name of your login module class and the security-domain's name must match the declaration in jboss-web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<security-domain>form-auth</security-domain>
<disable-audit>true</disable-audit>
</jboss-web>