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:
- Adding spring-security 2.0.5 as a dependency
- Adding spring-flex 1.0.1.RELEASE as a dependency
- Defining security with annotations on UserService with one role
- Making Login calls from Flex to secured services
- Making Logout calls from Flex to secured services
- 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
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.
January 4th, 2010 - 01:21
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.
January 4th, 2010 - 01:26
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
February 4th, 2010 - 04:19
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.
April 28th, 2010 - 11:41
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?
May 5th, 2010 - 11:33
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
May 19th, 2010 - 04:17
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
May 22nd, 2010 - 03:11
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
May 31st, 2010 - 01:40
Thanks a lot Adam, it is working now.
Thanks for all your help
June 17th, 2010 - 06:36
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