RCP Security
Integrates Acegi Security System for Spring
into RCP.
Authors
Larry Streepy
Ben Alex (original security code author)
EARLY REVIEW ONLY
Please note that the code documented here is not in the tree yet! It is here for review and comment by the developers.
Overview
Acegi Security is a comprehensive open-source security system that delivers fully-featured security to Spring applications. To learn more about Acegi Security or access detailed documentation, please visit the project home page at http://www.acegisecurity.org
It is envisaged that many RCP clients will be connecting with a remote Spring-powered server. In such deployments, security becomes of paramount importance. Whilst transport-layer security (such as HTTPS, port filtering and firewalls) are essential to almost all production applications, this package delivers comprehensive application-layer security to RCP clients by hooking into the Acegi Security project.
Background Knowledge
Whilst you should really read the Acegi Security System for Spring reference documentation to fully understand the architecture, the most important details you need to understand in order to utilize this RCP package is the summarized below.
RCP uses the following key Acegi Security classes and interfaces:
ContextHolder, which simply uses a ThreadLocal to store a SecureContext implementation.
Authentication, which stores the details of a principal, credentials and its granted authorities. RCP uses Acegi Security's UsernamePasswordAuthenticationToken, which simply represents a username and password for the principal and credentials respectively.
AuthenticationManager, which is able to accept a request Authentication object (containing only the principal and credentials details), process its validity, and return a populated Authentication object (also containing the GrantedAuthorty[]s).
Per-thread vs. per-Application authentication
One thing to keep in mind is that Acegi maintains authentication credentials on a per-thread basis (ContextHolder described above). This makes sense when considering server side implementations where different threads are working on behalf of potentially different principals. In a rich application, however, it is rare to have anything but a "global" notion of the logged in user (and associated credentials). In fact, having a per-thread credential store is problematic for a rich application where operations may take place on different threads (a main thread, UI event dispatch thread, worker threads, etc.). Further, it would be difficult to propogate credentials to all new threads created, or to update existing threads when a user changes credentials (such as loggin out or logging in as a different user).
For these reasons, the application security model provided within RCP uses a global store for credentials. See the ApplicationSecurityManager below.
Major Players - Objects, Interfaces, and Events
ApplicationSecurityManager and DefaultApplicationSecurityManager
Instances of ApplicationSecurityManager are responsible for performing the security operations of user login and logout and maintaining the global authentication token for the user. A default implementation is provided in DefaultApplicationSecurityManager.
An instance of the ApplicationSecurityManager is available from ApplicationServices like this: Application.services().getApplicationSecurityManager(). Application code can access the current authentication token by calling the getAuthentication() method (or it can implement the notification interfaces defined below).
As the ApplicationSecurityManager handles login/logout requests, it fires a set of events to inform the application of the security lifecycle. The table below shows the events that are fired in response to various login and logout processing.
| Action |
Events Fired |
| Successful login |
AuthenticationEvent, LoginEvent |
| Failed login |
AuthenticationFailedEvent |
| Logout |
AuthenticationEvent, LogoutEvent |
In order to perform authentication operations, the ApplicationSecurityManager must have an AuthenticationManager configured. This can be done in the application context, like this:
<bean id="applicationSecurityManager"
class="org.springframework.richclient.security.support.DefaultApplicationSecurityManager">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
LoginCommand and LogoutCommand
LoginCommand is a simple implementation of an ActionCommand that shows a dialog for collecting a user name and password and then handing them off to the ApplicationSecurityManager to perform the actual login processing (see below for more details). LoginCommand makes some very simple assumptions on the vaildation constraints for the username and password fields, so you might need to subclass it and provide your own implementation of the login form.
LoginCommand implements a simplistic login failure handling scheme - it lets the user keep trying as long as they want. Again, you'll probably want to subclass to provide something more clever. Future work will hopefully include a pluggable login failure handling strategy. One final configurable element on the LoginCommand is how the login dialog should respond to the user pressing the Cancel button. This handling is controlled by the closeOnCancel property on LoginCommand, which defaults to true. If closeOnCancel is true and the user cancels the dialog, then the applicaiton will be closed, by calling getApplication().close()
LogoutCommand is an implementation of ActionCommand that simply invokes the logout processing in the ApplicationSecurityManager.
AuthenticationAware
AuthenticationAware is a tag interface that marks beans in the application context. Any bean that implements this interface will be initially notified of the current authentication token (during bean post-processing) and subsequently notified whenever the authentication token changes. See SecurityAwareConfigurer for more details.
LoginAware
LoginAware is a tag interface that marks beans in the application context. Any bean that implements this interface will be notified of two major security events: login and logout. See SecurityAwareConfigurer for more details.
SecurityAwareConfigurer
SecurityAwareConfigurer is a key player in the security architecture. It is both a BeanPostProcessor and an ApplicationListener. Its job is to handle beans that implement AuthenticationAware and LoginAware and configure them with authenticaiton information and notify them of key security events.
As a bean post-processor, SecurityAwareConfigurer handles any bean that implements AuthenticationAware and configures them with the current authentication token.
As an ApplicationListener, SecurityAwareConfigurer watches for ClientSecurityEvents and turns them into method notifications on the AuthenticationAware and LoginAware interfaces.
AuthenticationAware is handled in a "stateful" manner - meaning that the current authentication token is handed to every new bean that is created. Whereas the LoginAware interface is handled in an "event" manner - meaning that beans that implement the interface are only updated when an event of the proper type occurs.
AuthenticationAware notification always takes place prior to LoginAware notifications. So, if you need to perform some operation that requires another bean to have its authentiation state updated, then you should do it in LoginAware (or watch for LoginEvent and LogoutEvent instances directly) as these are always delivered after the AuthenticationAware notifications. See below in the remoting section for a real example of why this matters.
Each security event is translated to a notification, as shown below.
| Event |
Notification Made |
| AuthenticationEvent |
AuthenticationAware.setAuthenticationToken |
| AuthenticationFailedEvent |
no notifications made |
| LoginEvent |
LoginAware.userLogin |
| LogoutEvent |
LoginAware.userLogout |
Note that for any of this to happen, the SecurityAwareConfigurer must be properly configured in the application context. Here is an example of that configuration:
<bean id="securityAwareConfigurer"
class="org.springframework.richclient.security.SecurityAwareConfigurer"
lazy-init="false"/>
ClientSecurityEvent
The ApplicationSecurityManager is responsible for firing events that correspond to important security lifecycle events (authentication, login, logout, etc.). Specific subtypes represent each important event:
| Event |
Description |
| AuthenticationEvent |
Event fired when the user's authentication changes. This happens on both a
successful login and a logout. |
| AuthenticationFailedEvent |
Event fired when an authentication attempt fails. This happens when a login is
attempted and the authentication manager denies the authentication attempt. |
| LoginEvent |
Event fired when a new user logs in. This happens when a user successfully
logs in, and after the AuthenticationEvent. |
| LogoutEvent |
Event fired when a user logs out. This happends when a user logs out, and
after the AuthenticationEvent. |
Any bean interested in these events should implement ApplicationListener and then watch for events that extend ClientSecurityEvent. If you want a "callback" mechanism instead of watching events, then a bean can implement AuthenticationAware and/or LoginAware.
How Login and Logout Works
This package provides two key RCP commands: LoginCommand and LogoutCommand. To use these commands, simply add them to your commands-context.xml, like this:
<bean id="loginCommand"
class="org.springframework.richclient.security.LoginCommand"/>
<bean id="logoutCommand"
class="org.springframework.richclient.security.LogoutCommand"/>
Both commands accept an optional property, displaySuccess, which defaults to true. This simply results in an information dialog being displayed after login or logout. You can switch this off by setting the property to false in the application context.
LoginCommand basically displays a dialog requesting the username and password. Upon these being entered, a request Authentication object is created (as mentioned in the Background Knowledge section above) and presented for authentication by calling ApplicationSecurityManager.doLogin. If the authentication succeeds, a populated Authentication object is stored as the "global" authentication toke (see above) and is returned to the caller (the LoginCommand). The returned token is also placed into the thread-specific ContextHolder (for a little bit of backward compatibility). As described above, the ApplicationSecurityManager publishes events so other interested classes know a login has taken place.
LogoutCommand is far simpler. It calls ApplicationSecurityManager.doLogout, which fires appropriate events, and then it updates the thread-specific ContextHolder so its Authentication object is null.
Which AuthenticationManager?
As mentioned above, an AuthenticationManager is configured against the LoginCommand. This is just like any other Acegi Security use of AuthenticationManager, so you can use any of the standard Acegi Security authentication providers with the RCP package (such as DaoAuthenticationProvider).
More typically, a rich client will need to use a remote server for authentication. In this case you need the client to ensure a username and password is valid against the remote server, and also obtain the list of GrantedAuthority[]s (so the populated Authentication object can be constructed). To achieve this, on the client you'll need to use the RemoteAuthenticationProvider. On the server you'll need to use the RemoteAuthenticationManagerImpl. On the client you'll use your preferred remoting proxy factory to access the server-side RemoteAuthenticationManagerImpl. You can find these classes in Acegi Security's net.sf.acegisecurity.providers.rcp package.
An example using the HTTP proxy would be configured like this:
<bean id="applicationSecurityManager"
class="org.springframework.richclient.security.support.DefaultApplicationSecurityManager">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
<bean id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref bean="remoteAuthenticationProvider" />
</list>
</property>
</bean>
<bean id="remoteAuthenticationProvider"
class="org.acegisecurity.providers.rcp.RemoteAuthenticationProvider">
<property name="remoteAuthenticationManager" ref="remoteAuthenticationManager" />
</bean>
<bean id="remoteAuthenticationManager"
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl">
<value>http://localhost:8080/myserver/context/RemoteAuthenticationManager</value>
</property>
<property name="serviceInterface">
<value>org.acegisecurity.providers.rcp.RemoteAuthenticationManager</value>
</property>
</bean>
Remoting Integration
HTTP proxies and Basic Authentication
Using HTTP invocation for remoting is one of the simplest mechanisms to configure. Two classes are provided to make using HTTP BASIC authentication on top of the simple HTTP remoting protocol. See the code sample above on how one might configure the use of the HTTP proxy factory.
org.springframework.richclient.security.remoting.BasicAuthHttpInvokerProxyFactoryBean and org.springframework.richclient.security.remoting.BasicAuthHttpInvokerRequestExecutor.
BasicAuthHttpInvokerProxyFactoryBean is an extension of HttpInvokerProxyFactoryBean that supports the use of BASIC authentication on each HTTP request. This factory takes care of instantiating the proper invocation executor, an BasicAuthHttpInvokerRequestExecutor, and keeping it up to date with the latest user credentials.
BasicAuthHttpInvokerProxyFactoryBean implements AuthenticationAware in order to get notifications of changes in the user's credentials. Please see the class documentation for AuthenticationAware above to see how to properly configure the application context so that authentication changes are broadcast properly.
Hession and Burlap proxies
If your application uses either the Hessian or Burlap remoting classes to access your business objects on the server, you will want to register RemotingSecurityConfigurer in your application context.
RemotingSecurityConfigurer listens for login and logout events and updates the usernames and passwords associated with any of your registered remoting proxy factories. This causes BASIC authentication to be used in the header of the remoting requests.
Server Side Configuration
On the server side you will need to register Acegi Security's BasicProcessingFilter so BASIC authentication headers can be processed. You'd need to do this if you're using Acegi Security with any form of BASIC authentication (it is not an RCP-specific requirement). Here is an example of how you might configure this in the application context of your server:
<bean id="basicProcessingFilter"
class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager">
<ref bean="authenticationManager" />
</property>
<property name="authenticationEntryPoint">
<ref bean="basicProcessingFilterEntryPoint" />
</property>
</bean>
<bean id="basicProcessingFilterEntryPoint"
class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
<property name="realmName">
<value>My Realm</value>
</property>
</bean>
<bean id="httpSessionContextIntegrationFilter"
class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
<property name="allowSessionCreation">
<value>false</value>
</property>
</bean>
<bean id="remoteAuthenticationManager"
class="org.acegisecurity.providers.rcp.RemoteAuthenticationManagerImpl">
<property name="authenticationManager">
<ref bean="authenticationManager" />
</property>
</bean>
<bean id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref bean="daoAuthenticationProvider" />
</list>
</property>
</bean>
<bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService">
<ref bean="userDetailsService" />
</property>
</bean>
<bean id="userDetailsService"
class="com.myco..security.MyAuthenticationDao">
</bean>
And in the web.xml you might install it like this:
<filter>
<filter-name>Acegi HTTP Session Integration</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.context.HttpSessionContextIntegrationFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Acegi HTTP Session Integration</filter-name>
<url-pattern>/context/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>Acegi HTTP BASIC Authorization Filter</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.ui.basicauth.BasicProcessingFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Acegi HTTP BASIC Authorization Filter</filter-name>
<url-pattern>/context/*</url-pattern>
</filter-mapping>