Summary - Integration of OpenID based sign-in with GWT-RPC services hosted within a Spring IoC container.
- Design of complex multi-participant distributed system.
- Integration of existing third party APIs to accomplish a task in the most elegant way possible.
- Support for modern web application design patterns.
Web applications frequently require authentication, whether for security, personalization or other reasons. The standard solution has been to request that users of the site register, creating a user-name and often using an email address to convey a password or verify the user in some fashion. As web applications become more common this can lead to poor user experience, as every site insists on capturing the same information - this is also a problem for application creators having to deal with data protection issues. One solution to this problem is the OpenID standard, a mechanism by which a web application, or relaying party in OpenID parlance, can delegate to an authentication provider to perform authentication without ever receiving the user's credentials directly. This allows sign-in with, for example, a Google account ID, Yahoo! ID or any number of other OpenID enabled systems.
The Spring Inversion of Control (IoC) container provides an excellent basis for the server-side component of a modern web application (for reasons not relevant to this article). Services can be created within Spring as methods on regular Java beans. When these services are protected by some form of authentication it is likely that the code within the service will need to access properties of the current user to, for example, look up the appropriate record in a database.
There are excellent third party Java libraries for the handling of OpenID requests and responses, and for the use of Spring to host GWT-RPC service implementations. The task is therefore to create a way to 'OpenID enable' the Spring hosted GWT-RPC services, and to provide the corresponding client side functionality to perform the OpenID based sign-in.
There are two distinct aspects to this problem. The first is the acquisition and verification of the OpenID credential. This is a reasonably well understood problem, after some time searching for existing work I found a solution I was able to adapt involving the creation of a Java servlet to handle the OpenID verification process and the use of client-side HTTP redirects to divert to the appropriate OpenID provider URL. The servlet verifying the response accesses the Spring context to establish a link between the OpenID token and the user within the web application
The second aspect is ensuring that the authentication details are passed through to GWT-RPC services hosted within the Spring IoC container. Services are implemented as Java beans (POJOs, or Plain Old Java Objects). These services therefore have no awareness that they are within a web server container, or that they are services at all. This is the strength of the approach (build your business logic without any ties to the service exposure logic) but obviously poses a problem in this case.
The solution here was to subclass the servlet handling the GWT-RPC requests, and to insert a small piece of intercept logic into its HTTP request handlers. The logic in question accesses the HTTP request and looks for the OpenID token which has been sent as a HTTP cookie. If it finds this token it accesses the Spring context and retrieves the association between the token and the user object within the web application. This user object is then placed in a thread-local variable called CurrentUser. The service implementations may then retrieve that thread-local value any time they need to act based on the current user.
To extend this, and take advantage of the Spring container's Aspect Oriented Programming (AOP) support, I created a set of annotations used to decorate service methods. These annotations indicated that the current user must exist, or must exist and have a particular role in the system. This allowed a form of declarative security through an aspect which trapped any calls to methods with these annotations and performed the appropriate check, throwing an exception if the necessary role wasn't granted to the current user. The Spring container enforces this check with the creation of a run-time dynamic proxy, meaning that the actual service implementation can be completely unaware of the process - it is simply never called if the current user doesn't have the correct permissions.