dima_vp (dima_vp) wrote,

Flex: Spring/BlazeDS services protection with Spring Security(Acegi).


Introduction


This article has following goals:
  1. Give an example of AMF-services based on Spring framework(www.springsource.org/about), BlazeDS services(opensource.adobe.com/wiki/display/blazeds/BlazeDS/), Spring BlazeDS Integration library(www.springsource.org/spring-flex) and Adobe Flex(www.adobe.com/products/flex/).
  2. Give an example of establishing a proper security of whole Java-Flex interaction using Spring Security(formerly known as Acegi)(static.springframework.org/spring-security/site/index.html). AMF-channels protection will not be discussed here since it can be done simply using HTTPS.
Here will be observed following web application work scheme:
Web application work scheme

Development was performed on Apache Tomcat 6.0.18.


Establish Spring/BlazeDS services

Following jars are required:
Spring core 2.5.6
spring.jar
spring-webmvc.jar
cglib-nodep-2.1_3.jar (needed for proxying classes without need to extract interfaces)
BlazeDS 3.2.0.3978
flex-messaging-common.jar,
flex-messaging-core.jar,
flex-messaging-opt.jar,
flex-messaging-proxy.jar,
flex-messaging-remoting.jar,
commons-codec-1.3.jar,
commons-httpclient-3.0.1.jar,
commons-logging.jar,
concurrent.jar,
backport-util-concurrent.jar,
cfgatewayadapter.jar,
xalan.jar,
Spring BlazeDS Integration library 1.0.0.RC2
org.springframework.flex-1.0.0.RC2.jar,
com.springsource.org.codehaus.jackson-1.0.0.jar,
asm-2.2.3.jar (get it from spring framework dependencies, see "spring-framework-2.5.6.SEC01-with-dependencies.zip"),
Note, that with various versions of Spring BlazeDS Integration library you may need various set of additional libraries. For example with version 1.0.0.M2 you do not need com.springsource.org.codehaus.jackson-1.0.0.jar and asm-2.2.3.jar.
Other
log4j-1.2.14.jar

Set up Spring and BlazeDS services. See opensource.adobe.com/wiki/display/blazeds/Installation+Guide#InstallationGuide-InstallingBlazeDSwithJ2EEwebapplications.
Set up Spring BlazeDS Integration library. See static.springframework.org/spring-flex/docs/1.0.x/reference/html/, chapters 2 and 3.

Just in case I cite here my working xmls.
Here is my web.xml (with Spring Security adjusted which will be referred in next paragraph):
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
           version="2.5">
    <session-config>
        <session-timeout>10</session-timeout>
    </session-config>
    <!--To auto-redirect user to http if he requested http-->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Protected context</web-resource-name>
            <url-pattern>/**</url-pattern>
        </web-resource-collection>
        <user-data-constraint>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher
        </listener-class>
    </listener>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--<init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext.xml</param-value>
        </init-param>-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!--<servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>-->
    <!-- Map all /messagbroker requests to the DispatcherServlet for handling -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/spring-flex/*</url-pattern>
    </servlet-mapping>
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>


My services-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<services-config>
    <services>
        <service-include file-path="remoting-config.xml" />
        <service-include file-path="proxy-config.xml" />
        <service-include file-path="messaging-config.xml" />
        <default-channels>
            <channel ref="my-amf"/>
        </default-channels>
    </services>
    <security>
        <login-command class="flex.messaging.security.TomcatLoginCommand" server="Tomcat"/>
    </security>

    <channels>
        <channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel">
            <endpoint url="http://{server.name}:{server.port}/{context.root}/spring-flex/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/>
            <properties>
                <serialization>
                    <instantiate-types>true</instantiate-types>
                </serialization>
            </properties>
        </channel-definition>
        <channel-definition id="my-secure-amf" class="mx.messaging.channels.SecureAMFChannel">
            <endpoint url="https://{server.name}:{server.port}/{context.root}/spring-flex/messagebroker/amfsecure" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>
        <channel-definition id="my-polling-amf" class="mx.messaging.channels.AMFChannel">
            <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amfpolling" class="flex.messaging.endpoints.AMFEndpoint"/>
            <properties>
                <polling-enabled>true</polling-enabled>
                <polling-interval-seconds>4</polling-interval-seconds>
            </properties>
        </channel-definition>
    </channels>

    <logging>
        <target class="flex.messaging.log.ConsoleTarget" level="Error">
            <properties>
                <prefix>[BlazeDS] </prefix>
                <includeDate>false</includeDate>
                <includeTime>false</includeTime>
                <includeLevel>false</includeLevel>
                <includeCategory>false</includeCategory>
            </properties>
            <filters>
                <pattern>Endpoint.*</pattern>
                <pattern>Service.*</pattern>
                <pattern>Configuration</pattern>
            </filters>
        </target>
    </logging>

    <system>
        <redeploy>
            <enabled>false</enabled>
        </redeploy>
    </system>
</services-config>


My remoting-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<service id="remoting-service" 
    class="flex.messaging.services.RemotingService">
    <adapters>
        <adapter-definition id="java-object" class="flex.messaging.services.remoting.adapters.JavaAdapter" default="true"/>
    </adapters>
    <default-channels>
        <channel ref="my-amf"/>
    </default-channels>
</service>
My proxy-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<service id="proxy-service" 
    class="flex.messaging.services.HTTPProxyService">
    <properties>
        <connection-manager>
            <max-total-connections>100</max-total-connections>
            <default-max-connections-per-host>2</default-max-connections-per-host>
        </connection-manager>
        <allow-lax-ssl>true</allow-lax-ssl>
    </properties>

    <adapters>
        <adapter-definition id="http-proxy" class="flex.messaging.services.http.HTTPProxyAdapter" default="true"/>
        <adapter-definition id="soap-proxy" class="flex.messaging.services.http.SOAPProxyAdapter"/>
    </adapters>

    <default-channels>
        <channel ref="my-amf"/>
    </default-channels>
    <destination id="DefaultHTTP">
    </destination>
</service>
My messaging-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<service id="message-service" 
    class="flex.messaging.services.MessageService">
    <adapters>
        <adapter-definition id="actionscript" class="flex.messaging.services.messaging.adapters.ActionScriptAdapter" default="true" />
        <!-- <adapter-definition id="jms" class="flex.messaging.services.messaging.adapters.JMSAdapter"/> -->
    </adapters>
    <default-channels>
        <channel ref="my-polling-amf"/>
    </default-channels>
</service>


My applicationContext.xml with the sample bean exposed in the network:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:flex="http://www.springframework.org/schema/flex"
       xmlns:security="http://www.springframework.org/schema/security"
       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/flex
                           http://www.springframework.org/schema/flex/spring-flex-1.0.xsd
                           http://www.springframework.org/schema/security
                           http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
    <flex:message-broker services-config-path="/WEB-INF/flex/services-config.xml">
        <flex:mapping pattern="/*"/>
    </flex:message-broker>

    <bean id="sampleService" class="com.demon.education.technologies.flex.services.SampleService">
        <flex:remoting-destination channels="my-amf"/>
    </bean>
</beans>



Protect Spring/BlazeDS services

Following jars are required:
Spring Security(Acegi) 2.0.4
spring-security-acl-2.0.4.jar,
spring-security-core-2.0.4.jar,
spring-security-core-tiger-2.0.4.jar,
spring-security-taglibs-2.0.4.jar,
aspectjrt.jar (get it from spring framework dependencies, see "spring-framework-2.5.6.SEC01-with-dependencies.zip")

Now let's protect our service by Spring Security. See static.springframework.org/spring-flex/docs/1.0.x/reference/html/, chapter 4.

Here is my applicationContext.xml with sample bean and security additions:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:flex="http://www.springframework.org/schema/flex"
       xmlns:security="http://www.springframework.org/schema/security"
       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/flex
                           http://www.springframework.org/schema/flex/spring-flex-1.0.xsd
                           http://www.springframework.org/schema/security
                           http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">

    <!--To disable login page redirect on security exception on URL level-->
    <!--<bean id="preAuthenticatedEntryPoint" class="com.demon.homepage.prototype.fileshare.service.PreAuthenticatedProcessingFilter403EntryPoint"/>-->

    <security:http>
        <!--These URL-interceptors are in fact Filters. So if Spring Security throws an access
            exception you will not receive it in flex client,
            you will recieve a redirect to login page or HTTP error code like 403.-->
        <security:intercept-url pattern='/index.jsp' filters='none' requires-channel="http"/>
        <security:intercept-url pattern='/flex/login*' filters='none' requires-channel="http"/>
        <security:intercept-url pattern='/flex/playerProductInstall.swf' filters='none' requires-channel="http"/>
        <security:intercept-url pattern='/flex/AC_OETags.js' filters='none' requires-channel="http"/>
        <security:intercept-url pattern='/flex/history/**' filters='none' requires-channel="http"/>
        <!--filtering of anonymus user used otherwise we will recieve
            org.springframework.security.AuthenticationCredentialsNotFoundException:
            "An Authentication object was not found in the SecurityContext" exception
            even though the user was successfuly logged in-->
        <security:intercept-url pattern='/spring-flex/messagebroker/**' access='ROLE_ANONYMOUS, ROLE_USER' requires-channel="http"/>
        <security:intercept-url pattern='/**' access='ROLE_USER' requires-channel="http"/>
        <security:form-login login-processing-url="/j_spring_security_check"
                             login-page='/flex/login.html' default-target-url='/flex/service.html'
                             always-use-default-target='true' authentication-failure-url="/flex/login.html"/>
        <security:concurrent-session-control max-sessions="100" session-registry-alias="sessions"/>
        <security:logout logout-url="/index.jsp" invalidate-session="true" logout-success-url="/flex/login.html"/>
        <security:anonymous granted-authority="ROLE_ANONYMOUS"/>
        <security:remember-me/>
    </security:http>

    <security:authentication-provider>
        <security:password-encoder hash="plaintext"/>
        <security:user-service>
            <!--Here we can use any role names-->
            <security:user name="jimi" password="jimi" authorities="ROLE_USER, ROLE_ADMIN"/>
            <security:user name="bob" password="bob" authorities="ROLE_USER"/>
        </security:user-service>
    </security:authentication-provider>

    <bean id="myExceptionTranslator" class="com.demon.education.technologies.flex.MySecurityExceptionTranslator"/>

    <flex:message-broker services-config-path="/WEB-INF/flex/services-config.xml">
        <flex:mapping pattern="/*"/>
        <flex:exception-translator ref="myExceptionTranslator"/>
        <flex:secured/>
    </flex:message-broker>

    <bean id="sampleService" class="com.demon.education.technologies.flex.services.SampleService">
        <!--AOP method interceptors (compare with Filter interceptors)-->
        <security:intercept-methods>
            <security:protect method="*" access="ROLE_USER"/>
        </security:intercept-methods>
        <flex:remoting-destination channels="my-amf"/>
    </bean>
</beans>


The simplest authentication scheme is used, see tag "security:authentication-provider".

The Spring Security has two levels of protection: security filters and security interceptors.
Security filters protect server resources and redirect on login page when user is not authenticated.
The security interceptors are Spring AOP interceptors which are responcible for user authorization.
First of all security filters are executed and then security interceptors do. See scheme in Introduction paragraph.

Tags "security:intercept-url" define security filters.
We expose login URLs and protect all internal URLs, see <security:intercept-url pattern='/**' access='ROLE_USER' requires-channel="http"/>.

Row <security:anonymous granted-authority="ROLE_ANONYMOUS"/> grants non-authenticated user with role ROLE_ANONYMOUS to give him full access to AMF-channel on security filters layer.
Note the line <security:intercept-url pattern='/spring-flex/messagebroker/**' access='ROLE_ANONYMOUS, ROLE_USER' requires-channel="http"/>. Here we allow anonymous user to access AMF-channel. But later tag "security:intercept-methods" protects spring bean on security interceptors level.
This is a trick which allows us correctly transmit security exception from server to Flex. With this approach if the security exception arised on server side (for ex. Access Denied exception) then we will get it on Flex side. Otherwise with the security filter only applied we will see only common HTTP exception which doesn't tell us what happened on server (this occurs because if user is not authorized security filter redirects him to login page or sends non-200 HTTP code, that is not expected by Flex AMF client ).

Note the bean defined in row <bean id="myExceptionTranslator" class="com.demon.education.technologies.flex.MySecurityExceptionTranslator"/>. It allows to extract helpful information from arized exception and pack it in special exception which we can observe on Flex side.

Here it is MySecurityExceptionTranslator.java:
public class MySecurityExceptionTranslator implements ExceptionTranslator {
    private static final Log logger = LogFactory.getLog(MySecurityExceptionTranslator.class);

    public boolean handles(Class<?> clazz) {
        return ClassUtils.isAssignable(AuthenticationException.class, clazz) || ClassUtils.isAssignable(AccessDeniedException.class, clazz);
    }
    public MessageException translate(Throwable t) {
        //log exception, otherwise it will not be displayed in log
        logger.error("Exception to translate.", t);
        if (t instanceof AuthenticationException || t instanceof AccessDeniedException) {
            SecurityException se = new SecurityException();
            se.setCode(SecurityException.CLIENT_AUTHENTICATION_CODE);
            se.setMessage(t.getLocalizedMessage());
            se.setRootCause(t);
            //transmit exception class name to see it on flex client side
            se.setDetails(t.getClass().getName());
            return se;
        }
        return null;
    }
}

An example of error message which was arised on Flex side when user is not allowed to access sample bean (you can see exception message and exception class name in the alert):
Tags: spring java flex blazeds
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded  

  • 0 comments