Search This Blog

Loading...

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.

59 comments:

  1. Thank you. Very useful tutorial.

    ReplyDelete
  2. Thank you for excellent tutorial.

    How can I add /public resource which will be allowed for all users (even anonymous)?

    Now when I got access into protected part of application, and then turn from it in /public area, and from there back into protected resource, I was thrown to the login page... Why this happening? User was already authenticated, the session was not interrupted...

    Excuse me for my bad English.

    ReplyDelete
  3. Hm,strange behaviour that you described.

    By default if security constraint is not added to web.xml for some url pattern (in your case /public) then all users should have access to that part of the site.

    How does secured url pattern looks like in your case?

    ReplyDelete
  4. I have a slightly different problem. Security constraints for /public pages aren't added to web.xml and these pages are accessible to all users (including not authenticated). Problem in the next:

    If an authenticated user tries to go to the /public pages it goes. But when he tries to go back (within one session) into protected area (e.g. /users) Glassfish again redirects it to the login page. That is, the authentication data was lost when user has left the protected area. Patterns are shown below:

    /faces/pages/app/admin/*
    /faces/pages/app/users/*

    Public pages are available in /faces/pages/app/public.

    ReplyDelete
  5. I tried your exact scenario and everything is working fine for me. I am using GlassFish 3.0.1 (build 22).

    I added download link at the end of tutorial so you can see if it helps.

    Problem you described is what you would get if you would delete session cookie in browser or something like that.
    Try to see with firebug when you try to go to protected part of the site again if in request header is set correct JSESSIONID cookie.

    ReplyDelete
  6. Thank you very much! Your project was successfuly deployed on my server. It became obvious that error in source code. One page was a commentary in the code in which EL expression was ("#{authBean.logout()}")! Obviously EL expression (#{}) shielded from comment :( This is why session was interrupted. Excuse for my English. Thanks.

    ReplyDelete
  7. Thank you so much.
    Please add that Glassfish 3.1 uses sha256 instead of md5 hashes by default. Needed some time to figure that out.
    Keep up the good work.

    ReplyDelete
  8. Thanks for info about default hash! I added comment to article.

    ReplyDelete
  9. Hi this is very urgent, i am having an exam tomorrow. I tried your example and i got the following error
    /users/pageU.xhtml Not Found in ExternalContext as a Resource
    java.io.FileNotFoundException: /users/pageU.xhtml Not Found in ExternalContext as a Resource

    Any idea, what might be causing it ?

    http://localhost:8080/PastryShop/faces/users/userlogin.xhtml

    userlogin.xhtml , is inside a folder called 'users' . please reply to this forum ASAP anyone if you know the solution. HELP!

    ReplyDelete
  10. ignore the above comment, i resolved it. i am now having a differnt problem. when i enter the username and password, i only end up in the error page. I believe that i have done everything in accordance to your tutorial. What might had gone wrong any clue ?

    ReplyDelete
  11. Did you check for errors in your log? Looks like problem with realm configuration. I had problem at first because glassfish used default file realm for authentication instead of my jdbc-realm(don't know the reason). I changed the default realm from file to jdbc-realm under security options and then it worked fine.

    ReplyDelete
  12. Thank you very much for this. This helped me a LOT!

    ReplyDelete
  13. Hello, I did everything I said here, but when you run the probrama (using netbeans), I throws the following exception:

    com.sun.enterprise.security.auth.login.common.LoginException: Login failed: No se han configurado LoginModules para jdbcRealm
    at com.sun.enterprise.security.auth.login.LoginContextDriver.doPasswordLogin(LoginContextDriver.java:394)
    at com.sun.enterprise.security.auth.login.LoginContextDriver.login(LoginContextDriver.java:240)
    at com.sun.enterprise.security.auth.login.LoginContextDriver.login(LoginContextDriver.java:153)
    at com.sun.web.security.RealmAdapter.authenticate(RealmAdapter.java:483)
    at com.sun.web.security.RealmAdapter.authenticate(RealmAdapter.java:425)
    .
    .
    .

    Caused by: javax.security.auth.login.LoginException: No se han configurado LoginModules para jdbcRealm
    at javax.security.auth.login.LoginContext.init(LoginContext.java:256)
    at javax.security.auth.login.LoginContext.(LoginContext.java:367)
    at javax.security.auth.login.LoginContext.(LoginContext.java:444)
    at com.sun.enterprise.security.auth.login.LoginContextDriver.doPasswordLogin(LoginContextDriver.java:381)
    ... 29 more

    ReplyDelete
  14. It seems to me that you have jdbcRealm for security realm name set in your web.xml instead of jdbc-realm.

    ReplyDelete
  15. Just in case it can help someone, for me it didn't work until I set a JNDI Name for the JDBC Resource with the prefix jdbc/

    I know that in this article the example comes with the prefix, but I already had the resource created without it and I didn't think it was important.

    Thanks for the article!
    JD

    ReplyDelete
  16. Is there someone using JDBC Realm with Glassfish 3.1.1? Seems to me that there is BUG in this verison...

    ReplyDelete
  17. Nice tutorial!

    I have a question about logging in this use case:
    How can I configure glassfish to log successful and not successfull user logins?

    Thanks
    Dieter Tremel

    ReplyDelete
  18. Check out http://jugojava.blogspot.com/2011/07/jsf-form-authentication-on-servlet-3.html article. There is an example of programmatic login that allows more control over the login process. There you can log successful and not successfull logins.

    ReplyDelete
  19. Thank You for your quick answer!
    I already have implemented a first prototype that works.

    ReplyDelete
  20. Like some other comments, I have tried this with Edition 3.1.1 (build 12) and I can't get it working ... anyone had luck / tricks with 3.1.1 ?

    ReplyDelete
  21. tutorial very good and hopefully can help me in building an application thanks

    ReplyDelete
  22. Avrono, I tried yesterday with 3.1.1 Edition with no luck. It looks like it is Glassfish problem. I see on other forums and blogs that people have similar problems..

    ReplyDelete
  23. Thanks for this good tutorial. But I still have one question. What is the difference between server-config and default-config?
    Where I have to create the security realms?

    ReplyDelete
  24. Hi Gordan Jugo,
    Thank you so much for this article. I'm stuck with one issue, may be my configurations might be wrong. I did exactly the same thing as you did in this tutorial..
    The below if condition is always returning false, and the page gets redirected to user's menu.

    if(request.isUserInRole("ADMIN")) {
    return "/admins/admins?faces-redirect=true";
    } else {
    return "/users/users?faces-redirect=true";
    }

    ReplyDelete
  25. Avrono: problem with glassfish 3.1 for this example is with digest algorithm on security realm. I added explanation to article.

    Anonymous: You create security realms in server-config. default-config is a special configuration that acts as a template and can only be copied to create configurations.

    Shiva: This happens after you make a successful login? You must have mapping in sun-web.xml or glassfish-web.xml, declared roles in web.xml and added groups in database.

    ReplyDelete
  26. Hi Gordan,
    Is there any rule that the name of the database/table/ column names for authentication. OR should mapping them appropriately in the glassfish realm admin take care of it?

    ReplyDelete
  27. Gordan: I've done exactly the same steps that you explained in the tutorial. I've added the roles in the web.xml as well as in the database. Not sure. :( Anyhow, thanks for your reply. Will try to find the issue.

    ReplyDelete
  28. Shiva,
    I fixed the same problem by adding annotation to my controller (managed bean)

    @DeclareRoles({"ADMIN", "USER"})

    ReplyDelete
  29. Gunz,
    configuration in realm config page will take care of it. Database is obtained via JNDI (db parameters are configured in connection pool), and you must tell security realm which table holds user information (username, password) and which table holds group information(mapping for user and his associated groups).

    ReplyDelete
  30. can't download loginApp.war ..file help pleese

    ReplyDelete
  31. thanks! it was exactly what i was looking for.
    i have just a question how to create a new user. When i try to save a Users with jpa, i get a ConstraintViolationException:
    Caused by: javax.validation.ConstraintViolationException: Bean Validation constraint(s) violated while executing Automatic Bean Validation on callback event:'prePersist'. Please refer to embedded ConstraintViolations for details.
    Do you have any hints for me??

    Thx a lot!

    ReplyDelete
  32. Hi !

    I download LoginApp. It`s OK !
    Jdbc and realm it`s OK !

    When I try login with admin admin I throws the following error:

    Advertência: Não foi possível encontrar o componente com a ID j_username na exibição.
    Advertência: Não foi possível encontrar o componente com a ID j_password na exibição.
    Grave: SEC1112: Cannot validate user [admin] for JDBC realm.
    Advertência: WEB9102: Web Login Failed: com.sun.enterprise.security.auth.login.common.LoginException: Login failed: Security Exception
    Advertência: Exception
    com.sun.enterprise.security.auth.login.common.LoginException: Login failed: Security Exception
    at com.sun.enterprise.security.auth.login.LoginContextDriver.doPasswordLogin(LoginContextDriver.java:394)
    at com.sun.enterprise.security.auth.login.LoginContextDriver.login(LoginContextDriver.java:240)
    at com.sun.enterprise.security.auth.login.LoginContextDriver.login(LoginContextDriver.java:15

    Can help me ?

    Cristian

    ReplyDelete
  33. Thank you, very good blog! I have JDBC-authentication working in my training software in Glassfish 3.1.1. How can I change my welcome page after login? Without authentication user goes to index.xhtml and after login user goes to different page after controller class has fetched some data from database? Login--->JDBC-realm--->method in controller class--->DB---->view. How can I implement that?

    Thank you very much for your blog!
    Sami

    ReplyDelete
  34. Gordan, this was a well done article. Thank you for sharing.

    ReplyDelete
  35. Nice, thanks for the post. One error I came across was due to MySQL case sensitivity differences between Win and Linux for table names. I would have no problem with Win, where I was developing, then it wouldn't work on Linux where the app was deployed. I turned on the global logging on MySQL to examine the queries from Glassfish during login. The glassfish query referred to a table "Users" but the table name was actually "users". This happened regardless of what I entered in jdbc realm creation page (i.e. "users"). The MySQL parameter that fixed this issue was "lower_case_table_names=1" in my.cnf file.

    ReplyDelete
  36. Superb, my friend. Why couldn't Oracle publish tutorials like this?

    ReplyDelete
  37. Oh dear, I'm having no joy. I've followed your guide to the letter. I'm on glassfish 3.1.2. When I click to login, absolutely nothing happens. No log messages, no error messages. Nothing. What gives?

    ReplyDelete
  38. Gordan, this was a well done article. Thank you for sharing. But I have a question. Can I use other columns in the user table? I have something like 'user status'. Can I indicate the rules for authentication ? or is better if I authenticate first only with username and password and then apply my own rules ? The problem that I see with this approach is that I have tu go to the database twice. The other solution I think is create a custom realm. What do you think about all of this ?

    ReplyDelete
  39. Gracias! Lo he implementado con GlassFish 3.1.1 y usé SHA-256.

    ReplyDelete
  40. cheers buddy...have being a full day on this, you page really helped.

    ReplyDelete
  41. You don't even know how much you helped me :D Thank you so much

    ReplyDelete
  42. Thanks Gordan!
    Can I translate it to Portuguese and post on the forum here in Brazil, but referencing your site?

    ReplyDelete
  43. Great post!
    But I could not understand how it authenticates the user, ie, how it is checked if the password is correct.

    ReplyDelete
    Replies
    1. Glassfish does that for you through configured jdbc realm. You just need to provide error page in configuration in case if auth fails.

      Delete
    2. Thank you!
      A question ...
      For my password I use to own the entered password and id ususario to generate a MD5, lest someone put the MD5 of a User in another User through the database to access. In your example, the MD5 is generated only with the password value. correct?

      Delete
    3. Thanks, Your post is very well explained!
      I applied here, and it worked perfectly.
      As for the password ... I want to generate the md5 with my rule. Do you know any way for me to authenticate the User. And send the User to the realm?

      Delete
  44. Thanks so much for this post. It was really informative.

    I have done everything as per the post, however, am getting the following error accessing Admin pages

    WARNING: Unable to find component with ID j_username in view.
    WARNING: Unable to find component with ID j_password in view.

    What am I not doing right?

    David

    ReplyDelete
    Replies
    1. same for me :(
      No other exceptions but this warnings. Result is that I end up directly with 403 pages after login... can't find a solution for this problem

      Delete
  45. Hello! After having some troubles with your tutorial I want to add this:
    I got a Warning like:
    Warnung: Keine Principals zugeordnet zu Rolle [USER].
    (Warning: No principal mapped to role [USER])
    and same for ADMIN.

    I found out that, at least for glassfish 3.1.1, you have to put the role mapping information into a file called glassfish-web.xml in the WEB-INF folder, which looks like this:



    USER
    USER



    The group name is the name of your user-group in the database!

    Anyway, thanks for the tutorial, it's much more straight-forward that oracle's s*** documentation ;)

    ReplyDelete
    Replies
    1. xml was removed, good guy ;)
      look here: http://pastebin.com/6WYAYQJ5

      Delete
  46. Hi

    Can you provide this example for ejb based web service application too?

    ReplyDelete
  47. Hi, I got two issues that I hope you can help me with :), well I'm unable to download the LoginAPP hehe, and the second one: I can't put all the information in practice, it just dont work

    ReplyDelete
  48. Excellent article! The idea of using a view solved all my problems with Glassfish security. One point worth mentioning, if new users are not a member of any groups they won't show up in the view, and cannot login. Code to create new users should add the user to a default group.

    ReplyDelete
  49. Great job. helped e a lot. Your style of going through the technical bits is good. Thanks for the code snippets and theory.

    ReplyDelete
  50. What is the password to use for both john & admin?

    ReplyDelete
  51. 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*/

    john -> john123
    admin -> admin

    Be attention ;)

    ReplyDelete