Neosavvy, Inc Modern Knowledge Blog

13Nov/099

Building a Simple Login Mechanism in Flex – Part 2 (Enabling Spring Security)

This guide is an extension to the previous post http://blog.neosavvy.com/wordpress/?p=18 that described how to build a simple Flex project with Maven and also create the necessary base to start securing simple features in an application with BlazeDS integrations provided by Spring-Flex.

Please note that all SVN Links are temporarily locked down - I apologize for the inconvenience but please find the source here: http://www.neosavvy.com/commons-user-blog-post.tar.gz

In this article we will address the following topics:

  1. Adding spring-security 2.0.5 as a dependency
  2. Adding spring-flex 1.0.1.RELEASE as a dependency
  3. Defining security with annotations on UserService with one role
  4. Making Login calls from Flex to secured services
  5. Making Logout calls from Flex to secured services
  6. Accessing user and authority information after a successful login call

Adding spring-security 2.0.5 and spring-flex 1.0.1.RELEASE is simple if your application uses Maven

Add the following dependencies to your top level pom.xml and also add them without versions to your WAR and JAR pom.xml files.

           <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>${spring.version}</version>
                <exclusions>
                    <exclusion>
                        <artifactId>com.springsource.javax.validation</artifactId>
                        <groupId>javax.validation</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.springframework.flex</groupId>
                <artifactId>spring-flex</artifactId>
                <version>1.0.1.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-core</artifactId>
                <version>${spring.security.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-core-tiger</artifactId>
                <version>${spring.security.version}</version>
            </dependency>

Spring-webmvc is used for its DispatcherServlet in the servlet.xml file and the javax.validation artifacts caused a ClassLoader issue when starting the WAR so it is excluded as a transitive dependency. The latest release of spring-flex is 1.0.1.RELEASE and is included as that version. The base spring-security classes are part of spring-security-core and the Java 1.5 compliant classes are part of spring-security-core-tiger. Without that library the @Secured() annotations will not work.

After the necessary dependencies are included securing the service is as simple as adding some annotations to the interface of the message broker exposed service that necessitates security.

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
package com.neosavvy.user.service;
 
import com.neosavvy.user.dto.UserDTO;
import org.springframework.security.annotation.Secured;
 
import java.util.List;
 
public interface UserService {
 
    @Secured("ROLE_ADMIN")
    public List<UserDTO> getUsers();
 
    public void saveUser(UserDTO user);
 
    @Secured("ROLE_ADMIN")
    public UserDTO findUserById(int id);
 
    @Secured("ROLE_ADMIN")
    public List<UserDTO> findUsers(UserDTO user);
 
    @Secured("ROLE_ADMIN")
    public void deleteUser(UserDTO user);
 
    public Boolean login(UserDTO user);
 
    public Boolean logout(UserDTO user);
 
}

Notice in the above class that all @Secured annotations at this time have a value of “ROLE_ADMIN” which means that the “ROLE_ADMIN” privilege is required when a user is looked up from the security DAO. More on this later.

The WAR configuration needs some modification to expose the services as secured services. This requires the use of DispatcherServlet instead of ContextLoaderListener to initialize spring in the web.xml.

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
 
<web-app>
 
    <display-name>COMMONS-USER-SERVICE</display-name>
 
    <listener>
        <listener-class>flex.messaging.HttpFlexSession</listener-class>
    </listener>
 
    <servlet>
        <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                classpath*:applicationContext.xml
                classpath:flexContext.xml
                classpath:securityContext.xml
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
 
    <servlet-mapping>
        <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>
 
 
</web-app>

Note that the additional securityContext.xml that is included. This file is used to configure Spring to use spring security. The flexContext.xml also needed a one line adjustment to enable security in the message broker servlet configuration.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:flex="http://www.springframework.org/schema/flex"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/flex http://www.springframework.org/schema/flex/spring-flex-1.0.xsd">
 
    <flex:message-broker>
        <flex:remoting-service default-channels="user-amf"/>
        <flex:secured/>
    </flex:message-broker>
 
    <flex:remoting-destination ref="userService"/>
 
</beans>

Notice the inclusion of that is all that is required to make the annotations on userService respect the ROLE_ADMIN definition. If a user tries to access the method without logging in first, they will receive a security exception at runtime on the client.

Using the following securityContext.xml will enable security and associate the existing UserDTO objects with the login requests when users try to authenticate.

<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
                      http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                      http://www.springframework.org/schema/security
                      http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
 
    <http entry-point-ref="preAuthenticatedEntryPoint"/>
 
    <beans:bean id="preAuthenticatedEntryPoint"
                class="org.springframework.security.ui.preauth.PreAuthenticatedProcessingFilterEntryPoint"/>
 
    <authentication-provider user-service-ref="authenticationDao"/>
 
    <beans:bean id="authenticationDao" class="org.springframework.security.userdetails.jdbc.JdbcDaoImpl">
        <beans:property name="dataSource" ref="dataSource"/>
        <beans:property name="usersByUsernameQuery">
            <beans:value>
                SELECT USERNAME, PASSWORD, true as enabled FROM USER WHERE USERNAME=?
            </beans:value>
        </beans:property>
        <beans:property name="authoritiesByUsernameQuery">
            <beans:value>
                SELECT USERNAME, 'ROLE_ADMIN' AS PRIVILEGE FROM USER WHERE USERNAME=?
            </beans:value>
        </beans:property>
    </beans:bean>
 
    <global-method-security secured-annotations="enabled" jsr250-annotations="enabled"/>
</beans:beans>

The authoritiesByUsernameQuery is currently a hack that allows all users to have the ROLE_ADMIN but in a later article the differentiation between ROLE_USER and ROLE_ADMIN will be established. The other item of note in this file is that the global-method-security tag enables secured annotations which ensures that the @Secured annotations on the UserService interface are honored.

Flex Cient configuration:

Making a login call is relatively simple and to ensure simplicity the remoteobject is not separated from the view in the example. To perform a login a user will simply type in the username/password and click login. If they do not have a login, they can register a user (saveUser) as that is not restricted by security.

Here is the login functionality in the Login.mxml:

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
protected function login():void {
            errorLbl.text = null;
            var userService:RemoteObject = new RemoteObject();
 
            var channel:AMFChannel = new AMFChannel("user-amf", "http://localhost:8080/commons-user-webapp/messagebroker/amf");
            var channelSet:ChannelSet = new ChannelSet();
            channelSet.addChannel(channel);
            userService.channelSet = channelSet;
            userService.destination = "userService";
            channelSet.login(username.text, password.text);
            channelSet.addEventListener(ResultEvent.RESULT, resultHandler);
            channelSet.addEventListener(FaultEvent.FAULT, faultHandler);
        }
 
        protected function resultHandler( result:ResultEvent ):void {
            if (result.result.authorities.indexOf("ROLE_ADMIN") >= 0) {
                trace("User logged in as admin");
            } else {
                trace("User logged in as user");
            }
            FlexGlobals.topLevelApplication.hideRegistrationLoginWindow(result.result);
        }
 
        protected function faultHandler( fault:FaultEvent ):void {
            trace("Login Failed: " + fault.fault.faultString);
            errorLbl.text = "login invalid";
        }

Here is a snippet used in another function to retrieve the user from the object that is returned from the service after a successful call to login:

1
2
3
4
5
6
7
8
     public function hideRegistrationLoginWindow(security:Object):void {
            PopUpManager.removePopUp(regAndLoginWindow);
 
            if(security.hasOwnProperty("name")) {
                usernameLbl.text = security.name;
            }
            initializeService();
        }

Logging out is rather simple as well and requires no parameters. The assumption in this code is that the logout is successful and no error occurs. This may be an unsafe assumption, but for now this will suffice.

    public function logout() {
            var userService:RemoteObject = new RemoteObject();
            var channel:AMFChannel = new AMFChannel("user-amf", "http://localhost:8080/commons-user-webapp/messagebroker/amf");
            var channelSet:ChannelSet = new ChannelSet();
            channelSet.addChannel(channel);
            userService.channelSet = channelSet;
            userService.destination = "userService";
            channelSet.logout();
            showRegistrationLoginWindow();
            usernameLbl.text = "Not Logged In";
        }

Some additional polish was added to this feature in that a getUsers call occurs on initial application startup. Since the user is not logged in yet on initial load of the application a fault occurs and a dialog is displayed to login or register. Additionally the logged in user should display at the top right of the main dialog window.

This is essentially all it takes to secure a flex application in a basic way. Next time the addition of some more ROLE_* information and a better explanation of how the privilege tables works will be included / created. Some new features like enabling and disabling users will also be added. If anyone has a feature request as well posting it on the blog in comments is hugely appreciated and if it seems like a good idea I am happy to try and add it.

Thanks for reading and please contact me at aparrish@neosavvy.com if you have any questions or want to use the code as it is open source and totally free to use in any application for profit or not.

Comments (9) Trackbacks (0)
  1. I found this info very usefull but unfortunately I try it with SpringSecurity 3.0 and I am having some problems. I was thinking if you can publish a new tutorial of the same topic but with SpringSecurity 3.0.
    Thanks in advance.

  2. Juan:

    Thank you for taking the time to read my tutorial. I first started with Spring 3.0 RC1 with Spring-Security 3.0 and at the time of writing Spring-Flex was not able to use Spring-security 3.0.

    I have posted a few times on Spring’s forums when I was working on this and I believe I found that the Spring-flex support isn’t quite there yet. What I gathered was the Spring 2.5.6 release worked best with Spring Security 2.5.6 and Spring-flex 1.0.

    I will likely document the upgrade process when I get around to doing it on my side project. Please let me know if you are successful in integrating those versions when you get a chance!

    Cheers,
    Adam

  3. Thanks for the feedback. I tried with SpringSecurity 2.5.6 and it works very good.
    I just was wondering how the userBo implementation should see, so if you could post the source code, or maybe send it to me will be really helpfully. I am almost done with this and the source code of the example would be great.

    Thanks in advance.

  4. How do you deal with Registration and checking if the username already exists without being logged into the channelset and this able to access the Spring backend?

  5. Des:

    The channelSet.login() is used to invoke the spring security hooks on the java side. Amazingly you don’t have to write code for this as the two sql queries defined in the spring’s securityContext.xml file will only return true and a set of roles/granted authorities if and only if the user exists, is enabled, and the password matches.

    Hope that helps – sorry for the lag in responding.

    -Adam

  6. This works using the channelSet but what can we do if we are not using channelSets.
    The code I have is this:
    remoteObject = new RemoteObject();
    remoteObject.endpint = “someEndpoint”;
    remoteObject.desination = “someDestination”;

    In this case I do not set any channelSet.

    Do you know how to call login and logout services in this case?

    Thanks in advance

  7. Juan:

    I honestly don’t know of the correct way to do it, but you could just setup a SecurityProxy that has a login() method on it.

    That method could then create a ChannelSet() and call channelSet.login() just for the purpose of logging in.

    All other RemoteObject calls could remain as they were and would then be secured.

    From what I could tell the only one that mattered was the login() call on the ChannelSet….hope that helps.

    -Adam

  8. Thanks a lot Adam, it is working now.

    Thanks for all your help

  9. Hi
    http://www.neosavvy.com/commons-user-blog-post.tar.gz
    this is not working.

    i really liked your post and i want ot qick start with that.plz help me


Leave a comment


No trackbacks yet.

Pages

Categories

Blogroll

Archive

Meta