Securing Java EE 6 Web Applications on Glassfish using JAAS
Usually when developing complex multi tier applications at some point architects and/or developers need to discuss about concepts for user authentication and authorization. Some resources of your application might be accessible for anyone, while other resources are only accessible for authenticated users or even only for authenticated users in a specific role (authorization). In such scenarios you have different options for implementing your requirements. You could implement everything from scratch on your own – with all the benefits and all the drawbacks. You could also use one of the many frameworks available instead of implementing everything on your own. In case you like Java, you ever heard about Java EE 6 and you are running your applications on Glassfish 3 you can make use of the built-in authentication and authorization features that Glassfish 3 offers. Using Glassfish and Java EE 6 allows you to make use of JAAS (Java Authentication and Authorization Service) for securing your Java EE 6 (Web) Applications. This tutorial goes even further and makes suggestions for designing your user management data model in combination with jdbcRealms. Basic user management services such as user login, user logout and user (self-) registration are also discussed. But even tha’s not all: you will also see how you could use Ajax in combination with jQuery to implement a simple Web Frontend for you application. Server and client will communicate via JSON data. For the conversion between Java object to JSON and the other way around we are not going to use JAXB (we will use Jackson instead). All over this tutorial you will find some further hints and Best Practices that you can leverage. Finally you will have a web application which you could use as a basic starting point for implementing your own requirements.
This tutorial has been tested with Glassfish 3.1.2. It will tell you how to integrate container managed authentication and authorization features offerd by Glasfish 3 into your Java EE application using different technologies. We will create a very simple application with the following features and requirements:
- any anonymous user can create/register a new account
- an already registered user can login with her/his credentials (username and password)
- some resources require a login, others are available to the public
- automatic redirection to a login page if user is not authenticated
- https is mandatory after login as well as for the login/register page
- users and roles are managed in a database (jdbcRealm)
To implement our “little” requirements we will use Glassfish 3, PostgreSQL, EJBs, JAAS, JPA, REST, JSON, AJAX, jQuery and even much more. So it would help if you are kind of familiar with all that stuff. If you are familiar with all that, you might need only a few hours to walk through this tutorial. Please consider that I have chosen not to use Maven. I believe that this allows a larger audience to follow this tutorial. This tutorial does not handle how to install Glassfish 3. I also expect you have already installed a PostgreSQL database. Other databases are also fine, but I will only handle PostgreSQL in detail when it comes to creating a JDBC resource on Glassfish and then using JPA to access it. Please refer to the security section of the official Oracle Java EE 6 Tutorial for an overview of how to secure Java EE 6 Web Applications. If you don’t know it already you might want to check The Open Web Application Security Project (OWASP). As a starting point you could begin with their page about Access Control In Your J2EE Application.
This tutorial requires some external libraries. Why and where they are needed is also discussed in this tutorial. Since I decided not to use maven (see above) please make sure to download the external dependecies (see below) manually and place them into the lib folder of your web application (you could still use maven if you want). All these dependencies are also packaged in the Downloads I offer below – so you might prefer that one instead of finding and downloading each jar one by one. To be honest, I used maven to get all these dependencies and then I just copied them one by one into the lib folder. Since Glassfish 3.1.2 ships with Jersey 1.11 you should make sure to get jersey-json version 1.11 and not 1.12.
- commons-codec-1.6.jar (Apache Commons Codec)
- jersey-json (Maven coordinates: groupId=com.sun.jersey, artifactId=jersey-json, version=1.11)
- activation-1.1.jar
- jackson-core-asl-1.9.2.jar
- jackson-jaxrs-1.9.2.jar
- jackson-mapper-asl-1.9.2.jar
- jackson-xc-1.9.2.jar
- jaxb-api-2.2.2.jar
- jaxb-impl-2.2.3-1.jar
- jersey-core-1.11.jar
- jersey-json-1.11.jar
- jettison-1.1.jar
- stax-api-1.0-2.jar
- stax-api-1.0.1.jar
Table of Contents:
- Defining a User Management Data Model using JPA
- Adding a JDBC Resource to Glassfish 3
- Generating JSON from POJOs without JAXB
- Defining a simple JSON Envelope for JSON Responses of our REST Services
- Implementing Login, Logout and Register services as RESTful Web Services (REST)
- Catching all REST Exceptions with an ExceptionMapper
- Implementing the view with Java Server Pages (JSP)
- Security Configuration in web.xml and glassfish-web.xml
- Creating a jdbcRealm
- Running the application
- Download Source Code
Creating this tutorial meant a lot of effort. I hope it will help others. If you have any questions do not hesitate to contact me. Any feedback is welcome! Also feel free to leave a comment (see below). For helping me to maintain my tutorials any donation is welcome. But now enough words – enjoy the tutorial.
I guess you will either use Eclipse or NetBeans IDE for following this tutorial. If you follow all the steps (see the Table of Contents above) you will have the following project structures (Netbeans and Eclipse):




1. Defining a User Management Data Model using JPA
As we have defined requirements for allowing new users to register new accounts we need to define where and how we want to store the account data for each user. An already registered user can login with her or his credentials. That means we need to be able to create new user accounts (user registration) as well as reading/finding existing user account data (login) for authentication and authorization. Authorization means we want to consider that authenticated users need to have a specific role assigned in order to be authorized to access restricted content. The relationship between users and roles needs also to be considered for our data storage.
Besides username and user password usually you also want to have some more information from your users, i.e. first name, last name, address etc. This kind of data depends on your own requirements – for illustration purposes I will only store the users first name, last name and registration date. When developing web applications at some point you have to ask yourself what data you want and how to store them. This can lead to a user management component which offers APIs for your requirements. Once you know what data you want to store you need to think about the storage itself. In this tutorial we will benefit from the Glassfish 3 and Java EE 6 features for storing all of our data in a database (i.e. credentials, last name, first name) and then using them for securing our web application (athentication and authorization). Later we will explain how to configure Glassfish for weaving everything together, but for now we will create our data model for our user management component.
When having a data model in your mind you could either write down the SQL statements to create the tables you want. Then you could write your own layer for accessing and manipulating the data. This is what we will not do here. Instead, we will implement JPA 2.0 Entity Classes and make our tables being generated automatically by JPA. This also allows us to easily access available user data stored in our database, manipulate them or to create new entities (i.e. for creating a new user account). The data model for our user management component looks as follows:

To make it easy I believe that it’s a good idea to model our groups as a simple enum. Let’s assume we have administrators and registered users that use our web application. We also could have other groups, i.e. I have just added a default group for demonstration reasons. The Group enum could look as follows:
1
2
3
4
5
6
7
|
package com.nabisoft.model.usermanagement; public enum Group { ADMINISTRATOR, USER, DEFAULT; } |
Now we can think about our User Entity class. Every user has an email address which serves as the unique identifier of the user. I have defined that the length of the field is 128 – actually this might be way more than you will ever need (you could also use 64 or what ever instead). The attributes firstName, lastName and registeredOn are also pretty much straight forward – I want them always to be filled and that’s why they cannot be null.
The password field is more interesting. As I mentioned above the user password should never be stored as clear text on the database. If you won’t follow that rule then people who might have gained access to our USERS table could see the original passwords and abuse them (there are different horrible scenarios for that but we will not discuss them here). So a good idea to solve this issue is to only store hashed values on the DB. Please consider that MD5 is not save anymore, so never use MD5 in a critical environment. I have chosen to use SHA-512 which is considered to be pretty safe. I have also chosen to use SHA-512 in combination with a hexadecimal encoding/representation. But what length do we now need for our password field (length of column in DB)? As its name indicates SHA-512 is 512 bits long. Since we are using an hexadecimal encoding, each digit codes for 4 bits. So you need 512 : 4 = 128 digits to represent 512 bits. That means on the DB you would need a varchar(128) to store SHA-512 HEX values. Actually, you could also use a char(128) because the length stays always the same – it is not varying at all, no matter how long the input String initially was before letting SHA-512 + HEX to run over it. Please consider that in real life projects you might even go one step further and hash the password with a “salt”. You might want to check this to see why you should add a salt. I will not use a salt in this tutorial because I want this tutorial to be kind of easy to follow. For creating a SHA-512 HEX value for a given String we can simply use the Apache Commons Codec library. So please make sure to add the Apache Commons Codec library to your application!
Attention: I store the password as a String. Everywhere I use a password (also in my REST APIs which will be discussed later) I use a String. From security point of view this is not a good idea! From security point of view it is much better to use a char array for such sensitive data! For more information about that please ask Google – this topic is out of scope for this tutorial. I have not used a char array in this tutorial only for pedagogical reasons (I don’t want to make it too complex…). As you will see later the Servlet 3.0 API offers HttpServletRequest.login(String username, String password) which is also using a String instead of a char array for passing the password. I am sure the Java EE Spec guys have a good reason reason for that.
The next interesting attribute is groups. It contains a list of all the groups which the user has been assigned to. One of our constructors offers to initialize some of the attributes by expecting an UserDTO. DTO stands for Data Transfer Object and is a simple pattern for passing data. As you will see later we will user the UserDTO for our registration REST Service (although some of you might say that DTOs are kind of old school…). Since we have this constructor we also have to offer a default constructor (parameterless). Usually you should validate your attributes at some point. For demonstration I only validate the passwords – and that only in the the constructor which expects a UserDTO (later you will understand what password1 and password2 is). Actually, you could also user Bean Validation (JSR 303) but in this tutorial we are not going to use Bean Validation at all. Please also consider that we do not use any JAXB annotations (i.e. @XmlRootElement). The JPA annotations you see avoid using a mapping table. And here is the source for both the User Entity and UserDTO:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
package com.nabisoft.model.usermanagement; import java.io.Serializable; import java.util.Date; import java.util.List; import javax.persistence.Cacheable; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.UniqueConstraint; import org.apache.commons.codec.digest.DigestUtils; import com.nabisoft.model.usermanagement.dto.UserDTO; @Entity @Table (name= "USERS" ) @Cacheable ( false ) public class User implements Serializable { @Id @Column (unique= true , nullable= false , length= 128 ) private String email; @Column (nullable= false , length= 128 ) private String firstName; @Column (nullable= false , length= 128 ) private String lastName; /** * A sha512 is 512 bits long -- as its name indicates. If you are using an hexadecimal representation, * each digit codes for 4 bits ; so you need 512 : 4 = 128 digits to represent 512 bits -- so, you need a varchar(128), * or a char(128), as the length is always the same, not varying at all. */ @Column (nullable= false , length= 128 ) //sha-512 + hex private String password; @Temporal (javax.persistence.TemporalType.TIMESTAMP) @Column (nullable= false ) private Date registeredOn; @ElementCollection (targetClass = Group. class ) @CollectionTable (name = "USERS_GROUPS" , joinColumns = @JoinColumn (name = "email" , nullable= false ), uniqueConstraints = { @UniqueConstraint (columnNames={ "email" , "groupname" }) } ) @Enumerated (EnumType.STRING) @Column (name= "groupname" , length= 64 , nullable= false ) private List<Group> groups; public User(){ } public User(UserDTO user){ if (user.getPassword1() == null || user.getPassword1().length() == 0 || !user.getPassword1().equals(user.getPassword2()) ) throw new RuntimeException( "Password 1 and Password 2 have to be equal (typo?)" ); this .email = user.getEmail(); this .firstName = user.getFName(); this .lastName = user.getLName(); this .password = DigestUtils.sha512Hex(user.getPassword1() ); this .registeredOn = new Date(); } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this .firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this .lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this .email = email; } /** * @return the password in SHA512 HEX representation */ public String getPassword() { return password; } public void setPassword(String password) { this .password = password; } public Date getRegisteredOn() { return registeredOn; } public void setRegisteredOn(Date registeredOn) { this .registeredOn = registeredOn; } public List<Group> getGroups() { return groups; } public void setGroups(List<Group> groups) { this .groups = groups; } @Override public String toString() { return "User [email=" + email + ", firstName=" + firstName + ", lastName=" + lastName + ", password=" + password + ", registeredOn=" + registeredOn + ", groups=" + groups + "]" ; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
package com.nabisoft.model.usermanagement.dto; public class UserDTO { private String email; private String fName; private String lName; private String password1; private String password2; public String getFName() { return fName; } public void setFName(String firstName) { this .fName = firstName; } public String getLName() { return lName; } public void setLName(String lastName) { this .lName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this .email = email; } public String getPassword1() { return password1; } public void setPassword1(String password) { this .password1 = password; } public String getPassword2() { return password2; } public void setPassword2(String password) { this .password2 = password; } @Override public String toString() { return "User [email=" + email + ", fName=" + fName + ", lName=" + lName + ", password1=" + password1 + ", password2=" + password2 + "]" ; } } |
So far we have defined a very simple model for our user management compoment. It’s time to implement an interface that uses our Entities, i.e. for saving a new user to the database. For this purpose we will use a simple Stateless Session Bean. Per default our UserBean EJB is only accessible locally (No-Interface View). We don’t want it to be accessible from any remote clients:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package com.nabisoft.model.usermanagement; import java.util.List; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; @Stateless public class UserBean { @PersistenceContext private EntityManager em; public List<User> findAll() { TypedQuery<User> query = em.createQuery( "SELECT usr FROM User ORDER BY usr.registeredOn ASC" , User. class ); return query.getResultList(); } public void save(User user) { em.persist(user); } public void update(User user) { em.merge(user); } public void remove(String email) { User user = find(email); if (user != null ) { em.remove(user); } } public void remove(User user) { if (user != null && user.getEmail()!= null && em.contains(user)) { em.remove(user); } } public User find(String email) { return em.find(User. class , email); } public void detach(User user) { em.detach(user); } } |
We are using JPA, therefore we also want to define a persistence.xml file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation = "http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" > < persistence-unit name = "authPU" transaction-type = "JTA" > < provider >org.eclipse.persistence.jpa.PersistenceProvider</ provider > <!-- see step 2 below --> < jta-data-source >jdbc/auth</ jta-data-source > < properties > < property name = "eclipselink.ddl-generation" value = "create-tables" /> <!-- logging --> <!-- log JPA Statements --> < property name = "eclipselink.logging.level" value = "FINE" /> <!-- also log of the values of the parameters used for the query --> < property name = "eclipselink.logging.parameters" value = "true" /> </ properties > </ persistence-unit > </ persistence > |
We have named our one and only Persistence Unit authPU. If we had multiple Persistence Units then we would have to specify which of our Persistence Units shall be used for our EntityManager in our UserBean, i.e. like this (see UserBean.java above):
1
2
3
4
5
|
//this is enough for our case because we have only one PU //@PersistenceContext //we could also use this @PersistenceContext (unitName= "authPU" ) private EntityManager em; |
2. Adding a JDBC Resource to Glassfish 3
Now we want to tell Glassfish where our DB is, which credentials to use when connecting to the DB and so on. As I have mentioned earlier we want to connect to a PorstgreSQL database. There are different options for adding new JDBC Resources to Glassfish:
- creating it via Admin Console (web frontend for Glassfish administration)
- using asadmin create-jdbc-connection-pool and create-jdbc-resource (see here and here)
- using glassfish-resources.xml file + asadmin add-resources (see http://docs.oracle.com/cd/E18930_01/html/821-2433/add-resources-1.html#scrolltoc)
I never use option 1 because it doesn’t support automated scripts. I prefer option 2 or 3, but I will only tell you how to configure all of your resources (in our case only a jdbc-connection-pool) into a glassfish-resources.xml file and afterwards how to add all of your resources defined in that file via asadmin add-resources to Glassfish (option 3). So first of all we will create the glassfish-resources.xml file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
<? xml version = "1.0" encoding = "UTF-8" ?> <!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd"> < resources > <!-- JDBC --> < jdbc-connection-pool name = "auth-Pool" allow-non-component-callers = "false" associate-with-thread = "false" connection-creation-retry-attempts = "0" connection-creation-retry-interval-in-seconds = "10" connection-leak-reclaim = "false" connection-leak-timeout-in-seconds = "0" connection-validation-method = "auto-commit" datasource-classname = "org.postgresql.ds.PGSimpleDataSource" fail-all-connections = "false" idle-timeout-in-seconds = "300" is-connection-validation-required = "false" is-isolation-level-guaranteed = "true" lazy-connection-association = "false" lazy-connection-enlistment = "false" match-connections = "false" max-connection-usage-count = "0" max-pool-size = "32" max-wait-time-in-millis = "60000" non-transactional-connections = "false" pool-resize-quantity = "2" res-type = "javax.sql.DataSource" statement-timeout-in-seconds = "-1" steady-pool-size = "8" validate-atmost-once-period-in-seconds = "0" wrap-jdbc-objects = "false" > < property name = "serverName" value = "localhost" /> < property name = "portNumber" value = "5432" /> < property name = "databaseName" value = "db_auth" /> < property name = "User" value = "myUser" /> < property name = "Password" value = "mySecretPassword" /> < property name = "driverClass" value = "org.postgresql.Driver" /> </ jdbc-connection-pool > < jdbc-resource enabled = "true" jndi-name = "jdbc/auth" object-type = "user" pool-name = "auth-Pool" /> <!-- other resources, i.e. Mail --> <!-- ... --> </ resources > |
As you can see our glassfish-resources.xml defines a JDBC resource. With that configuration Glassfish can manage DB connections. In our case we have configured a PorstgreSQL database. Please see the official documentation for more details regarding the parameters I have used. The next step is to tell Glassfish about our jdbc-resource defined in the glassfish-resources.xml file by executing a asadmin add-resources command:
1
2
3
4
5
|
#you might not need "--secure" asadmin --secure add-resources glassfish-resources.xml #you can also specify the complete path to your glassfish-resources.xml file asadmin --secure add-resources /home/myUser/glassfish-resources .xml |
3. Generating JSON from POJOs without JAXB
A common format for data exchange between web frontend and backend system is JSON. Most tutorials you find will tell you how to use JAXB (Java API for XML Binding) and its Java annotations for generating JSON. Once you have done what those tutorials told you suddenly you see some very strange JSON fromat which is not what you expected. At this point you might look for other tutorials or blogs that tell you what else you have to do in order to get a natural JSON representation of your POJOs by using JAXB. If you are lucky, then you will find the POJOMappingFeature that is shipped with Jersey. But then you find out that you still have null values in your generated JSON Strings. So you keep asking Google for aome annotations or some other Features like the POJOMappingFeature that allow you to get rid of the null values. Then you find some annotations and code snippets that seem not to work for you because you don’t have the correct libraries or library versions on your Glassfish classpath. With your last breath you have come to a point where you are getting ready to develop your own @Provider classes to generate JSON from POJOs and vice versa. Since you have found this tutorial you don’t have to implement your own @Provider classes and you don’t have to use JAXB.
JAXB is a great thing. But in case you only need JSON and no XML at all, like in our case, then JAXB might not be the best choice. JAXB allows to convert your POJOs to JSON and vice versa. But it always generates XML in an intermediate step and that’s what we actually don’t need. Furthermore you have to annotate your classes with @XmlRootElement, but what happens if you don’t have the sources of classes that you want to convert to JSON? So there can be good reasons for not using JAXB, it depends from case to case… As an alternative you could use Google’s gson (which is by the way really great). But since we are in a Glassfish world (at least here in this tutorial) we will use Jackson (Jersey is based on Jackson). Please keep in mind that I have tested this tutorial with a Glassfish 3.1.2 installation. The 3.1.2 version ships with Jersey 1.11 (you can easily check your Jersey version in the server output when you start your Glassfish, i.e. something like this: Initiating Jersey application, version ‘Jersey: 1.11 12/09/2011 10:27 AM’). By the end of 2011 Jersey was modularized. What we actually want is to have all the jersey-json maven dependencies in our project. But since I have promised not to use Maven you have to add them all one by one to your classpath. Please download all the jars listed on the Jersey Dependencies page under 11.4.1. JAXB (we only need the jersey-json dependencies). Hint: I have added all the relevant dependencies to the /WEB-INF/lib/ folder of the downloadable sources below. If you want some more information about the different Jackson packages please check here. By the way: we are using Jackson 1.9.2 and we want to configure Jackson globally in our own @Provider class. Instead we could also use @Json* annotations, i.e. @JsonSerialize(include = Inclusion.NON_EMPTY). Inclusion.NON_EMPTY tells Jackson not to include null values or empty collections. That’s it – the rest happens without any further configuration Please check here in case you have multiple Providers for the same type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package com.nabisoft.jaxrs.provider; import javax.inject.Singleton; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.annotate.JsonSerialize; @Provider @Produces (MediaType.APPLICATION_JSON) @Singleton public class MyJacksonJsonProvider implements ContextResolver<ObjectMapper> { private static final ObjectMapper MAPPER = new ObjectMapper(); static { // since Jackson 1.9 // this default configuration can be overwritten via annotations MAPPER.setSerializationInclusion(JsonSerialize.Inclusion.NON_EMPTY); } public MyJacksonJsonProvider() { System.out.println( "Constructor called: com.nabisoft.jaxrs.provider.MyJacksonProvider" ); } @Override public ObjectMapper getContext(Class<?> type) { System.out.println( "MyJacksonProvider.getContext(): " +type); return MAPPER; } } |
4. Defining a simple JSON Envelope for JSON Responses of our REST Services
The browser (or any client) sends HTTP requests to our Glassfish server in order to consume our REST Services. If the server is able to process the request it returns the response data itself and a HTTP Status code that tells the caller if everything was ok or not. There are a lot of HTTP Staus codes defined in the HTTP Spec, I am sure you know at least 404 (Not Found), 200 (OK), 500 (Internal Server Error). On our frontend we use jQuery (frontend is discussed later). When executing Ajax requests with jQuery the error callback function that you define for your Ajax call is called automatically by jQuery depending on the HTTP Status code of the response. Besides that you could also define a statusCode object for your Ajax call and define callback functions for each HTTP Status code you are interested in (later you will see some example code below). When implementing your backend services you always want to implement a safe error handling, you want to consider everything that could happen. Once you know what could happen you start thinking about what HTTP Status code should be returned for each specific case, i.e. you might want to return a 404 in case the ID for your getBookById Service doesn’t exist in the database, or you might want to return a 403 in case the user doesn’t have enough privileges to call your getBookById service. This little example should only tell you that you could and should make use of the HTTP Status codes. But then there are also cases where you cannot clearly identify which HTTP Status you should return. Furthermore using all of the possible HTTP Status codes could also require additional lines of code within your frontend in order to “react” correctly. You should decide whether you want to treat the HTTP Status code as part of your REST APIs or not, and that decision can be different from project to project, or even from service to service (latter might not be the best solution). I usually make use of only a few HTTP Status codes, i.e. 404, 403, 500, 200 etc. Besides that I use a JsonEnvelope which separates the response data itself and the “application” status of the request. In all my JSON responses I use this JsonEnvelope – a very simple POJO which is not even extending javax.ws.rs.core.Response (ok, maybe I was a little lazy to extend javax.ws.rs.core.Response…). The application status (you might want to use another term for that, maybe “app request status”) is something different than the HTTP Status. To demonstrate this all let’s talk about our login service which is discussed in detail later: For now we only want to consider two cases (although there can be more). The first case is “Login succeeded” – in that case the HTTP Status is 200 (OK) and the JsonResponse status (aka application status) could be “SUCCESS”. The second case is “Login failed” – in that case the HTTP Status still stays at 200, whereas the JsonResponse status is “FAILED”. You don’t have to do it the way I decided, there are other options as well. Feel free to choose your own “Best Practics”. Please also consider that I have added a version attribute to the JsonResponse class because we might have different client technologies (not only our web frontend) and we might want to change the attributes of our JsonResponse. Here is the source code for our JsonResponse class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
package com.nabisoft.json; import java.util.Map; //import javax.xml.bind.annotation.XmlElement; //import javax.xml.bind.annotation.XmlRootElement; //import org.codehaus.jackson.map.annotate.JsonSerialize; //@XmlRootElement //we don't need this thanks to Jackson //@JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY) //we already use global configuration, see MyJacksonJsonProvider.java public class JsonResponse{ private static final float version = 1 .0f; private String status; private String errorMsg; private Map<String, Object> fieldErrors; private Object data; public JsonResponse() { } public JsonResponse(String status) { this .status = status; } //@XmlElement //we don't need this thanks to Jackson public float getVersion() { return JsonResponse.version; } public String getStatus() { return status; } public void setStatus(String status) { this .status = status; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this .errorMsg = errorMsg; } public Map<String, Object> getFieldErrors() { return fieldErrors; } public void setFieldErrors(Map<String, Object> fieldErrors) { this .fieldErrors = fieldErrors; } public Object getData() { return data; } public void setData(Object data) { this .data = data; } } |
5. Implementing Login, Logout and Register services as RESTful Web Services (REST)
In the previous steps we have implemented our JPA 2.0 User Entity as well as a locally accessible (No-Interface View) Stateless Session Bean (UserBean). We also have added a JDBC Resource to our Glassfish installation. In this step we want to implement some RESTful Web Services that can be called from any remote client. Glassfish comes with Jersey – the JAX-RS (JSR 311) Reference Implementation for building RESTful Web Services (see http://jersey.java.net/). As you can guess we will use Jersey for implementing our REST Services. Our REST Services will allow to login and logout an existing user. We will also implement a service that allows to register a new user. The REST Services use the Stateless Session Bean we have created in step 1. The following class implements our REST Services:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
package com.nabisoft.service.usermanagement; import java.util.ArrayList; import java.util.List; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import com.nabisoft.json.JsonResponse; import com.nabisoft.model.usermanagement.Group; import com.nabisoft.model.usermanagement.User; import com.nabisoft.model.usermanagement.UserBean; import com.nabisoft.model.usermanagement.dto.UserDTO; @Path ( "/auth" ) @Produces (MediaType.TEXT_PLAIN) @Stateless public class UserManagementService { @EJB private UserBean userBean; @GET @Path ( "ping" ) public String ping() { return "alive" ; } @POST @Path ( "login" ) @Produces (MediaType.APPLICATION_JSON) public Response login( @FormParam ( "email" ) String email, @FormParam ( "password" ) String password, @Context HttpServletRequest req) { JsonResponse json = new JsonResponse(); //only login if not already logged in... if (req.getUserPrincipal() == null ){ try { req.login(email, password); req.getServletContext().log( "Authentication Demo: successfully logged in " + email); } catch (ServletException e) { e.printStackTrace(); json.setStatus( "FAILED" ); json.setErrorMsg( "Authentication failed" ); return Response.ok().entity(json).build(); } } else { req.getServletContext().log( "Skip logged because already logged in: " +email); } //read the user data from db and return to caller json.setStatus( "SUCCESS" ); User user = userBean.find(email); req.getServletContext().log( "Authentication Demo: successfully retrieved User Profile from DB for " + email); json.setData(user); //we don't want to send the hashed password out in the json response userBean.detach(user); user.setPassword( null ); user.setGroups( null ); return Response.ok().entity(json).build(); } @GET @Path ( "logout" ) @Produces (MediaType.APPLICATION_JSON) public Response logout( @Context HttpServletRequest req) { JsonResponse json = new JsonResponse(); try { req.logout(); json.setStatus( "SUCCESS" ); req.getSession().invalidate(); } catch (ServletException e) { e.printStackTrace(); json.setStatus( "FAILED" ); json.setErrorMsg( "Logout failed on backend" ); } return Response.ok().entity(json).build(); } @POST @Path ( "register" ) @Produces (MediaType.APPLICATION_JSON) @Consumes (MediaType.APPLICATION_JSON) @TransactionAttribute (TransactionAttributeType.NEVER) public Response register(UserDTO newUser, @Context HttpServletRequest req) { JsonResponse json = new JsonResponse(); json.setData(newUser); //just return the date we received //do some validation (in reality you would do some more validation...) //by the way: i did not choose to use bean validation (JSR 303) if (newUser.getPassword1().length() == 0 || !newUser.getPassword1().equals(newUser.getPassword2())) { json.setErrorMsg( "Both passwords have to be the same - typo?" ); json.setStatus( "FAILED" ); return Response.ok().entity(json).build(); } User user = new User(newUser); List<Group> groups = new ArrayList<Group>(); groups.add(Group.ADMINISTRATOR); groups.add(Group.USER); groups.add(Group.DEFAULT); user.setGroups(groups); //this could cause a runtime exception, i.e. in case the user already exists //such exceptions will be caught by our ExceptionMapper, i.e. javax.transaction.RollbackException userBean.save(user); // this would use the clients transaction which is committed after save() has finished req.getServletContext().log( "successfully registered new user: '" + newUser.getEmail() + "':'" + newUser.getPassword1() + "'" ); req.getServletContext().log( "execute login now: '" + newUser.getEmail() + "':'" + newUser.getPassword1() + "'" ); try { req.login(newUser.getEmail(), newUser.getPassword1()); json.setStatus( "SUCCESS" ); } catch (ServletException e) { e.printStackTrace(); json.setErrorMsg( "User Account created, but login failed. Please try again later." ); json.setStatus( "FAILED" ); //maybe some other status? you can choose... } return Response.ok().entity(json).build(); } } |
The UserManagementService class offers three services: login, logout and register. All of our services produce JSON (we will not produce XML or plain text). The register service even consumes JSON. We also want to offer a simple ping service that could tell whether our REST services are reachable or not. The ping service is a very “stupid” one without any special logic (feel free to extend or disable it).
The login service uses the Servlet 3.0 API for logging in a user. As you can see the API requires to pass both username and password as Strings. In our case the username is the email address. We only want to login a user if the user is not already logged in. If we would not consider that then the re-login would cause an exception: javax.servlet.ServletException: Attempt to re-login while the user identity already exists. In fact, if req.login(email, password) fails due to wrong credentials we also get a ServletException. So the first thing you should keep in mind is that we don’t have something like a LoginException here although you can see something like this in your stack trace: Web Login Failed: com.sun.enterprise.security.auth.login.common.LoginException: Login failed: Security Exception. But this exception is wrapped in a ServletException. So if you want to catch only the LoginExceptions in an ExceptionMapper (we will implement an ExceptionMapper later) then forget it. The only way to catch a LoginException is by putting req.login(email, password) into a try-catch block. If you would try to use a ExceptionMapper, then you would have to implement ExceptionMapper<ServletException> because req.login(email, password) only throws a ServletException. Unfortunately, the ServletException is also thrown at other places, so in the ExceptionMapper you cannot distinguish if the ServletException came from a failed login or not. So we are stuck to put req.login(email, password) into a try-catch block. Our login service returns a User entity in JSON format. Since we don’t want to send the SHA-512 HEX value of the user’s password to the frontend “clear” it, what means that will simply set the value to null. Before we can do that we need to detach the entity. The same we do for the assigned groups – we don’t want to tell the caller which groups the user is assigned to (this information is only relevant for the backend). Because of these two fields we could also have used a DTO or other ways to ignore these two fields. Besides the DTO an other way would be to use the @JsonIgnore annotation which is offered by Jackson.
The logout service uses the Servlet 3.0 API for logging out a user. I also make sure to clear the session as the API does not really tell me what happens with the session if logout() is called. I guess that the session is not invalidated, so I want to do it on my own. In fact, I believe that it is a good design approach to not automatically invalidate the user’s session when logout() is called. Depending on your own business logic you can best decide on your own if the session shall be invalidated or not. There are even good examples where you might not want to invalidate the users session, i.e. do you want your Amazon shopping cart to be cleared after you have logged out (assuming the shopping cart data is stored in the sesison)? After logout() is called then request.getUserPrincipal() will return null indicating the caller is not authenticated (not logged in). Please have a look at Oracle’s official Java EE 6 Tutorial for some example code telling you how you could use the HttpServletRequest interface to authenticate users for a web application programmatically by using the Servlet 3.0 methods login(userName,password) and logout(). The logout service returns a very simple JSON response.
The register service both produces and consumes JSON. When registering a new user we want to automatically login the user we have just created. In case the user creation (= registration) fails the user, of course, is not logged in. As you can see from the register API a UserDTO is expected. Our strategy is to simply return to the caller what ever we have received from the caller (both in JSON format) with one exception: in case our call to userBean.save(user) fails we will simply return what ever our ThrowableExceptionMapper returns (see ThrowableExceptionMapper.java below). The newUser parameter of type UserDTO will contain the values which have been passed from the client. Jersey and Jackson will manage to fill the corresponding fields of our UserDTO by parsing the incoming JSON String (remind that the service consumes JSON). We only have to make sure that the client really sends a JSON String instead of regular HTML form data via POST. Please consider that there are differences between the UserDTO and our User entity. The UserDTO has password1 and password2, while the User entity only has one password attribute. Furthermore the UserDTO does not contain any information ragarding the groups assigned to a user. While we usually don’t want our User entity data to be published to clients (think of our SHA-512 HEX passwords and Groups) the UserDTO is meant to be used for passing data from client to backend and (maybe) the other way around. As already mentioned above you could also have implemented all that without an additional UserDTO class. Basically, our UserDTO serves as a container for our HTML registration form data passed as JSON to our register service (the registration form will be discussed later). The only validation we want to implement is checking if password1 and password2 of our UserDTO are equal and not empty Strings. This allows to prevent typos, I am sure you know the idea behind that. As you can see we don’t have to check for null values what makes the code to look a little better. In a real world application you would have some more validations, i.e. you might want to check if the passed email address is a real email address. You might also want to benefit from Bean Validation (JSR 303), but this is out of scope for this tutorial. Please feel free to implement your own validations. After we have verified that our validations are fine we can continue with creating the user. First we create an instance of our User entity by passing the UserDTO to the constructor. We assign every new user to all three groups we have defined in step 1 (see Group.java). Please consider how the user’s password is set within the constructor of our User entity: we have to create a SHA512 + HEX representation and store exactly this value to the DB – not the clear text (String)! For that we simply use the Apache Commons Codec library found at http://commons.apache.org/codec/. It offers exactly what we want: DigestUtils.sha512Hex(password1). For saving the new user via JPA we simply use our UserBean EJB. We don’t have to create an instance, instead Glassfish injects a reference for us. If everything worked then we login the user we have just persisted by using the same Servlet 3.0 API we have used for our login REST Service (see above).
But how about transactions? As you might have noticed our UserManagementService is actually a Stateless Session Bean. Glassfish allows to inject resources into an EJB via Dependency Injection. In our case we inject another EJB (UserBean) into our UserManagementService EJB by using the @EJB annotation (keep in mind that our UserManagementService EJB is only used for making our REST Services available for the public). I have choosen to implement it this way to put your attention on a common pitfall when working with transactions and EJBs. As we know Statefull/Stateless EJBs are per default transactional and the default TransactionAttributeType is REQUIRED. That means that each business method of an EJB is executed within a transaction. The official JavaDoc says: “If a client invokes the enterprise bean’s method while the client is associated with a transaction context, the container invokes the enterprise bean’s method in the client’s transaction context.” In our case this means that when ever a REST Service is called Glassfish will create a new transaction context – especially for our register service this piece of information is very important. In the register service we use another EBJ (UserBean) and call its save() method. This save() method is not executed in a new transaction – instead it is executed within the same transaction that was created initially for our register service. The transaction is committed after the register service has finished. Our problem is now that within our register service method we create a new user and right after that we want to login that user. The login will access the DB and will only succeed if the user data can be found on the DB, but the user data is committed to the DB after we want to execute req.login(email, password1). So what we actually want is to make our UserBean commit right after userBean.save(user) is executed. There are different ways to implement this requirement, I have simply choosen to add a @TransactionAttribute(TransactionAttributeType.NEVER) annotation to our register service’s implementation method. This will tell Glassfish not to create a new transaction when the register service is called, which means that our UserBean will get its own transaction (created by Glassfish) and after userBean.save(user) has finished the transaction will be committed. That’s exactly what we want.
For more details about this check spring web hosting website.