5. Security

Introduction

Security in Flex applications cannot rely on standard web-app security-constraints configured in web.xml. Generally, you have only one channel-definition, equivalent to a url-pattern in web.xml, and multiple destinations. So, the security must be destination-based rather than URL-pattern based, and J2EE standard configuration in web.xml does not provide anything like that.

With a configured SecurityService, you will be able to use RemoteObject's setCredentials, setRemoteCredentials and logout methods.

Another important feature in security is to be able to create and expose a java.security.Principal to, for example, an EJB3 session bean backend so role-based security can be used.

At this time, GraniteDS only provides security services for Tomcat5+, Jetty6+, GlassFish V2+ (V3 is supported) and WebLogic 10+ servers. Because JBoss comes with Tomcat by default but may be configured to use Jetty instead, Tomcat or Jetty security services may work as well with JBoss.

For a complete sample of a JBoss/Tomcat security service, please download and install graniteds-2.0.0.GA.zip, read the examples/README.txt file and test the examples/granite_ejb3 sample.

When you are using Java Enterprise frameworks such as Seam or Spring together with GraniteDS, you may use specific Seam/Identity or Spring/Acegi (now called Spring Security) security implementations instead of the previous Servlet container based services: please refer to Seam Services or Spring Services for more informations.

Configuration

To enable security, you simply put this kind of declaration in your granite-config.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE granite-config PUBLIC
    "-//Granite Data Services//DTD granite-config internal//EN"
    "http://www.graniteds.org/public/dtd/2.0.0/granite-config.dtd">
<granite-config>
    ...
    <security type="org.granite.messaging.service.security.TomcatSecurityService"/>
    <!--
    Alternatively:
    <security type="org.granite.messaging.service.security.Jetty6SecurityService"/>
    Or
    <security type="org.granite.messaging.service.security.GlassFishSecurityService"/>
    Or
    <security type="org.granite.messaging.service.security.GlassFishV3SecurityService"/>
    Or
    <security type="org.granite.messaging.service.security.WebLogicSecurityService"/>
    -->
</granite-config>

Normally, there is no need to pass additional parameters, but TomcatSecurityService accepts one optional parameter for its internal server.findService advanced utility, otherwise, the first available service is used by default:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE granite-config PUBLIC
    "-//Granite Data Services//DTD granite-config internal//EN"
    "http://www.graniteds.org/public/dtd/2.0.0/granite-config.dtd">
<granite-config>
    ...
    <security type="org.granite.messaging.service.security.TomcatSecurityService">
        <param name="service" value="your-tomcat-service-name-here"/>
    </security>
</granite-config>

You may now use role-based security on destination in your services-config.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
    <services>
        <service id="granite-service"
            class="flex.messaging.services.RemotingService"
            messageTypes="flex.messaging.messages.RemotingMessage">
            <destination id="person">
                <channels>
                    <channel ref="my-graniteamf"/>
                </channels>
                <properties>
                    <scope>session</scope>
                    <source>test.pojo.PersonService</source>
                </properties>
                <security>
                    <security-constraint>
                        <auth-method>Custom</auth-method>
                        <roles>
                            <role>user</role>
                            <role>admin</role>
                        </roles>
                    </security-constraint>
                </security>
            </destination>
        </service>
        <service id="granite-service"
            class="flex.messaging.services.RemotingService"
            messageTypes="flex.messaging.messages.RemotingMessage">
            <destination id="personRestricted">
                <channels>
                    <channel ref="my-graniteamf"/>
                </channels>
                <properties>
                    <scope>session</scope>
                    <source>test.pojo.PersonRestrictedService</source>
                </properties>
                <security>
                    <security-constraint>
                        <auth-method>Custom</auth-method>
                        <roles>
                            <role>admin</role>
                        </roles>
                    </security-constraint>
                </security>
            </destination>
        </service>
    </services>
    ...
</services-config>

Here, the person destination can be used by authenticated users with user or admin roles, while the personRestricted destination can only be used by authenticated users with the admin role.

Please refer to Tomcat and JBoss documentation for setting up your users/roles configuration.

SecureRemoteObject

The simplest way to use security in your Flex application is to used the org.granite.rpc.remoting.mxml.SecureRemoteObject action script class. This class brings advanced event-based security support as shown here:

...
import org.granite.rpc.remoting.mxml.SecureRemoteObject;
import org.granite.events.SecurityEvent;
...
private var srv:SecureRemoteObject = null;
...
public function init():void {
    srv = new SecureRemoteObject("mydestination");
    srv.addEventListener(SecurityEvent.ALL, onSecurityEvent);
    ...
}

public function onSecurityEvent(event:SecurityEvent):void {
    switch (event.type) {
    case SecurityEvent.INVALID_CREDENTIALS:
        // show message "wrong username or pasword"
        break;
    case SecurityEvent.NOT_LOGGED_IN:
        srv.logout(); // reset remote object
        // show login panel...
        break;
    case SecurityEvent.SESSION_EXPIRED:
        srv.logout(); // reset remote object
        // show login panel...
        break;
    case SecurityEvent.ACCESS_DENIED:
        // show message "you don't have rights..."
        break;
    }
}

