User Login Validation with JAAS and JSF

14

Posted on September 8, 2011 by

Share it now!

Hello, how are you?

In another post I have talked about a User validation using Servlet / Filter. Today we will see another way of doing it, we will use JSF and JAAS.

The JAAS validation uses the server as tools to validate the user, his password and the role that the user has. You will not need to validate the user (programmatically) at every http request.

He are the others posts about web in my blog: JSF: Converter and Bean AutoComplete, JSF – Hello World, Auto Complete, Handling Exceptions on a WebApp, User Authentication (Filter/Servlet), Creating a WebServer.

We will use today JSF, Postgres, Eclipse and JBoss AS 6:

I will use the JBoss Tools just to help us with the JSF and allow us to use the JBoss 6 inside the Eclipse. You can create all the code posted here and put the WAR inside the JBoss folder and run it without the need of the Eclipse. In the beginning of the post I have listed a link with a “how-to” about a JSF application.

I will not show how to install the Postgres database because it is out of the scope of this post, but you can use any database that you want like MySQL, SqlServer, Oracle, etc.

To install the JBoss you just have to unzip it and it will be ready to be used.

Unzip the Eclipse and install the JBoss Tools. It is a really helpful tool when we work with JBoss and JSF.

  •   To make it easier I have selected this two options:

  • Then Finish the installation.

To install the server just need to click in the tab “Servers” > New >Server

Select JBoss 6 > Next

Select the path where you unzipped your JBoss:

And click in the “Finish” button.

Let us create a new JSF project (File > New > Project). I will use a project created by the JBoss Tools. If you want to create a JSF project by yourself, check the link in the begging of this post about JSF.

I am naming the application “LoginJSF”, select JSF 2.0 and click in Finish.

The Eclipse will ask you if you want to change to the web view, I clicked on the “No” button. You can click Yes later to see this view.

The Eclipse will create an application with a basic navigation. Notice that the Eclipse already added your application to the server, if it did not added, click with the right button of the mouse on the project name > Run As > Run on Server. Select JBoss.


Run the server and access by the link: http://localhost:8080/LoginJSF/pages/inputname.jsf

Stop the JBoss and let us create 3 folders:

  • A folder with public access
  • A folder only to admin access
  • A folder to user and admin access

<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:composite="http://java.sun.com/jsf/composite">

    <h:head>
        I am public! [=
    </h:head>
    <h:body>
        Everyone can see me. \o/
    </h:body>
</html>
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:composite="http://java.sun.com/jsf/composite">

    <h:head>
        Yes master admin! Welcome!
    </h:head>
    <h:body>
        Let us run this business!
    </h:body>
</html>
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:composite="http://java.sun.com/jsf/composite">

    <h:head>
        Welcome mister user!
    </h:head>
    <h:body>
        <br/>
        Please, do not create any bugs, ok?! >=)
    </h:body>
</html>

