Form Based Login with JAAS on JBoss and ZK

From Documentation
DocumentationSmall Talks2009AugustForm Based Login with JAAS on JBoss and ZK
Form Based Login with JAAS on JBoss and ZK

Date
August 13, 2009
Version
ZK 3.6.2, ZK 5.0.0 CE

Introduction

The talk shows how to use ZK to implement form based login for JAAS on JBoss 5.x. I assume you know how to secure your web application using form base login and a plain HTML or JSP form. If not, then first have a look at JBoss Wiki[1] or other resources.

When you use ZK, you can use plain HTML (or JSP) login form with action j_security_check and input fields j_username and j_password as well. However, when developing the login form, you may want as well:

  • Only one login page, not the login and error page.
  • Validate the user input (for non-null values) before performing the login.
  • Inform the user, why login failed.
  • Have the login form with the same look&feel as the rest of your ZK application.
  • And maybe more control about what's going on.

I will not explain everything in detail, download the example and look into the code. The talk also continues at Ajax and ZK Based Login with JAAS on JBoss.

JAAS and DB

JAAS is flexible in a way how to authenticate the user. JBoss provides several so called login modules. Usually, you need to check the username and password against the database, so we will use org.jboss.security.auth.spi.DatabaseServerLoginModule. The login module may be configured site-wide in the conf/login-config.xml. But I prefer to have the configuration packed into my deployment EAR or WAR. In JBoss, starting from 5.x, it's easy. Just make any file named xxx-jboss-beans.xml (e.g. login-jboss-beans.xml) in the META-INF directory of your EAR (or META-INF in the EJB module, or WEB-INF of WAR):

<?xml version="1.0" encoding="UTF-8"?>

<deployment xmlns="urn:jboss:bean-deployer:2.0">
  <application-policy xmlns="urn:jboss:security-beans:1.0" name="zkformlogin">
    <authentication>
      <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule"
        flag="required">
        <!-- <module-option name="hashAlgorithm">MD5</module-option> BASE64 also possible-->
        <!-- <module-option name="unauthenticatedIdentity">guest</module-option> -->
        <module-option name="dsJndiName">java:/DefaultDS</module-option>
        <module-option name="principalsQuery">SELECT password FROM User WHERE username=?</module-option>
        <module-option name="rolesQuery">SELECT role, 'Roles' FROM UserRoles, User WHERE User.username=? AND User.id = UserRoles.user_id</module-option>
      </login-module>
    </authentication>
  </application-policy>
</deployment>

The example uses the data source bind on java:/DefaultDS and assumes that there are tables User and UserRoles in the DB. In the element rolesQuery the string 'Roles' means the groups of roles -- the role group 'Roles' is the same for all users in the example. You may easily extend the DB schema to contain different group roles as well (i.e. one more column it the table UserRoles).

Note, for JBoss 4.x this mechanism does not work, but you may use dynamic login config [2] instead.

The username of the logged-in user may be accessed:

  • By the HTTP servlet request. javax.servlet.http HttpServletRequest.getRemoteUser() returns the username and the user role may be checked by javax.servlet.http HttpServletRequest.isUserInRole(String role).
  • By the session context in a session bean. javax.ejb.SessionContext.getCallerPrincipal().getName() returns the username and the user role may be checked by javax.ejb.SessionContext.getCallerPrincipal().isCallerInRole(String role).

However, one usually needs more information from the DB about the logged in user, like the real name, email, etc. Also, I usually do not access the DB directly, I like to use JPA/Hibernate. So I have made two entities: User and UserRoles to access the information about the user in the DB, and the session bean UserDao, which can get the User object for the currently logged-in user. The class Identity provides the simplified way how to access the user information from UserDao stores it in the HTTP session, see the example code.

JBoss JAAS uses ThreadLocal class, so it does not work, when it is accessed by other thread than the thread processing the request. For ZK the best solution is to disable the event thread. It is disabled by default in ZK 5. Disable it for ZK 3 by adding to your zk.xml:

<system-config>
	<disable-event-thread />
</system-config>

ZK Login Form

In addition to the standard form login, the JBoss guys has made a nice class org.jboss.web.tomcat.security.ExtendedFormAuthenticator, which puts into the HTTP session username (under the key j_username) and the exception (under the key j_exception), when the login fails. Thus, one can:

  • make the same page for the login and error page,
  • analyse, why the login has failed,
  • and prefill the username into the input field if the login has failed.

To use this ExtendedFormAuthenticator, just make a file WEB-INF/context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Context cookies="true" crossContext="false">
   <Valve className="org.jboss.web.tomcat.security.ExtendedFormAuthenticator"
      includePassword="false" ></Valve>
</Context>

Then, the form login in web.xml may be:

<login-config>
	<auth-method>FORM</auth-method>
	<realm-name>ZK Form Login Demo</realm-name>
	<form-login-config>
		<form-login-page>/public/login.zul</form-login-page>
		<form-error-page>/public/login.zul</form-error-page>
	</form-login-config>
</login-config>

