UPDATE: 28.01.2013
There is a new version of the tutorial out since a few days.
Preparation
I am assuming, that you have NetBeans (7 Beta2, Java), GlassFish (3.0.1 or 3.1 bundled with NetBeans) and MySQL (5.x) installed and you verified that everything single bit is working.
Basic Project Setup
Fire up NetBeans and start a new project. Choose Java Web > Web Application and hit next. Now enter a name (e.g. jdbcrealm) and hit next. Choose a Server or add a new one. Select Java EE 6 as your EE version and hit next. Check the box that states JavaServer Faces and switch to the tab components to select Primefaces as the component suite. Click finish. Now you are set. With your NetBeans project.
Database
Create a new database. There are a couple of ways to do this. I like the MySQL GUI Tooling. Get your copy from the mysql.com website. But you can also use the mysql cmd line. How ever. Execute the following SQL against your installation:
CREATE DATABASE jdbcrealmdb;
USE jdbcrealmdb;
CREATE TABLE `jdbcrealmdb`.`users` (
`username` varchar(255) NOT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `jdbcrealmdb`.`groups` (
`username` varchar(255) DEFAULT NULL,
`groupname` varchar(255) DEFAULT NULL)
ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE INDEX groups_users_FK1 ON groups(username ASC);
If we are going to secure our application we need some users with roles. Let's create them, if we are already here:
INSERT INTO users VALUES('admin','adminadmin');
INSERT INTO users VALUES('markus','blogeisele');
INSERT INTO groups VALUES('admin','admin');
INSERT INTO groups VALUES('markus','user');
Domain Configuration
Next is to copy the %NB_HOME%/ide/modules/ext/mysql-connector-java-5.1.13-bin.jar to your GlassFish domain (%GF_HOME%/glassfish/domains/domain1/lib). Fire up your domain by switch to the services tab in NetBeans and expand the servers node. Right click your GlassFish server node and select start. Open a browser and visit
http://localhost:4848/
. Select "Configuration > Security > Realms" and click new. 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 |
Assign Groups | default |
Digest Algorithm | none |
After you are finished, click save.
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. Let's start with the loginForm.xhtml. Create the file 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.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:p="http://primefaces.prime.com.tr/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 shown, 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}/faces/loginForm.xhtml" >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-constraint>
<display-name>Admin Pages</display-name>
<web-resource-collection>
<web-resource-name>Protected Admin Area</web-resource-name>
<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>
</security-constraint>
<security-constraint>
<display-name>User Pages</display-name>
<web-resource-collection>
<web-resource-name>Protected Users 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>
<auth-constraint>
<description/>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
If the constraints are in place you have to define, how the container should challenge the user.
<realm-name>JDBCRealm</realm-name>
<form-login-config>
<form-login-page>/faces/loginForm.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 sun-web.xml:
<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>
If you now point your browser to
localhost:8080/jdbcrealm/
you will see the login screen. Enter your credentials and see what happens.That's it. By far the longest tutorial I did so far. Hope, you like it. Let me know, if you need more details or have questions. Thanks for reading!
Very good article. Thanks for sharing.
ReplyDeleteThanks for taking your time in explaining. Really useful.
ReplyDeletegreat post, congratulations. Could you in response to this post show an example of how to set permission for links, menus and buttons using primefaces to. I don't find this in anywhere.
ReplyDeleteTanks for sharing.
thank you very much for this.
ReplyDeleteI'm having a problem with the authentication.
The column 'password' doesnt seem to be referenced, i mean, users with no password are authorized, but the 'imput text' for passwords is completly ignored.
I'd love any help.
thank you in advance.
Hi Carlos,
ReplyDeletewithout knowing anything more: Hard to tell. Have you inserted the pwd correctly into the db? Sound to me, like the pwd column could be empty ?
Any log output?
- M
Hi JWDN,
ReplyDeleteI put it on my todo-list. Need to do a separate post on this.
Thanks,
M
Very nice indeed, but please remember to change Digest Algorithm to default and do not use plain passwords in production environment.
ReplyDeleteI get com.sun.faces.context.FacesFileNotFoundException: /pages/loginForm.xhtml Not Found in ExternalContext as a Resource
ReplyDeleteHello,
ReplyDeletethis example seems to be not working on the new Glassfish 3.1.2 jdbcRealm.
The 3.1.2 versions introduces some "weird" new field (or maybe a very old one) which I can't understand they work. Documentation is not already up to date.
This sample is not working on Glassfish 3.1.1 and above and I desperately need help on this
ReplyDeleteLooking at it next week and posting an update.
Delete-m
Just a quick update notice:
ReplyDeleteA new version of the tutorial is available here:
http://blog.eisele.net/2013/01/jdbc-realm-glassfish312-primefaces342.html
- M
This comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by the author.
Delete