One thing to keep in mind with the out-of-the-box security support in Blaze DS is the approach to integration is container-specific: there is support for Tomcat (and therefore JBoss), WebSphere, Weblogic and Oracle through various implementations of the LoginCommand interface. Unfortunately if you have custom security requirements for authentication that means you're dealing with a lot of cumbersome, container-specific security configuration and/or writing and configuring JAAS plugins. The authorization support in Blaze DS is limited to specifying which roles have access to a particular destination which isn't nearly flexible enough.
Fortunately Spring Security provides solutions to many common problems in Java EE space, including features like container portability, a flexible authentication provider model, authorization of service method invocation via AOP and even some very cool ACL support to enforce granular security at the domain object level. Integrating Spring Security with Blaze DS isn't as hard as you think either: I was able to bang out a quick proof of concept over a weekend.
The config for Spring Security 2 is quite straightforward with the new XML namespace support in your Spring config files:
<security:http>
<security:form-login>
</security:form-login>
This is basically a very stripped-down configuration since things like RememberMeServices (using cookies) don't usually apply in a Flex-based RIA. You can also throw in a very simple AuthenticationProvider like this one from the SS2 docs:
<security:authentication-provider>
<security:user-service>
<security:user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN"/>
<security:user name="bob" password="bobspassword" authorities="ROLE_USER"/>
</security:user-service>
</security:authentication-provider>
There are two items you need to add to your web.xml to bootstrap SS2 in a Servlet container:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter>
<filter-name>securityContextAwareFilter</filter-name>
<filter-class>org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</filter-mapping>
</filter-mapping>
<filter-mapping>
<filter-name>securityContextAwareFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
The first filter is standard part of any Spring Security configuration. The "SecurityContextHolderAwareRequestFilter" adapts SS2 to the Servlet environment so that calls like getPrincipal() and isUserInRole() behave as expected. This really comes in handy when you have other code in your projects that assumes a "standard" Java security setup.
Now we need a little config in the Blaze services-config.xml file:
<security>
<login-command class="net.histos.util.spring.SpringSecurityLoginCommand" server="Tomcat"/>
<security-constraint id="valid-user">
<auth-method>Custom</auth-method>
<roles>
<role>ROLE_USER</role>
</roles>
</security-constraint>
</security>
Blaze DS seems to require that a "server" attribute be specified for any LoginCommand even though this isn't really used in our implementation. The security-constraint isn't necessary if you are going to use SS2's service method invocation authorization support. However if your security requirements are more straightforward you can do role/destination based restrictions here. Then simply add this element to the appropriate destinations:
<security>
<security-constraint ref="valid-user"/>
</security>
The last part is some Java code. If you extend AppServerCommand you get a default impl for this method:
protected boolean doAuthorization(Principal principal, List roles, HttpServletRequest request) throws SecurityException
This method makes use of isUserInRole(), so by adding the servlet filter referred to above, this logic can work without any modification required. This leaves only two methods in your LoginCommand impl:
public Principal doAuthentication(String username, Object credentials) {
log.debug("doAuthentication");
// get the ProviderManager from app context
Map<string, providermanager=""> map = getContext().getBeansOfType(ProviderManager.class);
if (map.size() != 1)
throw new RuntimeException("Spring ApplicationContext must contain exactly one ProviderManager bean");
ProviderManager provider = map.get( map.keySet().iterator().next() );
// authenticate
String password = extractPassword(credentials);
Authentication auth = provider.authenticate( new UsernamePasswordAuthenticationToken(username, password) );
SecurityContextHolder.getContext().setAuthentication(auth);
return auth;
}
public boolean logout(Principal principal) {
log.debug("logout");
SecurityContextHolder.getContext().setAuthentication(null);
return true;
}
Those are the basics! As I said, this a proof of concept that I haven't had time to test extensively yet but it should get you started! One other note: I noticed while testing the Flex side that calling login() or logout() on a RemoteObject without calling a "regular" service method would result an error; apparently the ChannelSet hadn't been defined yet. I did a little digging and found that apparently you're supposed to call login() or logout() on the underlying ChannelSet itself. I wrote a very simple ChannelSet implementation that can be easily instantiated in MXML and bound as the channelSet property for your RemoteObjects:
package net.histos.flex.util
{
import mx.messaging.ChannelSet;
import mx.messaging.channels.AMFChannel;
public class SimpleChannelSet extends ChannelSet
{
public function set url(channelUrl : String) : void {
addChannel(new AMFChannel("defaultChannel", channelUrl));
}
}
}
<util:SimpleChannelSet id="channelSet" url="http://localhost:8080/testapp/messagebroker/amf"/>
Then simply invoke login() and logout() on the ChannelSet itself and you should have no problems.
I have use a custom login command say xyz.LoginCommand BlazeDS is unable to create a channel or enpoint. However if i use a tomcat LoginCommand , BlazeDS is able to create a channel.
What could be the problem.
Regards
Govind
Posted by: Govind | October 29, 2008 at 12:04 AM
Hello Cliff,
Thanks a lot for this blog entry and the associated source code.
I re-used and included your stuff in a library I released and documented here :
* http://fna.googlecode.com
* http://fna.googlecode.com/svn/trunk/fna/site/flex-contrib-spring/index.html
This library is also referenced in a maven archetype of mine:
* http://fna.googlecode.com/svn/trunk/fna/site/mvn_archetypes/blazeds-autowired-spring-hibernate-archetype/index.html
Blazeds-autowired-spring-hibernate-archetype helps you generate a multi-module maven project : a flex front-end application communicating with the backend through Adobe's blazeds messaging. The back-end relies on a spring hibernate architecture.
If you wanna contribute :
I'd be happy to update this library with your newest findings.
Cheers !
François
http://wwww.droff.com
Posted by: François Le Droff | October 29, 2008 at 06:01 AM
Hey Govind, I would recommend extending TomcatLoginCommand first but not overriding anything to ensure your configuration is correct. Then change your class to extend AppServerLoginCommand or simply just implement the LoginCommand interface.
Hey François, that is very cool. I'm glad you found it useful and thanks for sharing!
Posted by: Cliff Meyers | October 29, 2008 at 08:05 AM
Hi François, Thanks for the example I have BlazeDS-Spring Security up and running fine but there is one thing that is troubling me. The doAuthentication method returns an Authentication object which contains the appropriate roles for the user but the Flex client only recieves a String Object with the value 'Success' (From a valid login!)
Is there a way to return the user roles to the Flex client from this initial authentication call? I realise that it could be done with another call to a service but it would be useful to be able to do it here and then alter the client according to the returned roles.
Cheers!
Posted by: Jim To | January 15, 2009 at 06:28 AM
Hi there, where can i find a link to source files?
Posted by: en3rgizer | March 13, 2009 at 04:44 AM
I never posted the raw source files, just the snippets in this blog post. You can follow Francois' links to the FNA project which incorporates my snippets into full sample code.
Posted by: Cliff Meyers | March 13, 2009 at 12:17 PM
Thanks a lot, this greatly helped me!
Posted by: en3rgizer | March 16, 2009 at 01:52 AM
For an example of integration of Blaze Data Services and Spring Security see http://dima-vp.livejournal.com/573.html.
Posted by: dima_vp | May 29, 2009 at 09:54 AM