public function onCredentialsSet(username:String, password:String):void {
    srv.setCredentials(username, password);
    ...
}

public function doLogout():void {
    srv.logout();
    ...
}
...

Note that you must compile your MXML/AS classes with the granite.swc library in order to use SecureRemoteObject.

Role-Based Security with EJB 3

If you use EJB 3 session beans, you can use role-based security checking with special annotations:

@Stateless
@Local(PersonService.class)
@SecurityDomain("other")
public class PersonServiceBean implements PersonService {
    
    @PersistenceContext
    protected EntityManager manager;

    public List<Person> findAllPersons() {
        return manager.createQuery("select distinct p from Person p").getResultList();
    }

    @RolesAllowed({"admin"})
    public Person createPerson(Person person) {
        return manager.merge(person);
    }

    @RolesAllowed({"admin"})
    public Person modifyPerson(Person person) {
        return manager.merge(person);
    }

    @RolesAllowed({"admin"})
    public void deletePerson(Person person) {
        person = manager.find(Person.class, person.getId());
        manager.remove(person);
    }
}

Here, within the security domain other (@SecurityDomain("other"), see login-config.xml in <JBOSS_HOME>/server/default/conf), calling the three last methods will fail if the current user does not have the admin role (@RolesAllowed({"admin"})), while the findAllPersons() method is available for everybody.

Granite DS Security Implementation

Granite DS implements security based on the following SecurityService interface. Note that Service in SecurityService has nothing to do with a true Flex destination, since security services are not exposed to outside calls:

package org.granite.messaging.service.security;

import java.util.Map;

public interface SecurityService {

    public void configure(Map<String, String> params);

    public void login(Object credentials) throws SecurityServiceException;

    public Object authorize(AbstractSecurityContext context) throws Exception;

    public void logout() throws SecurityServiceException;
    
    public void handleSecurityException(SecurityServiceException e);
}

An implementation of this interface must be thread safe, i.e., only one instance of this service is used in the entire web-app and will be called by concurrent threads.

  • configure: This method is called at startup time and gives a chance to pass parameters to the security service.
  • login: This method is called when you call one of the setCredentials or setRemoteCredentials RemoteObject's method. Note that these method calls do not fire any request by themselves but only pass credentials on the next destination service method call. The login method is responsible for creating and exposing a java.security.Principal or throwing an appropriate org.granite.messaging.service.security.SecurityServiceException if credentials are invalid. Note that credentials are a Base64 string with the common "username:password" format.
  • authorize: This method is called upon each and every service method call invocations (RemoteObject) or subscribe/publish actions (Consumer/Producer). When used with RemoteObjects, the authorize method is responsible for checking security, calling the service method, and returning the corresponding result. When used with Consumers/Producers, it is simply responsible for checking security; no service method invocation, no result. If authorization fails, either because the user is not logged in or because it doesn't have required rights, it must throw an appropriate org.granite.messaging.service.security.SecurityServiceException.
  • logout: This method is called when you call the logout RemoteObject's method. Note that the RemoteObject.logout method fires a remote request by itself.
  • handleSecurityException: This method is called whenever a SecurityServiceException is thrown by a login or logout operation. The default implementation of this method in AbstractSecurityService is to do nothing, but you may add extra care for these security exceptions if you need so.

Fine Grained Per Destination Security

You may write and configure a specific DestinationSecurizer in order to get fine grained security checking for specific actions. This especially useful for Gravity (Data Push) when you want to control subscribe/publish calls.

With Gravity, you must implement the org.granite.gravity.security.GravityDestinationSecurizer interface:

public interface GravityDestinationSecurizer extends DestinationSecurizer {

    public void canSubscribe(GravityInvocationContext context)
        throws SecurityServiceException;
    public void canPublish(GravityInvocationContext context)
        throws SecurityServiceException;
}

For standard remote calls, you must implement the org.granite.messaging.service.security.RemotingDestinationSecurizer interface:

public interface RemotingDestinationSecurizer extends DestinationSecurizer {

    public void canExecute(ServiceInvocationContext context)
        throws SecurityServiceException;
}

In both cases, you should also tell Granite where to use your securizer:

services-config.xml
<services-config>
    <services>
        <service ...>
            <destination id="securized">
                ...
                <properties>
                    <securizer>path.to.MyDestinationSecurizer</securizer>
                </properties>
            </destination>
        </service>
    </services>
    ...
</services-config>

Note that securizers, if any, are always called before the standard SecurityService.authorize() method.


Browse Space

- Pages
- Blog
- Labels
- Attachments
- Bookmarks
- Mail
- Advanced

Explore Confluence

- Popular Labels
- Notation Guide

Your Account

Log In

Other Features

Add Content

- Add Comment


SourceForge.net Logo
Copyright © 2007-2010 Adequate Systems. All Rights Reserved.