JDBC Realm and Form Based Authentication with GlassFish 3.1.2.2 and Primefaces 3.4
Join the DZone community and get the full member experience.
Join For FreeOne 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
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:
JAAS | jdbcRealm |
JNDI | jdbc/securityDatasource |
User Table | users |
User Name Column | username |
Password Column | password |
Group Table | groups |
Group Name Column | groupname |
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: <h:link outcome="admin/index" value="To the admin section" /> <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> <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" /> <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 snippet 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>hat 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
Published at DZone with permission of Markus Eisele, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments