Enterprise grade Java.
You'll read about Conferences, Java User Groups, Java, Integration, Reactive, Microservices and other technologies.

Friday, January 25, 2013

JDBC Realm and Form Based Authentication with GlassFish 3.1.2.2 and Primefaces 3.4

12:44 Friday, January 25, 2013 Posted by Unknown 17 comments:
, , ,
One of the most popular posts on my blog is the short tutorial about the JDBC Security Realm and form based Authentication on GlassFish with Primefaces. After I received some comments about it that it isn't any longer working with latest GlassFish 3.1.2.2 I thought it might be time to revisit it and present an updated version. Here we go:

Preparation
As in the original tutorial I am going to rely on some stuff. Make sure to have a recent NetBeans 7.3 beta2 (which includes GlassFish 3.1.2.2) and the MySQL Community Server (5.5.x) installed. You should have verified that everything is up an running and that you can start GlassFish and the MySQL Server also is started.

Some Basics
A GlassFish authentication realm, also called a security policy domain or security domain, is a scope over which the GlassFish Server defines and enforces a common security policy. GlassFish Server is preconfigured with the file, certificate, and administration realms. In addition, you can set up LDAP, JDBC, digest, Oracle Solaris, or custom realms. An application can specify which realm to use in its deployment descriptor. If you want to store the user credentials for your application in a database your first choice is the JDBC realm.

Prepare the Database
Fire up NetBeans and switch to the Services tab. Right click the "Databases" node and select "Register MySQL Server". Fill in the details of your installation and click "ok". Right click the new MySQL node and select "connect". Now you see all the already available databases. Right click again and select "Create Database". Enter "jdbcrealm" as the new database name. Remark: We're not going to do all that with a separate database user. This is something that is highly recommended but I am using the root user in this examle. If you have a user you can also grant full access to it here. Click "ok". You get automatically connected to the newly created database. Expand the bold node and right click on "Tables". Select "Execute Command" or enter the table details via the wizard.

CREATE TABLE USERS (
  `USERID` VARCHAR(255) NOT NULL,
  `PASSWORD` VARCHAR(255) NOT NULL,
  PRIMARY KEY (`USERID`)
);

CREATE TABLE USERS_GROUPS (
  `GROUPID` VARCHAR(20) NOT NULL,
  `USERID` VARCHAR(255) NOT NULL,
  PRIMARY KEY (`GROUPID`)
);

That is all for now with the database. Move on to the next paragraph.

Let GlassFish know about MySQL
First thing to do is to get the latest and greatest MySQL Connector/J from the MySQL website which is 5.1.22 at the time of writing this. Extract the mysql-connector-java-5.1.22-bin.jar file and drop it into your domain folder (e.g. glassfish\domains\domain1\lib). Done. Now it is finally time to create a project.

Basic Project Setup
Start a new maven based web application project. Choose "New Project" > "Maven" > Web Application and hit next. Now enter a name (e.g. secureapp) and all the needed maven cordinates and hit next. Choose your configured GlassFish 3+ Server. Select Java EE 6 Web as your EE version and hit "Finish". Now we need to add some more configuration to our GlassFish domain.Right click on the newly created project and select "New > Other > GlassFish > JDBC Connection Pool". Enter a name for the new connection pool (e.g. SecurityConnectionPool) and underneath the checkbox "Extract from Existing Connection:" select your registered MySQL connection. Click next. review the connection pool properties and click finish. The newly created Server Resources folder now shows your sun-resources.xml file. Follow the steps and create a "New > Other > GlassFish > JDBC Resource" pointing the the created SecurityConnectionPool (e.g. jdbc/securityDatasource).You will find the configured things under "Other Sources / setup" in a file called glassfish-resources.xml. It gets deployed to your server together with your application. So you don't have to care about configuring everything with the GlassFish admin console.Additionally we still need Primefaces. Right click on your project, select "Properties" change to "Frameworks" category and add "JavaServer Faces". Switch to the Components tab and select "PrimeFaces". Finish by clicking "OK". You can validate if that worked by opening the pom.xml and checking for the Primefaces dependency. 3.4 should be there. Feel free to change the version to latest 3.4.2.

Final GlassFish Configuration
Now it is time to fire up GlassFish and do the realm configuration. In NetBeans switch to the "Services" tab again and right click on the "GlassFish 3+" node. Select "Start" and watch the Output window for a successful start. Right click again and select "View Domain Admin Console", which should open your default browser pointing you to http://localhost:4848/. Select "Configurations > server-config > Security > Realms" and click "New..." on top of the table. Enter a name (e.g. JDBCRealm) and select the com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm from the drop down. Fill in the following values into the textfields:
JAASjdbcRealm
JNDIjdbc/securityDatasource
User Tableusers
User Name Columnusername
Password Columnpassword
Group Tablegroups
Group Name Columngroupname
Leave all the other defaults/blanks and select "OK" in the upper right corner. You are presented with a fancy JavaScript warning window which tells you to _not_ leave the Digest Algorithm Field empty. I field a bug about it. It defaults to SHA-256. Which is different to GlassFish versions prior to 3.1 which used MD5 here. The older version of this tutorial didn't use a digest algorithm at all ("none"). This was meant to make things easier but isn't considered good practice at all. So, let's stick to SHA-256 even for development, please.

Secure your application
Done with configuring your environment. Now we have to actually secure the application. First part is to think about the resources to protect. Jump to your Web Pages folder and create two more folders. One named "admin" and another called "users". The idea behind this is, to have two separate folders which could be accessed by users belonging to the appropriate groups. Now we have to create some pages. Open the Web Pages/index.xhtml and replace everything between the h:body tags with the following:

 <h:body>
        Select where you want to go:
        <br />
        <h:link outcome="admin/index" value="To the admin section" /><br />
        <h:link outcome="users/index" value="To the user section" />
    </h:body>

Now add a new index.xhtml to both users and admin folders. Make them do something like this:
 <h:body>
        <h1>Hello Admin|User</h1>
        <br />
        <h:link outcome="/index" value="Back to Homepage" />
    </h:body>


On to the login.xhtml. Create it with the following content in the root of your Web Pages folder.
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:p="http://primefaces.org/ui"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>Login Form</title>
    </h:head>
    <h:body>
        <p:panel header="Login From">
            <form method="POST" action="j_security_check">
                Username: <input type="text" name="j_username" />
                Password: <input type="password" name="j_password" />
                <br />
                <input type="submit" value="Login" />
                <input type="reset" value="Reset" />
            </form>
        </p:panel>
    </h:body>
</html>


As you can see, whe have the basic Primefaces p:panel component which has a simple html form which
points to the predefined action j_security_check. This is, where all the magic is happening. You also have to include two input fields for username and password with the predefined names j_username and j_password. Now we are going to create the loginerror.xhtml which is displayed, if the user did not enter the right credentials. (use the same DOCTYPE and header as seen in the above example).
 <h:body>
        <p:panel header="Login Error">
            Sorry, you made an Error. Please try again: <a href="#{facesContext.externalContext.requestContextPath}/" >Login</a>
        </p:panel>
    </h:body>


The only magic here is the href link of the Login anchor. We need to get the correct request context and this could be done by accessing the faces context. If a user without the appropriate rights tries to access a folder he is presented a 403 access denied error page. If you like to customize it, you need to add it and add the following lines to your web.xml:
<error-page>
<error-code>403</error-code>
<location>/faces/403.xhtml</location>
</error-page>

