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.
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:
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:
And again we start by right clicking on the jdbcrealm database on the "Services" tab in NetBeans. Select "Execute Command" and insert the following:
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`) );
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">
<
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 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
>
Adding a Test-User to the DatabaseAnd 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