To use the ZK look&feel, just allow public access to the URI update-uri of the org.zkoss.zk.ui.http.DHtmlLayoutServlet servlet. Usually, it is /zkau/* URI. Hence, my web.xml contains:

<security-constraint>
	<display-name>Zkau and Public Unprotected</display-name>
	<web-resource-collection>
		<web-resource-name>HtmlAdaptor</web-resource-name>
		<description>Exclude Zkau and Public</description>
		<url-pattern>/zkau/*</url-pattern>
		<url-pattern>/public/*</url-pattern>
	</web-resource-collection>
	<user-data-constraint>
	<transport-guarantee>NONE</transport-guarantee>
	</user-data-constraint>
</security-constraint>

This security constrain has no <auth-constraint>, hence it allow public access for the two defined URL patterns.

Now, you have to make a HTML POST form with action j_security_check and input fields of names j_username and j_password. See Work with Legacy Web Applications, Part I - Servlets and Forms . For instance, the file public/login.zul may look like:

<window title="ZK Form Login Demo" width="400px" position="cetner,center" border="normal">
	<zscript><![CDATA[
// parse the j_exception
Throwable j_exception = (Throwable) sessionScope.get("j_exception");
String errMsg = null;
if (j_exception != null) {
	if (j_exception instanceof javax.security.auth.login.FailedLoginException) {
		errMsg = "Username and/or the password is not right. Please, try it again.";
	} else {
		errMsg = "Unknown exception when logging in: " + this.j_exception + " Please, contact the admin.";
	}
}
	]]></zscript>
	<h:form method="post" id="j_security_check" action="j_security_check">
		<grid>
			<rows>
				<row>
					Username :
					<textbox id="j_username" name="j_username" value="${sessionScope.j_username}" width="200px" />
				</row>
				<row>
					Password :
					<textbox id="j_password" name="j_password" type="password" width="200px" />
				</row>
			</rows>
		</grid>
		<div width="100%">
			<h:input type="submit" value="Login" />
		</div>
	</h:form>
	<div if="${errMsg != null}" width="100%" style="color:red">${errMsg}</div>
	<zscript><![CDATA[
		j_username.focus();
	]]></zscript>
</window>

This code takes the advantage of the ExtendedFormAuthenticator, so the the HTTP session objects j_username and j_exception are present when the login has failed. Similar login form is implemented in the file public/login_simple.zul in the example.

Furthermore, before submitting the form, you may execute any code, like validate the form, seeWork with Legacy Web Applications, Part III - Validate Forms . The main trick is made by the method org.zkoss.zk.ui.util.Clients.submitForm(...). Instead of the input type submit button, we may use ZK button and it's onClick action:

<button id="b_login" label="Login">
	<attribute name="onClick">
	// just to perform constraints checks
	j_username.getValue();
	j_password.getValue();
	// submit the form
	Clients.submitForm(j_security_check);
	</attribute>
</button>

To logout, just invalidate the HTTP session. See the file public/logout.zul in the example.

More Tricks

You can execute any code before sending the form by Clients.submitForm(j_security_check). E.g. you may store some more data in the HTTP session (like "remember me" checkbox value). You may even access DB and check, if the login is going to succeed by yourself. In this case, you do not need org.jboss.security.auth.spi.DatabaseServerLoginModule, but you may be happy only with org.jboss.security.auth.spi.SimpleServerLoginModule, org.jboss.security.auth.spi.AnonLoginModule or the other ones. (Just take care, that your code and configuration has no any security hole, like SQL injection.)

The login page does not need to be specified in the web.xml. You may do Clients.submitForm(j_security_check) on other pages too, e.g. on a registration page.

Redirect Time-Out to the Login Page

When the session time-outs, you may like to redirect the user to the login page and show the user a message what has happened. Configure your zk.xml:

<session-config>
	<device-type>ajax</device-type>
	<timeout-uri>/?tmout=1</timeout-uri>
	<automatic-timeout/>
	<!-- Make a Timer to send keep-alive.  -->
	<!-- <timer-keep-alive>true</timer-keep-alive> -->
</session-config>

Then the browser is redirected to the root page of the application, which is secured. Hence a web container show a login page with a parameter ?tmout=1. You can check this parameter e.g. by:

final boolean timeout = "1".equals(Executions.getCurrent().getParameter("tmout"));

Caching the JAAS Credentials

When you allow the user to change credentials (username and password, usually, depends on your JAAS configuration) within the web application, then you may have see javax.ejb.EJBAccessException, because JAAS tries to authenticate the request with the old credentials. Then, it is wise to add flushOnSessionInvalidation into WEB-INF/jboss-web.xml:

<jboss-web>
   <security-domain flushOnSessionInvalidation="true">java:/jaas/zkformlogin</security-domain>
</jboss-web>

See CachingLoginCredentials in JBoss Wiki .

Example

The example (download bellow) uses the data source java:/DefaultDS which should be the HSQL database. Also, the hibernate.hbm2ddl.auto is set to create-drop, so the database tables are dropped and created during the deployment. Beware! It may destroy your data! Do not use it, if your java:/DefaultDS points to a DB with any precious data! The example has been tested with JBoss 5.1.0GA and ZK5.0.0 (ZK3.6.2, too). I have also removed some ZK libraries not required for this demo, so you get a few warnings during deployment.

You can login as demo:demo, or admin:admin. After the login, you can go to the page /admin.zul. Every user is allowed to access this page, but for non-admin users it throws an error, because it uses the secured session bean method UserDao.getAllUsers(). The page /admin/admin.zul is exactly the same, but the access to it is restricted in web.xml only for admin users.

You may try to switch public/login.zul for public/login_simple.zul in a <form-login-page> element of web.xml.

Tomcat creates "Cache-Control: no-cache" HTTP response header for every request of an authenticated resource. To enable browser caching, put disableProxyCaching="false" attribute of the Valve element in the context.xml:

<Context cookies="true" crossContext="false">
   <Valve className="org.jboss.web.tomcat.security.ExtendedFormAuthenticator"
      includePassword="false" disableProxyCaching="false"></Valve>
</Context>

Download

zkformlogin-5-ear.ear

zkformlogin-ear.ear - the older one with ZK3.6.2

Summary

You can use ZK elements in a form based JAAS login and you can execute your code before the login form is submitted. The JAAS restricts the access to your web application and session beans. However, the AJAX calls are not secured - anyone can access the /zkau/* URI, but it should not be a problem, because the potential malicious user has no valid session.

See Also


References:



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.