That snipped defines, that all requests that are not authorized should go to the 403 page. If you have the web.xml open already, let's start securing your application. We need to add a security constraint for any protected resource. Security Constraints are least understood by web developers, even though they are critical for the security of Java EE Web applications. Specifying a combination of URL patterns, HTTP methods, roles and transport constraints can be daunting to a programmer or administrator. It is important to realize that any combination that was intended to be secure but was not specified via security constraints, will mean that the web container will allow those requests. Security Constraints consist of Web Resource Collections (URL patterns, HTTP methods), Authorization Constraint (role names) and User Data Constraints (whether the web request needs to be received over a protected transport such as TLS).
 <security-constraint>
        <display-name>Admin Pages</display-name>
        <web-resource-collection>
            <web-resource-name>Protected Admin Area</web-resource-name>
            <description></description>
            <url-pattern>/faces/admin/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
            <http-method>HEAD</http-method>
            <http-method>PUT</http-method>
            <http-method>OPTIONS</http-method>
            <http-method>TRACE</http-method>
            <http-method>DELETE</http-method>
        </web-resource-collection>
        <auth-constraint>
            <description/>
            <role-name>admin</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
    <security-constraint>
        <display-name>All Access</display-name>
        <web-resource-collection>
            <web-resource-name>None Protected User Area</web-resource-name>
            <description/>
            <url-pattern>/faces/users/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
            <http-method>HEAD</http-method>
            <http-method>PUT</http-method>
            <http-method>OPTIONS</http-method>
            <http-method>TRACE</http-method>
            <http-method>DELETE</http-method>
        </web-resource-collection>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

If the constraints are in place you have to define, how the container should challenge the user. A web container can authenticate a web client/user using either HTTP BASIC, HTTP DIGEST, HTTPS CLIENT or FORM based authentication schemes. In this case we are using FORM based authentication and define the JDBCRealm
<login-config>
        <auth-method>FORM</auth-method>
        <realm-name>JDBCRealm</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>

The realm name has to be the name that you assigned the security realm before. Close the web.xml and open the sun-web.xml to do a mapping from the application role-names to the actual groups that are in the database. This abstraction feels weird, but it has some reasons. It was introduced to have the option of mapping application roles to different group names in enterprises. I have never seen this used extensively but the feature is there and you have to configure it. Other appservers do make the assumption that if no mapping is present, role names and group names do match. GlassFish doesn't think so. Therefore you have to put the following into the glassfish-web.xml. You can create it via a right click on your project's WEB-INF folder, selecting "New > Other > GlassFish > GlassFish Descriptor"
    <security-role-mapping>
        <role-name>admin</role-name>
        <group-name>admin</group-name>
    </security-role-mapping>

That was it _basically_ ... everything you need is in place. The only thing that is missing are the users in the database. It is still empty ...We need to add a test user:

Adding a Test-User to the Database
And again we start by right clicking on the jdbcrealm database on the "Services" tab in NetBeans. Select "Execute Command" and insert the following:

INSERT INTO USERS VALUES ("admin", "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918");
INSERT INTO USERS_GROUPS VALUES ("admin", "admin");


You can login with user: admin and password: admin and access the secured area. Sample code to generate the hash could look like this:
 try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            String text = "admin";
            md.update(text.getBytes("UTF-8")); // Change this to "UTF-16" if needed
            byte[] digest = md.digest();
            BigInteger bigInt = new BigInteger(1, digest);
            String output = bigInt.toString(16);

            System.out.println(output);

        } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
            Logger.getLogger(PasswordTest.class.getName()).log(Level.SEVERE, null, ex);

        }


Have fun securing your apps and keep the questions coming! In case you need it, the complete source code is on https://github.com/myfear/JDBCRealmExample