Now let us start the JAAS configuration. Let us start editing the web.xml file indicating which folders are protected:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    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_3_0.xsd">
    <display-name>LoginJSF</display-name>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
        <url-pattern>*.jsf</url-pattern>
        <url-pattern>/faces/*</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

    <!-- Protected Areas -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Only admins</web-resource-name>
            <url-pattern>/pages/protected/admin/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ADMIN</role-name>
        </auth-constraint>
    </security-constraint>
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Users and admins</web-resource-name>
            <url-pattern>/pages/protected/user/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ADMIN</role-name>
            <role-name>USER</role-name>
        </auth-constraint>
    </security-constraint>

    <!-- Validation By Form -->
    <login-config>
        <auth-method>FORM</auth-method>
        <form-login-config>
            <form-login-page>/pages/public/login.xhtml</form-login-page>
            <form-error-page>/pages/public/loginError.xhtml</form-error-page>
        </form-login-config>
    </login-config>

    <!-- Allowed Roles -->
    <security-role>
        <role-name>ADMIN</role-name>
    </security-role>
    <security-role>
        <role-name>USER</role-name>
    </security-role>

    <!-- Filter to get the user name and work with it -->
    <filter>
        <filter-name>LoginFilter</filter-name>
        <filter-class>com.filters.LoginFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>LoginFilter</filter-name>
        <url-pattern>/pages/protected/*</url-pattern>
    </filter-mapping>
</web-app>

We will need a page to login and another page to be displayed if any error happens. Take a closer look at web.xml and you will see these pages described there.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
    JSF 2 Login
</h:head>
<h:body>
    <p>Login to access secure pages:</p>
    <form method="post" action="j_security_check">
        <h:panelGrid columns="2">
            <h:outputLabel for="j_username" value="Username" />
            <input type="text" id="j_username" name="j_username" />
            <h:outputLabel for="j_password" value="Password" />
            <input type="password" id="j_password" name="j_password" />
            <input type="submit" name="submit" value="Login" />
        </h:panelGrid>
        <br />
    </form>
</h:body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
    JSF 2 Login
</h:head>
<h:body>
    <p>Ops! Check your user and password</p>
</h:body>
</html>

Let us create a java Filter that will catch the login (user name) that was typed. With this login (user name) you will be able to search for the user in the database and put it in the session.

package com.filters;

import java.io.IOException;

import javax.servlet.*;

import org.jboss.security.SecurityAssociation;

public class LoginFilter implements Filter {

    @Override
    public void destroy() {
    }

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

        String userName = SecurityAssociation.getPrincipal().getName();

        System.out.println("Yeeey! Get me here and find me in the database: " + userName);

        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}

Let us create now our database tables.

I will use the simplest database possible, with only two tables and a few columns.
Create a database named LoginJSF and run the script bellow:

CREATE TABLE "users"
(
  "name" character varying(50),
  pass character varying(50)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE "users" OWNER TO postgres;

CREATE TABLE user_roles
(
  user_name character varying(50),
  role_name character varying
)
WITH (
  OIDS=FALSE
);
ALTER TABLE user_roles OWNER TO postgres;

Let us insert two users, one with the USER role and another with the ADMIN role:

INSERT INTO users("name", pass) VALUES ('user', 'user');
INSERT INTO users("name", pass) VALUES ('admin', 'admin');
INSERT INTO user_roles(user_name, role_name) VALUES ('user', 'USER');
INSERT INTO user_roles(user_name, role_name) VALUES ('admin', 'ADMIN');

We are almost there, have a little bit more of patient. [=

We need to edit the file “login-config.xml” of the JBoss. You will find this file at “/JBOSS_THAT_YOU_UNZIPPED/server/default/conf/“. Add the lines bellow near the other tags named “application-policy”:

<!-- Others application-policy -->

<!-- ..................... -->

<!-- Security Profile Of our Software-->
<application-policy name="LoginJSFRealm">
    <authentication>
        <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
            <module-option name="dsJndiName">java:/LoginJSFRealmDS</module-option>
            <module-option name="principalsQuery">select pass from users where name=?</module-option>
            <module-option name="rolesQuery">select role_name, 'Roles' from user_roles where user_name = ?</module-option>
        </login-module>
    </authentication>
</application-policy>

We just gave to the JBoss a Realm to use: LoginJSFRealm. Now we need to let the JBoss know that our app will use this Realm. Let us create a file named “jboss-web.xml” inside the folder WEB-INF with the code bellow:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
    <!-- Links with JBoss the Realm to use -->
    <security-domain>java:/jaas/LoginJSFRealm</security-domain>
</jboss-web>

In the file “login-config.xml” we informed the JBoss to use a datasource named “LoginJSFRealmDS”. Through this datasource the JBoss will connect to the database. Let us create a file named “LoginJSFRealm-ds.xml” inside the folder “/JBOSS_THAT_YOU_UNZIPPED/server/default/deploy/“:

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

<datasources>
    <local-tx-datasource>
        <jndi-name>LoginJSFRealmDS</jndi-name>
        <connection-url>jdbc:postgresql://localhost:5432/LoginJSF</connection-url>
        <driver-class>org.postgresql.Driver</driver-class>
        <user-name>postgres</user-name>
        <password>YOUR_PASSWORD</password>
        <!-- sql to call when connection is created. Can be anything, select 1 is valid for PostgreSQL <new-connection-sql>select 1</new-connection-sql> -->

        <!-- sql to call on an existing pooled connection when it is obtained from pool. Can be anything, select 1 is valid for PostgreSQL <check-valid-connection-sql>select
            1</check-valid-connection-sql> -->

        <!-- corresponding type-mapping in the standardjbosscmp-jdbc.xml (optional) -->
        <metadata>
            <type-mapping>PostgreSQL 7.2</type-mapping>
        </metadata>
    </local-tx-datasource>
</datasources>

To finish this tutorial, copy the Postgres Driver JDBC 4 that you downloaded into the folder “/JBOSS_THAT_YOU_UNZIPPED/server/default/lib/“.

Let us run our application! \o/

Access the public link first: http://localhost:8080/LoginJSF/pages/public/public.html

Let us access the protected area where only the admin may enter: http://localhost:8080/LoginJSF/pages/protected/admin/admin.html

Type and user and the password and it is done. You will log in.

What if a USER accesses the admin URL? What will happen?

Try the same with the USER trough the URL: http://localhost:8080/LoginJSF/pages/protected/user/user.html
We cannot forget about the message that were be displayed in our console:
With the filter you will be able to search in your database for a user with the user name typed in the login page and put the user in the session.

Note 1: Your filter will be invoked only if the user login with success. The job to validate if the password/login is correct we leave to the JBoss.

Note 2: If you type the right login/password and the error page is displayed check again the JBoss datasource data, if the datasource name declared in the login-config.xml is the same declared in the file with the “-ds.xml” in its name, if the configurations inside the datasource are correct like: password, database, connection URL.

Note 3: Just after I finish the post I notice that I named the pages with html and not with xhtml (user.html,admin.html, pubilc.html). You can rename this pages and access again to see the JSF working (do not forget to change the link also before you access, it will finish with xhtml and not html). My bad! =/

I hope this post helps you.

Any doubt, question or suggestions just post it.

See you soon! \o_

Response to User Login Validation with JAAS and JSF

  1. Abelardo Barbosa

    Ops, há alguma diferença razoável de performance entre as opções de validação de login por JAAS:

    através de um managedBean:

    ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
    String user = context.getUserPrincipal().getName();

    através de filter:

    String userName = SecurityAssociation.getPrincipal().getName();

    Obs: considerando que na opção de managedBean, eu pego o usuário quando autenticado e guardo num bean de sessão, e que o uso do filter, o usuário vai ter que passar pelo filter em todas as requisições.

    Estou desenvolvendo uma rede social em Java e minha dúvida é se preciso realmente usar filtro para validar o login, que aparentemente acredito perder um pouco de performance.

    Você poderia me dar uma luz sobre essa comparação?

    • uaihebert Post author

      Abelardo, boa tarde.

      Nesse caso te recomendaria fazer um teste de carga aplicado ao contexto real do seu projeto.

      Crie uma máquina virtual que será igual a máquina de produção, crie os casos de testes usando o SOAP ui e mande muitas requisições e veja como cada caso se comporta.

      Obrigado pela visita.

  2. xalibeut

    hi uaihebert,
    so the login module you are using is provided by jboss :
    org.jboss.security.auth.spi.DatabaseServerLoginModule

    Suppose you want to handle password expiration in your example;
    lets say :
    - we add maxPwdAge=90 days
    - we add a column lastModifiedPwdTime (storing last time the password has been created/changed)
    How would you modify the login module to handle it?
    More specifically, how would you redirect, from the login-module to a change password jsf page?
    Thanks
    Thanks

    • uaihebert Post author

      Hello xalibeut,

      I believe that you will need to implement your own JAAS module. I do not think that there is a way of doing it with the default JAAS implementation.

      I do not have any tutorial in the blog to give to you, but if you search in google you will find: “implement jaas module jboss”

      Thanks for passing by.

  3. youngc

    great article here. I hope I can pose a question to you.

    I am starting to research JAAS as an alternative to my applications own authentication mechanism. The problem is that we also have web services built into the applicationthat use basic authentication defined in the web.xml. Since we can only have one login-config in the web.xml, is it possible to use form authentication for logged-in users while basic authentication for the soap web service? Or do we need to strip out the web services into a seperate application?

    Thanks!

    • uaihebert Post author

      Hello Youngc,

      You should search for Single Sing On approaches, that you help you.

      I am sorry for the late response, I am very busy these days.

  4. Zen

    Hi, Im a newbie…
    My company is currently running an app on Jboss4.2.3.
    I need to have a secured login page. I followed your instructions above, but when I login, I get the following error:
    ERROR [security.auth.spi.UsersRolesLoginModule] [] – Failed to load users/passwords/role files
    java.io.IOException: No properties file: users.properties or defaults: defaultUsers.properties found
    at org.jboss.security.auth.spi.Util.loadProperties(Util.java:315)
    at org.jboss.security.auth.spi.UsersRolesLoginModule.loadUsers(UsersRolesLoginModule.java:186)
    at org.jboss.security.auth.spi.UsersRolesLoginModule.createUsers(UsersRolesLoginModule.java:200)
    at org.jboss.security.auth.spi.UsersRolesLoginModule.initialize(UsersRolesLoginModule.java:127)
    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 javax.security.auth.login.LoginContext.invoke(LoginContext.java:756)
    at javax.security.auth.login.LoginContext.access$000(LoginContext.java:186)
    at javax.security.auth.login.LoginContext$4.run(LoginContext.java:683)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680)
    at javax.security.auth.login.LoginContext.login(LoginContext.java:579)
    at org.apache.catalina.realm.JAASRealm.authenticate(JAASRealm.java:361)
    at org.apache.catalina.authenticator.FormAuthenticator.authenticate(FormAuthenticator.java:257)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:416)
    at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)

    PLease advise….
    Thanks and best regards,

    • uaihebert Post author

      Hello Zen,

      I am sorry, but I cannot help you with JBoss 4.x right now. =(

      It has a lot of different configurations/annotations than the earlier versions.

      I cannot searh in the web right now for that, I am out o time. =/

      Thanks for passing by.

  5. Manuel Loayza

    Great post.

    I have a small inconveniences because of I’ve used Jboss 7.1

    1. Use in the filter as following:
    String userName = SecurityContextAssociation.getPrincipal().getName();

    2. As I used standalone profile I had added in the standalone.xml.

    It shoould be into the subsystemL

    3. I have used the Jboss-CLI in order to deploy the JBDC 4 driver for PostgreSQL. After execute the .bat file, I wrote in the prompt

    [standalone@localhost:9999 /] deploy D:\AS\postgresql-9.1-902.jdbc4.jar

    4. For datasource, I’ve use the web console in the URL

    http://localhost:9990

    In the next URL:

    https://docs.jboss.org/author/display/AS71/DataSource+configuration

    You can find more useful information about datasource.

    More information about Jboss 7 Security Model

    https://community.jboss.org/wiki/JBossAS7SecurityDomainModel

    Thank you.

    • uaihebert Post author

      Hello Manuel,

      Thanks for sharing this information.

      I put your two messages together.

      Thanks for passing by. [=

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Current day month ye@r *