Secure Your JSF Application with JAAS
In my experience as a Java developer, I noticed that a large number of companies prefer building their custom security model to using an already existent one. I think an explanation for this would be that many developers or architects aren’t well up on Java technologies and their strengths. So, instead of studying these technologies, they prefer building security models from scratch.
An advantage of using frameworks which already exist is that these are already tested and adopted by some companies.
What Is JAAS?
JAAS (Java Authentication and Authorization Service) is the security model proposed by Sun Microsystems to secure Java applications. The entire Java API is built on the JAAS security model.
By examining the canRead() method in the java.lang.File class, we find the following lines of code:
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
Thus, when a new File object is created, if we call the canRead() method, this will return true or false depending on whether we can read the specified file or not. This operation is known as authorization operation.
Authentication is the operation which verifies if the credentials (in most cases, the username and password) provided by a user or a system are valid.
To enable security in Java, you have to specify the -Djava.security.manager parameter for the virtual machine. The basic element of JAAS is the Subject object, which can be a person or a service. This subject contains a list of principals, public credentials and private credentials.
A Principal is a piece of information that belongs to a user who is logged in to the system. For example, a user has a name Principal (e.g., John Doe) and a SSN Principal (e.g., 1234). The principals are associated with the subject only if the authentication succeeds. A principal must implement the java.security.Principal and java.io.Serializable interfaces. Public and private credentials are represented by any class because they are not part of JAAS.
For more information about JAAS, you can read the JAAS Reference Guide.
Implementing The RBAC Security Model Using JAAS
RBAC is a role-based security model. When defining an RBAC model, the following conventions are useful:
- S = Subject – A person or an automated agent
- R = Role – A job function or title which defines a level of authority
- P = Permissions – An approval of a mode of access to a resource
- SE = Session – A mapping involving S, R and/or P
- SA = Subject Assignment
- PA = Permission Assignment
- RH = Partially ordered role hierarchy. RH can also be written as ≥ (The notation x≥y means that x inherits the permissions of y.)
- A subject can have multiple roles.
- A role can have multiple subjects.
- A role can have multiple permissions.
- A permission can be assigned to multiple roles.
A simple RBAC model definition
Suppose we have a medical application containing the following elements:
- Users: admin, John, Marry, Bill
- Roles: admin, doctor, secretary, patient
- Permissions: all permissions, create medical records, view medical records, update medical records, delete medical records, create appointment, update appointment, delete appointment
Definition of roles
- admin role has the following permissions: all permissions
- doctor role has the following permissions: create medical records, view medical records, update medical records
- secretary role has the following permissions: create appointment, update appointment
- patient has the following permissions: view medical records
Definition of users
- admin has admin role
- John has doctor role
- Marry has secretary role
- Bill has patient role
The RBAC model is a good security model, but it might prove problematic when dealing with a lot of users because it is then possible to have an explosion of roles. This means that, for each user in the system, we must create a separate role and all those roles can be very hard to maintain.
When using the RBAC model with JAAS, we have the following associations:
- Subject – the user
- Principal – the user role
- Permission – the role permission
- Credentials – the information about the user (the id from the database, the user name, etc.)
Preparing the Test Environment
For my demo application, I used Tomcat 6.0.x as a web container, MySQL 5.0 as a database, Java 6 and the following frameworks:
- Hibernate 3.3
- Spring framework 2.5.6
- JSF 2.0 – Oracle implementation
- Facelets 1.1.9
- Maven – for the build
Tomcat Configuration
Add the lines below to the end of the catalina.policy file. (You can find this file in the conf directory of your Tomcat installation.)
grant codeBase "file:${catalina.home}/webapps/jjwa/-" {
permission java.util.PropertyPermission "*", "read,write";
permission javax.security.auth.AuthPermission "modifyPrincipals";
permission javax.security.auth.AuthPermission "modifyPublicCredentials";
permission javax.security.auth.AuthPermission "modifyPrivateCredentials";
permission javax.security.auth.AuthPermission "createLoginContext.*";
permission javax.security.auth.AuthPermission "doAs";
permission javax.security.auth.AuthPermission "doAsPrivileged";
permission javax.security.auth.AuthPermission "getSubject";
permission java.security.SecurityPermission "setPolicy";
permission java.security.SecurityPermission "getPolicy";
permission java.lang.RuntimePermission "accessClassInPackage.*";
permission java.lang.RuntimePermission "getProtectionDomain";
permission java.lang.RuntimePermission "loadLibrary.*";
permission java.lang.RuntimePermission "modifyThread";
permission java.lang.RuntimePermission "createClassLoader";
permission java.lang.RuntimePermission "accessDeclaredMembers";
permission java.net.SocketPermission "*:*", "accept,connect,resolve";
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
permission java.lang.RuntimePermission "setContextClassLoader";
permission java.lang.RuntimePermission "getClassLoader";
permission java.io.FilePermission "<>", "read";
};
When you start the Tomcat container, use the following command: catalina.bat run -security (for Windows) or sh catalina.sh run -security (for Linux).
Don’t forget to define CATALINA_HOME as a system variable.
Database Configuration
In order to create the database, execute the following scripts:
ddl.sql
DROP DATABASE IF EXISTS jjwa; CREATE DATABASE jjwa; USE jjwa; CREATE TABLE USERS ( ID bigint(6) unsigned NOT NULL auto_increment, USERNAME varchar(256) NOT NULL, PASSWORD varchar(256) NOT NULL, IS_ONLINE varchar(1) NOT NULL, FIRST_NAME varchar(256) NOT NULL, LAST_NAME varchar(256) NOT NULL, GENDER varchar(1) NOT NULL, BIRTH_DATE date not null, COUNTRY varchar(256) NOT NULL, PROVINCE varchar(256) NOT NULL, CITY varchar(256) NOT NULL, CREATION_DATE timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, PRIMARY KEY (ID) ) ENGINE=InnoDB; CREATE TABLE ROLES ( ID bigint(6) unsigned NOT NULL auto_increment, NAME varchar(128) NOT NULL, CREATION_DATE timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, PRIMARY KEY (ID) ) ENGINE=InnoDB; CREATE TABLE PERMISSIONS ( ID bigint(6) unsigned NOT NULL auto_increment, NAME varchar(512) NOT NULL, DESCRIPTION varchar(512) NOT NULL, CREATION_DATE timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, PRIMARY KEY (ID) ) ENGINE=InnoDB; CREATE TABLE PERM_ACTIONS ( ID bigint(6) unsigned NOT NULL auto_increment, NAME varchar(512) NOT NULL, MASK int(6) NOT NULL, CREATION_DATE timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, PRIMARY KEY (ID) ) ENGINE=InnoDB; CREATE TABLE USERS_ROLES ( USER_ID bigint(6) unsigned NOT NULL, ROLE_ID bigint(6) unsigned NOT NULL, PRIMARY KEY (USER_ID, ROLE_ID), constraint FK_USERS_ROLES_U foreign key (USER_ID) references USERS (ID), constraint FK_USERS_ROLES_R foreign key (ROLE_ID) references ROLES (ID) ) ENGINE=InnoDB; CREATE TABLE ROLES_PERMISSIONS ( ROLE_ID bigint(6) unsigned NOT NULL, PERMISSION_ID bigint(6) unsigned NOT NULL, PRIMARY KEY (ROLE_ID, PERMISSION_ID), constraint FK_ROLES_PERMISSIONS_P foreign key (PERMISSION_ID) references PERMISSIONS (ID), constraint FK_ROLES_PERMISSIONS_R foreign key (ROLE_ID) references ROLES (ID) ) ENGINE=InnoDB; CREATE TABLE PERMS_ACTIONS ( PERMISSION_ID bigint(6) unsigned NOT NULL, ACTION_ID bigint(6) unsigned NOT NULL, PRIMARY KEY (PERMISSION_ID, ACTION_ID), constraint FK_PERMS_ACTIONS_P foreign key (PERMISSION_ID) references PERMISSIONS (ID), constraint FK_PERMS_ACTIONS_A foreign key (ACTION_ID) references PERM_ACTIONS (ID) ) ENGINE=InnoDB;
dml.sql
insert into PERM_ACTIONS (ID, NAME, MASK) values (1, 'read', 1); insert into PERM_ACTIONS (ID, NAME, MASK) values (2, 'create', 2); insert into PERM_ACTIONS (ID, NAME, MASK) values (3, 'update', 4); insert into PERM_ACTIONS (ID, NAME, MASK) values (4, 'delete', 8); insert into PERMISSIONS (ID, NAME, DESCRIPTION) values (1, 'USER_LIST', 'View users list'); insert into PERMISSIONS (ID, NAME, DESCRIPTION) values (2, 'USER', 'View user profile'); insert into PERMISSIONS (ID, NAME, DESCRIPTION) values (3, 'USER', 'Create a new user'); insert into PERMISSIONS (ID, NAME, DESCRIPTION) values (4, 'USER', 'Update a user'); insert into PERMISSIONS (ID, NAME, DESCRIPTION) values (5, 'USER', 'Delete a user'); insert into PERMISSIONS (ID, NAME, DESCRIPTION) values (6, 'MY_ACCOUNT', 'Access my account'); insert into PERMISSIONS (ID, NAME, DESCRIPTION) values (7, 'MY_ACCOUNT', 'Update my account'); insert into PERMS_ACTIONS(PERMISSION_ID, ACTION_ID) values(1, 1); insert into PERMS_ACTIONS(PERMISSION_ID, ACTION_ID) values(2, 1); insert into PERMS_ACTIONS(PERMISSION_ID, ACTION_ID) values(3, 2); insert into PERMS_ACTIONS(PERMISSION_ID, ACTION_ID) values(4, 3); insert into PERMS_ACTIONS(PERMISSION_ID, ACTION_ID) values(5, 4); insert into PERMS_ACTIONS(PERMISSION_ID, ACTION_ID) values(6, 1); insert into PERMS_ACTIONS(PERMISSION_ID, ACTION_ID) values(7, 3); insert into ROLES(ID, NAME) values(1, 'admin'); insert into ROLES(ID, NAME) values(2, 'user'); insert into ROLES_PERMISSIONS(ROLE_ID, PERMISSION_ID) values(1, 1); insert into ROLES_PERMISSIONS(ROLE_ID, PERMISSION_ID) values(1, 2); insert into ROLES_PERMISSIONS(ROLE_ID, PERMISSION_ID) values(1, 3); insert into ROLES_PERMISSIONS(ROLE_ID, PERMISSION_ID) values(1, 4); insert into ROLES_PERMISSIONS(ROLE_ID, PERMISSION_ID) values(1, 5); insert into ROLES_PERMISSIONS(ROLE_ID, PERMISSION_ID) values(1, 6); insert into ROLES_PERMISSIONS(ROLE_ID, PERMISSION_ID) values(1, 7); insert into ROLES_PERMISSIONS(ROLE_ID, PERMISSION_ID) values(2, 6); insert into ROLES_PERMISSIONS(ROLE_ID, PERMISSION_ID) values(2, 7); insert into USERS(ID, USERNAME, PASSWORD, IS_ONLINE, FIRST_NAME, LAST_NAME, GENDER, BIRTH_DATE, COUNTRY, PROVINCE, CITY) values(1, 'admin', 'admin', 'N', 'John', 'Doe-Admin', 'M', '1982-07-31', 'Country1', 'Province1', 'City1'); insert into USERS(ID, USERNAME, PASSWORD, IS_ONLINE, FIRST_NAME, LAST_NAME, GENDER, BIRTH_DATE, COUNTRY, PROVINCE, CITY) values(2, 'user', 'user', 'N', 'John', 'Doe', 'M', '1980-06-21', 'Country2', 'Province2', 'City2'); insert into USERS_ROLES(USER_ID, ROLE_ID) values (1, 1); insert into USERS_ROLES(USER_ID, ROLE_ID) values (2, 2);
After that, update the following lines in the dao-context.xml file with your data (your database username and password). By default, the database username is root and the password is admin.:
<property name="url" value="jdbc:mysql://localhost/jjwa" /> <property name="username" value="root" /> <property name="password" value="admin" />
The diagram below provides a visual overview of the database structure.
Building the Project
To build the project, you have to install Maven and call mvn clean package.
Running the Application
Start the Tomcat container using the following command: catalina.bat run -security (for Windows) or catalina.sh run -security (for Linux). The application can be accessed using the following address: http://localhost:8080/jjwa/.
The accounts which can be used for testing are:
- admin (username = admin, password = admin) – can access all pages
- user (username = user, password = user) – can view all pages except the users list page
How it Works
To show you how this application works, I created some sequence diagrams which illustrate how an http request is processed and how security is applied.
Suppose we call the following link in the browser: http://localhost:8080/jjwa/admin_page.html/.
When this request is received by the web container, the doFilter() method in SecurityFilterListener is called. This method ensures that the Subject is set on the user session context. It also checks if the Subject principals are empty. If so, then an anonymous role (which is the default role) is added to the user principals list.
After that, the request is sent to the FacesServlet service() method for processing. In the JSF render response phase, the getUsers() method in AccountController is called to render admin_page.xhtml.
Below is shown the body of this method.
public ListgetUsers() { try { return execute(new Callable >() { public List
call() throws Exception { return userService.getAll(); } }); } catch (Exception e) { e.printStackTrace(); return null; } }
The execute(callable) method tries to execute the getAll() method in the UserServiceImpl class. To verify if the user has the necessary rights to execute this method, I created an aspect using Spring AOP (see the service-context.xml file).
Thus, when a method of any class in the com.jsource.jjwa.service package is executed, the Object checkAuthorization(ProceedingJoinPoint pjp) method in the AuthorizationCheckerExecutor class is invoked. The implementation of this method is shown below.
public Object checkAuthorization(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature o = (MethodSignature) pjp.getSignature();
SecurityConstraint sc = o.getMethod().getAnnotation(SecurityConstraint.class);
if (sc != null) {
Permission[] permissions = Permissions.getPermissionsByTypes(sc.permissions());
if (permissions != null) {
if (SecurityUtil.hasPermissions(permissions)) {
return pjp.proceed();
} else {
throw new SecurityViolationException("You have no rights to do this operation.");
}
}
}
return pjp.proceed();
}
As you can see in the example above, first I checked to see if the user had the required rights to execute the target method. If he hasn’t the necessary roles, then a SecurityViolationException is thrown.
I chose to put the security verification in the service tier because, when you change the client tier (for example, when you want to implement a Java Swing client in this application), security is maintained and all you have to do is manage the security exceptions properly.
Observation
Since the project was made a long time ago, it is possible to encounter problems during the compilation because the JBoss mvn repository address was changed in the meantime. To fix this problem, please do the following modifications in the pom.xml file:
- (Mandatory) Delete all the repositories which contain jboss.org in their URLs
- (Mandatory) Add this repository under the <repositories> tag:
<repository> <id>repository.jboss.org</id> <name>jboss.org Repository for Maven</name> <url>https://repository.jboss.org/nexus/content/repositories/thirdparty-releases</url> <layout>default</layout> </repository>
- (Optional, but recommended) Comment the tags <sourceDirectory> and <directory> which are located between the <build> tags. The final build will be located under the target folder in the project.
Enjoy!








11 Responses to "Secure Your JSF Application with JAAS"
Add Comment