17 comments:

  1. > do a mapping from the application role-names to the actual groups [...] This abstraction feels weird, but it has some reasons. It was introduced to have the option of mapping application roles to different group names in enterprises. I have never seen this used extensively but the feature is there and you have to configure it.

    As an optional feature this is perhaps a nice-to-have, but as a mandatory requirement it's IMHO atrocious. This server specific mandatory role mapping seems to be one of the main issues that makes Java EE security feel so awkward to many users.

    In a way, it's the Java EE security's equivalent to the old EJB home interface: a mandatory tedious construct that brings no direct value to most of its users, and those users thus loathe to use it.

    If there's enough support, perhaps this issue can be addressed at the spec level?

    >Other appservers do make the assumption that if no mapping is present, role names and group names do match. GlassFish doesn't think so.

    Indeed, although unfortunately there are more than a few appservers that require this role mapping, among others Geronimo and WebLogic also require this.

    ReplyDelete
    Replies
    1. Hi Arjan,

      I researched a bit. There actually _is_ a chance of default "principle to role mapping".
      http://docs.oracle.com/cd/E18930_01/html/821-2418/beacr.html

      asadmin set server-config.security-service.activate-default-principal-to-role-mapping=true
      asadmin set server-config.security-service.mapped-principal-class=CustomPrincipalImplClass

      Anyway, the mapping keeps making sense if you recall the different roles in the overall application life cycle. The ones developing aren't typically the ones deploying and configuring it according to the infrastructure. This additional indirection isn't bad at all.

      I'm also not a big fan of Java EE security as it is today. It indeed is a very special field which requires a lot of knowledge and is far away from developer productivity :) I try to keep this in focus but I guess it will not be an issue for EE 7 anymore. Happy to read a JIRA item written by you to be taken care of in EE 8!

      Thanks,
      - Markus

      Delete
  2. Hi,

    Indeed, GlassFish has the option to disable the mandatory mapping, but it's only accesible via a proprietary UI or console. It would be far more convenient if this was controllable via a context parameter in say web.xml (even better if such parameter was standardized).

    Even more unfortunate is that as far as I can see WebLogic and Geronimo don't have this option, and I wouldn't even know where to start looking for this on say JEUS.

    >Anyway, the mapping keeps making sense if you recall the different roles in the overall application life cycle. The ones developing aren't typically the ones deploying and configuring it according to the infrastructure.

    Well, I have to disagree a little here ;)

    It greatly depends on the organization and the setup.

    I think we can all agree that for a single developer trying out Java EE at home it's certainly the case that there are no different roles. If Java EE scares away those developers by requiring tedious and elaborate actions to be done that only make sense in a much larger setup, then I'm afraid we're not getting these developers on board.

    It's especially troublesome since this task is absolutely necessary to get an application to run correctly, but general Java EE text books can never really explain this well. They can mention that some container specific step is needed and that the student has to look this up in the manual of whatever container is being used, but in my experience it are at moments like these where a lot of people just loose interest in Java EE and switch over to a competing platform. The text book might explain how to do it for GlassFish (like the Oracle Java EE Tutorial does), but then people get massively confused if their company happens to be using e.g. JBoss.

    But also in somewhat larger setups it's not always the case that those developing aren't the ones who are also deploying and configuring.

    Especially in agile teams working according to devops principals there is typically no such distinction. There may be people in the team who are more specialized in the configuration aspects, and some who are more specialized in the coding aspects, but ultimately it's one team who all work on the same code base that's in the same source repository.

    If this team then works on an application that their company publicly runs on their own servers (e.g. like Facebook, Twitter, Google, etc develop and operate their own software, as opposed to selling software to clients), then there's as fas as I know almost never a need for role mapping.

    Of course, for an IT department that integrates externally obtained applications into an enterprise environment, the developer role is totally different from the deployer role. In such environments role mapping might make a lot of sense indeed.

    >I try to keep this in focus but I guess it will not be an issue for EE 7 anymore.

    Of course, for Java EE 7 the deadline has pretty much passed, so all efforts will have to concentrate on Java EE 8 at the earliest.

    >Happy to read a JIRA item written by you to be taken care of in EE 8!

    Thanks! :)

    Kind regards,
    Arjan

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete
  6. This comment has been removed by a blog administrator.

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  7. This comment has been removed by a blog administrator.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. This comment has been removed by a blog administrator.

      Delete
    3. This comment has been removed by the author.

      Delete
    4. This comment has been removed by a blog administrator.

      Delete
  8. This comment has been removed by a blog administrator.

    ReplyDelete