Search This Blog

Sunday, February 6, 2011

JDBC security realm with glassfish and jsf


Security realm is very important aspect of almost every application.

In this post I'll show an example of glassfish JDBC realm authentication through simple jsf web application. Example includes glassfish and web app configuration. At the end of this post is also source code for download.

When dealing with web application security you have two choices: implement your own application security or use container based security solution. I prefer second choice because you now that it has been implemented by security professionals and it requires very little coding. For the most part, securing an application is achieved by setting up users and security groups in a security realm in application server.
But if the constraints of container-managed security don't fit your application requirements then you can implement your own application-managed security from scratch or on top of existing container-managed facilities.

Web application security is actually broken to authentication (process of verifying user identity) and authorization (process of determining whether a user has access to a particular resource or task, and it comes into play once a user is authenticated.

In this post I will demonstrate how to set up web application form based login using glassfish application server(version 3.0.1) and java server faces (Mojarra 2.0.4).

Let's assume you have web application and two type of users: regular users (who can access /users/ part of the site) and admin users (who in addition to being able to access users resources, have access to /admin/ part of the site). Users that are not authenticated can access only public part of the site.

We want our application to use JDBC realm to read information about user and user groups.

Security realms are, in essence, collections of users and related security groups. User can belong to one or more security group and groups that user belongs to define what actions the system will allow the user to perform. For application container, realm is interpreted as string to identify a data store for resolving username and password information.

1. To begin, we will first create database with following structure:
ERA model of user and user groups


Our database has three tables. Users table holding user information, a groups table holding group information and join table between users and groups as there is a many-to-many relationship. I created also a view v_user_role that joins data from users and groups tables. I'll explain this later.

CREATE TABLE `groups` (

  `group_id` int(10) NOT NULL,

  `group_name` varchar(20) NOT NULL,

  `group_desc` varchar(200) DEFAULT NULL,

  PRIMARY KEY (`group_id`)

);

CREATE TABLE `users` (

  `user_id` int(10) NOT NULL AUTO_INCREMENT,

  `username` varchar(10) NOT NULL,

  `first_name` varchar(20) DEFAULT NULL,

  `middle_name` varchar(20) DEFAULT NULL,

  `last_name` varchar(20) DEFAULT NULL,

  `password` char(32) NOT NULL,

  PRIMARY KEY (`user_id`)

);

CREATE TABLE `user_groups` (

  `user_id` int(10) NOT NULL,

  `group_id` int(10) NOT NULL,

  PRIMARY KEY (`user_id`,`group_id`),

  KEY `fk_users_has_groups_groups1` (`group_id`),

  KEY `fk_users_has_groups_users` (`user_id`),

  CONSTRAINT `fk_groups` FOREIGN KEY (`group_id`) REFERENCES `groups` (`group_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,

  CONSTRAINT `fk_users` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION

); 

CREATE VIEW `v_user_role` AS

SELECT  u.username, u.password, g.group_name

 FROM `user_groups` ug

 INNER JOIN `users` u ON u.user_id = ug.user_id

 INNER JOIN `groups` g ON g.group_id =  ug.group_id; 

INSERT  INTO `groups`(`group_id`,`group_name`,`group_desc`) VALUES 
  (1,'USER','Regular users'),
  (2,'ADMIN','Administration users');
  
INSERT  INTO `users`(`user_id`,`username`,`first_name`,`middle_name`,`last_name`,`password`) VALUES 
  (1,'john','John',NULL,'Doe','6e0b7076126a29d5dfcbd54835387b7b'), /*john123*/
  (2,'admin',NULL,NULL,NULL,'21232f297a57a5a743894a0e4a801fc3'); /*admin*/
  
INSERT  INTO `user_groups`(`user_id`,`group_id`) VALUES (1,1),(2,1),(2,2);



2. Now that we have database that will hold user credentials, we are ready to create connection to our database through glassfish administration console.

Go to glassfish administration console and go to Resources-Connection Pools and choose New.
Enter name and choose resource type and click next.


Create JDBC connection pool

On second step just leave defaults for now and click finish.

Create JDBC connection pool step2


 
After that select your connection pool, go to Additional properties and add properties as on picture bellow.

Configure connection properties


When done, you can ping database to see if connection is set up properly.

If connection succeeded click on JDBC resources and click on new. JNDI name of this resource will be provided to jdbc security realm to obtain database connection. Enter some JNDI name and choose your connection pool.

 
Create resource


3. Once we have the database that will hold user credentials and JDBC connection to our database, we can setup JDBC realm. Go to Configuration-Security-Realms-New. 

Create JDBC security realm

 
Enter name for this jdbc realm (this name will be used in web.xml) and select JDBCRealm in select box. Enter properties as on picture above.
The value of JNDI property must be the JNDI name of the data source corresponding to the database that contains the realm's user and group data.

Interesting part here is that for user table and group table I used v_user_role as the value for the property. v_user_role is a database view that contains both user and group information. The reason i didn't use the users table directly is because glassfish assumes that both the user table and the group table contain a column containing the user name and that would result in duplicate data.

You can enter properties as I did and all other properties are optional and can be left blank.

4. Once we have defined JDBC realm we need to configure our application. All authentication logic is taken care of by the application server, so we only need to make modifications in order to secure the application in its deployment descriptors, web.xml and sun-web.xml.

Add following snippet to web.xml file.
<login-config>
    <auth-method>FORM</auth-method>
    <realm-name>jdbc-realm</realm-name>
    <form-login-config>
      <form-login-page>/faces/login.xhtml</form-login-page>
      <form-error-page>/faces/loginError.xhtml</form-error-page>
    </form-login-config>
    </login-config>

login-config element defines authorization method for the application (in this case form will be displayed to end user to authenticate) and security realm that is used for authorization. We define login and login error pages. If user tries to go to restricted url without authenticating first, he will be redirected to login page first. If authentication fails, he will be redirected to loginError page.

<security-constraint>
    <web-resource-collection>
      <web-resource-name>Admin user</web-resource-name>
      <url-pattern>/faces/admin/*</url-pattern>
      <http-method>GET</http-method>
          <http-method>POST</http-method>
    </web-resource-collection>
    <auth-constraint>
      <role-name>ADMIN</role-name>
    </auth-constraint>
  </security-constraint>
  
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Admin user</web-resource-name>
      <url-pattern>/faces/users/*</url-pattern>
      <http-method>GET</http-method>
          <http-method>POST</http-method>
    </web-resource-collection>
    <auth-constraint>
      <role-name>ADMIN</role-name>
      <role-name>USER</role-name>
    </auth-constraint>
  </security-constraint>



security-constraint element defines who can access pages mathcing a certain URL pattern. Roles allowed to access the pages are defined in the element.

We want only admin users to have access to /admin/* resources. Regular users and admin users both are allowed to access /users/* resources.

Before we can successfully authenticate our users, we need to link the user roles defined in web.xml with the groups defined in the realm. We do this linking in sun-web.xml deployment descriptor.

<security-role-mapping>
    <role-name>ADMIN</role-name>
    <group-name>ADMIN</group-name>
  </security-role-mapping>
  <security-role-mapping>
    <role-name>USER</role-name>
    <group-name>USER</group-name>
  </security-role-mapping>


sun-web.xml deployment descriptor can have one or more elements. One of these elements for each role defined in web.xml.

5. So, if user enters url something like http://host/showcase/faces/users/users.xhtml he will be redirected to login page. Code for login page is bellow.

<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" name="j_username" />

      <h:outputLabel for="j_password" value="Password" />
      <input type="password" name="j_password" />

      <h:outputText value="" />
      <h:panelGrid columns="2">
        <input type="submit" name="submit" value="Login" />
        <h:button outcome="index" value="Cancel" />
      </h:panelGrid>
    </h:panelGrid>
  </form>
</h:body>

The important thing to note is that j_security_check, j_username and j_password attributes are required by container-managed authentication and shouldn't be renamed.
After user authenticates, he will be redirected to requested url, in our case to /users/users.xhtml. Users page has just one simple link for logout.
<h:body>
  <p>Welcome to user pages</p>
  <h:form>
    <h:commandButton action="#{authBackingBean.logout}" value="Logout" />
  </h:form>
</h:body>

And bean that executes logout is:

@ManagedBean
@RequestScoped
public class AuthBackingBean {
  
  private static Logger log = Logger.getLogger(AuthBackingBean.class.getName());
  
  public String logout() {
    String result="/index?faces-redirect=true";
    
    FacesContext context = FacesContext.getCurrentInstance();
    HttpServletRequest request = (HttpServletRequest)context.getExternalContext().getRequest();
    
    try {
      request.logout();
    } catch (ServletException e) {
      log.log(Level.SEVERE, "Failed to logout user!", e);
      result = "/loginError?faces-redirect=true";
    }
    
    return result;
  }
}

As you can see, Servlet 3.0, wich is new in Java EE 6, has method called logout() that will make the container unaware of the authentication credentials.

So, what we have done is that when regular user tries to access /users/somePage.xhtml he will be redirected to login page and will have to authenticate. After he submits his credentials he will be allowed to access requested resources. If he tries to access /admin/ resources he will get access denied. Admin user on the other hand, when authenticated, he will be allowed to acces /admin/ and /users/ resources.

Source code can be downloaded from the following link:
LoginApp.war

Migrating to glassfish 3.1.1 version

I see a lot of people have problems running this example on GlassFish version 3.1.1. Only thing that needs to be configured is  Digest Algorithm in realm configuration page.
By default glassfish 3.0.1 version assumed MD5 digest algorithm if nothing was set for this property - as in my example. Glassfish 3.1.1 version by default assumes SHA-256 so we need to set MD5 digest algorithm (since passwords in my sql script are in MD5 format).
Go to Configurations - server-config - security -realm and edit realm by setting Digest Algorithm to MD5 (or any other algorithm depending what you use).

And that's it. I tried and it works ok.