flexlogo.png

In my previous blog post : integrating flex 3 with spring security I made a good effort to create a nice flex 3 application and integrate authentication and authorization with spring security. A few days a go I received a trackback from sven. Curious as I am I started reading the material he provided and especially the other links he mentioned. That made me think about my own solution. To be honest I think I did not really do a good job. It works, but still not optimal for most of the flex situations.

In my previous post I already mentioned the problem of sessions that are closed and exception handling with respect to security. In this article I am looking at the available mechanisms for security in flex. In this post I explain why I am not really using the flex or better BlazeDS security mechanisms and what you probably should use them for.

If this made you curious enough, read on. If you have questions, remarks or improvements, do not hesitate to use the comments feature of this blog item.

The security problem

The basic problem: we want to know who you are and we then what you are allowed to do. In other words, we want to use authentication to determine if you are who you say you are and authorization to determine what you are allowed to do. For web applications, authentication is a territory that is well known. There are lot’s of different ways to determine if you are who you say you are. Think about password, iris scan, finger print, tokens, etc.

When clients are becoming richer or fat, this does not have to change, but if you are creating applications that should function offline as well, you have some limitations. For my sample I am using a username/password combination. I am not using https, not using encryption. It is a basic sample.

I am going to show you a possible solution that uses some basic components to authenticate against a server side web application. Using BlazeDS we are talking direct to spring beans that are secured using spring security.

Flex and BlazeDS security mechanism for remoting

BlazeDS has a few things that can help you to secure the remote calls from the web client. You can configure security for remote destinations. This way BlazeDS makes sure you have been authenticated and have the required role. The following piece of code gives an example of a destination that is secured. The destination references a security-constraint that is also presented in the code.

<destination id="bookManager">
    <properties>
        <factory>spring</factory>
        <source>bookManager</source>
    </properties>
    <security>
        <security-constraint ref="trusted"/>
    </security>
    <security-constraint id="trusted">
        <auth-method>Custom</auth-method>
        <roles>
            <role>ROLE_USER</role>
        </roles>
    </security>
</destination>

This way the user is asked for a username/password using for instance using Basic authentication. This mechanism makes use of the application server. Since not all application servers use the same mechanism for authentication there are special implementations available for a number of application servers. If you want to use an application level authentication you need to create a custom LoginCommand.

At first I thought this was the way to go, to be honest I never really understood it. So maybe I am missing something. For now I think my solutions is more flexible and easier to understand for an average java programmer. Therefore the flex/BlazeDS model was not the way to go for me. Let’s go over to a general view of my solution.

Overview of my solution

I am a java programmer coming to the flex world. Therefore my solution might be a bit to java-ish. Since I am integrating with java and spring security specifically, I do not really mind. The following things were important to me when creating the solution:

  • No double configuration of roles for authorization, but the flex application must have the availability of obtained authorizations.
  • Easy recovery in case of exception due to bad credentials, unauthorized access of remote methods, session time out, etc.
  • No necessity for offline working (no authentication details available in the client)
  • Possibility of re-authentication in case of a session time out without asking for credentials again.

This all can be done using some hierarchy in classes, use of singletons for user data and custom events. The following image gives a schematic overview of the solution. You are missing the actual pages: Home and AuthenticationForm. You are also missing the actual service implementations on the server side. Both topics will be talked about later on.

Remote services, error handling and events.

FlexDiagrams.jpg

The image shows the main components for authentication and authorization from the flex side. The yellow classes are provided by the framework, the green ones are services that interact with remote services and the data obtained from the server. The blue ones are the events used by the security mechanism to expose the results of authentication and authorization requests.

Let’s start with the services. The super class RemoteService contains some generic methods for catching and translating them into events. The constructor excepts an id and the name of the destination for the RemoteObject to create. Beware that this name should be the same as configured in the destination of the remoting-config.xml. The next block of code shows you the implementation of this RemoteService

/**
 * Super class for all remote services that contains some generic methods.
 */
public class RemoteService {
    private static var BAD_CREDENTIALS:String = 
            "org.springframework.security.BadCredentialsException : Bad credentials";
    protected var remoteObject:RemoteObject;

    /**
     * Constructor accepting an id and destination for the actual RemoteObject to create. An event listener 
     * is added for exceptions.
     * @param id String representing the id of the new RemoteObject to create
     * @param destination String representing the destination of the RemoteObject to create
     */
    public function RemoteService(id:String, destination:String) {
        this.remoteObject = new RemoteObject(id);
        this.remoteObject.destination = destination;
        this.remoteObject.addEventListener(FaultEvent.FAULT,onRemoteException);
    }

    /**
     * generic fault event handler for all remote object actions. based on the received message an action 
     * is taken, mostly throwing a new event.
     * @param event FaultEvent received for handling
     */
    public function onRemoteException(event:FaultEvent):void {
        trace('code : ' + event.fault.faultCode + 
              ', message : ' + event.fault.faultString + 
              ',detail : ' + event.fault.faultDetail);
          
        if (event.fault.faultString == BAD_CREDENTIALS) {
            Application.application.dispatchEvent(
                    new AuthenticationFailureEvent(AuthenticationFailureEvent.AUTHENTICATION_FAILURE,
                            "problem while authenticating"))
        } else {
            Application.application.dispatchEvent(
                    new RemoteExceptionEvent(RemoteExceptionEvent.REMOTE_EXCEPTION,
                            "unknown problem occurred during a remote call : " + event.fault.message));
        }
    }
}

The onRemoteException is not finished yet, at the moment we only handle BAD_CREDENTIALS differently than other exceptions. I can imagine that we will have more exceptions here. But for now the structure is clear. Most important to notice is the use of events. Using the special Application object, we dispatch the AuthenticationFailureEvent as well as the RemoteExceptionEvent. The next thing is to create a sub-class of this service that actually uses the connection. So let’s have a look at the SecurityService

The SecurityService is used to authenticate a user making use of the provided username and password. The following block of code contains the implementation if this method:

public function authenticatePrincipal(username:String,password:String):void {
    var userData:UserData = UserData.getInstance();
    userData.username = username;
    userData.password = password;
    remoteObject.authenticatePrincipal.addEventListener(ResultEvent.RESULT,handleAuthenticatePrincipal);
    remoteObject.authenticatePrincipal(username,password);
}

Important in this piece of code is obtaining the singleton UserData instance. This instance contains the credentials and has some utility methods to see if the logged in user has administrative rights or is a plain user. The way to get the results back looks a bit strange to the average java programmer, but we are doing a-synchronous calls (like JMS guys). The next piece of code gets called if the response is received and handles this response.

protected function handleAuthenticatePrincipal(event:ResultEvent):void {
    var userData:UserData = UserData.getInstance();
    userData.authenticated = true;

    var obj:Object = event.result;
    var obtainedRoles:Array = obj.roles as Array;
    for(var i:int=0; i < obtainedRoles.length; i++) {
        userData.addGrantedRole(obtainedRoles&#91;i&#93;)
    }

    Application.application.dispatchEvent(
            new AuthenticationEvent(AuthenticationEvent.AUTHENTICATION,"user is authenticated"));
};
&#91;/sourcecode&#93;
<p>As you can see in the code we tell the userData object that he is authenticated now and we add the roles he is authorized for. To make sure that those that are interested get notified of the authentication we throw an event.
<p>The next step is to actually start the authentication process. This is done on the visual components. The first one that gets loaded is the <strong>Home.mxml</strong> page.</p>

<h3>Visible components interacting with services</h3>
<p>To demonstrate the complete authentication process we need only two visual components: <strong>Home.mxml</strong> and <strong>AuthenticationForm.mxml</strong>. I am not going to show them completely, you can browse the sources only. (see references at the bottom). In Home.mxml we add listeners for the <strong>AuthenticationEvent</strong> as well as the <strong>RemoteExceptionEvent</strong>, then we create the form and add it to the main content panel. The following piece of code shows just that:</p>
[sourcecode language='java']
private function initializeApplication():void {
    Application.application.addEventListener(AuthenticationEvent.AUTHENTICATION, handleAuthenticationEvent);
    Application.application.addEventListener(RemoteExceptionEvent.REMOTE_EXCEPTION, handleRemoteExceptionEvent);
    authenticateUser();
}

private function authenticateUser():void {
    myMainContentPanel.removeAllChildren();
    var authenticationForm:AuthenticationForm = new AuthenticationForm();
    authenticationForm.securityService = authenticationHelper;
    myMainContentPanel.addChild(authenticationForm);
}

As you can see in the code we call the method handleAuthenticationEvent when a user gets authenticated. This method is not interesting for our cause, it could just show Yes, I am authenticated in an alert box. Let’s have a look at the AuthenticationForm now. This is also a pretty simple class with a call to the security service and a method that handles the a-synchronous callback.

private function submitLogin():void {
    submitButton.enabled = false;
    Application.application.addEventListener(
            AuthenticationFailureEvent.AUTHENTICATION_FAILURE,loginServiceFaultHandler);
    securityService.authenticatePrincipal(userName.text,password.text);
}

private function loginServiceFaultHandler(event:AuthenticationFailureEvent):void {
    authenticationMessage.text = "Problem while authentication ...";
    authenticationMessage.visible = true;
    submitButton.enabled = true;
}

As you can see, the AuthenticationFailureEvent is handled by this form page. We show just a text that there was something wrong. In any other cases we let the Home page catch the AuthenticationEvent, we do nothing here.

Final part to cover is the server side implementation. That is what we’ll do next.

Server side implementations of remote services

Within the remoting-config.xml I have configured the following destination:

<destination id="authenticationHelper">
    <properties>
               <source>nl.gridshore.samples.books.web.security.security.AuthenticationHelper</source>
    </properties>
</destination>

Within the class Authenticationhelper you can find the following method that obtains the spring application context, executes the authentication and handles the obtained principal. Take notice of the name of the bean we obtain. This is the default name when using the namespace configuration of spring-security. The other important part is the creation of the UsernamePasswordAuthenticationToken and doing the actual authentication. Problems with authentication result in a runtime exception that is propagated into flex and handled by the RemoteService object.

public AuthorizationData authenticatePrincipal(String username, String password) {
ApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(
flex.messaging.FlexContext.getServletConfig().getServletContext());
AuthenticationManager manager = (AuthenticationManager)appContext.getBean(“_authenticationManager”);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(username,password);

Authentication authentication = manager.authenticate(usernamePasswordAuthenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);

GrantedAuthority[] authorities =
SecurityContextHolder.getContext().getAuthentication().getAuthorities();
int numAuthorities = authorities.length;
String[] grantedRoles = new String[numAuthorities];
for (int counter = 0; counter < numAuthorities ; counter++) { grantedRoles[counter] = authorities[counter].getAuthority(); } String name = SecurityContextHolder.getContext().getAuthentication().getName(); return new AuthorizationData(grantedRoles,name); } [/sourcecode]

That is about it, if you feel this is the way to go, check the sources in my google code repo. If you have strong opinion that this is just wrong, please share your thoughts.

References

Integration spring security (Acegi) and flex 3 the sequel
Tagged on: