Skip Headers
Oracle® Coherence Tutorial for Oracle Coherence
Release 3.7.1

E22622-03
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Index
Index
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
View PDF

10 Working with Security

This chapter describes how to apply security to a Coherence*Extend client. Coherence*Extend allows a wide range of access to Coherence caches. These include desktop applications, remote servers, and machines located across wide area network (WAN) connections.

This chapter contains the following sections:

10.1 Introduction

Coherence*Extend consists of an extend client running outside the cluster and a proxy service running inside the cluster, hosted by one or more cache servers. The client APIs route all requests to the proxy. The proxy responds to client requests by delegating to Coherence clustered services, such as a partitioned or replicated cache service or an invocation service.

Because the extend client exists outside of the cluster, the issue of securing access to the cluster takes on greater importance. This chapter describes three techniques that you can use to secure access between the client and the cluster. The techniques include using identity token-based passwords, an entitled cache service, and an invocation service.

A detailed discussion of these security techniques and extend clients is beyond the scope of this tutorial. For more information on these topics, see the Oracle Coherence Security Guide.

10.2 Enabling Token-Based Security

You can implement token-based security to enable access between an extend client and an extend proxy in the cluster. To enable access between the extend client and an extend proxy the following files are typically required:

To add token-based security, you must also provide identity transformer and asserter implementations. The transformer generates the token on the client side and the asserter validates it on the cluster side.

The following steps describe how to create and run an application for an extend client that uses token-based security to access the cluster.

  1. Use a Security Helper File

  2. Create an Identity Transformer

  3. Create an Identity Asserter

  4. Create the Password File

  5. Enable the Identity Transformer and Asserter

  6. Create a Cache Configuration File for the Extend Client

  7. Create a Cache Configuration File for the Extend Proxy

  8. Create a Start-Up Configuration for a Cache Server with a Proxy Service

  9. Create a Start-Up Configuration for a Cache Server

  10. Run the Password Example

10.2.1 Use a Security Helper File

The examples in this chapter reference a security helper file that defines role-based security policies and access control to the cache. For the purpose of these examples, a file with simplified mappings is provided for you.

Cache access is determined by a user's role. The security helper file defines several roles: role_reader, role_writer, and role_admin. It defines the mappings of various users to the roles, such as BuckarooBanzai to ROLE_ADMIN. It defines the mappings of roles to integer IDs, such as ROLE_ADMIN to 9. The helper file also defines the cache name and the invocation service name used in the examples.

The key features of this file are the login and checkAccess methods. The login method takes a user name and constructs a simplified distinguished name (DN). It then associates a role with the name. PofPrincipal provides the Principal implementation.

The checkAccess method shows where the authorization code is placed. It determines whether the user can access the cache based on a provided user role.

To create a new project and the security helper file:

  1. In Eclipse IDE, select the Java EE perspective and create a new Application Client Project called Security. Select CoherenceConfig from the Configuration drop-down list. Ensure that the Create a default main is not selected on the Application Client module page.

    In the Coherence page, select only Coherence37.

    See "Creating a New Project in the Eclipse IDE" for detailed information on creating a project.

  2. Create a new Java file named SecurityExampleHelper.java. Ensure that the package path is com.oracle.handson.

    See "Creating a Java Class" for detailed information on creating a Java class.

  3. Copy the code illustrated in Example 10-1 into the file.

    Example 10-1 A Security Helper File

    package com.oracle.handson;
     
    import com.tangosol.io.pof.PofPrincipal;
     
    import com.tangosol.net.security.SecurityHelper;
     
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Set;
     
    import java.security.Principal;
     
    import javax.security.auth.Subject;
     
     
    /**
    * This class provides extremely simplified role based policies and access control.
    *
    */
    public class SecurityExampleHelper
        {
        // ----- static methods -------------------------------------------------
     
        /**
        * Login the user.
        *
        * @param sName  the user name
        *
        * @return the authenticated user
        */
        public static Subject login(String sName)
            {
            // For simplicity, just create a Subject. Normally, this would be
            // done using JAAS.
            String sUserDN       = "CN=" + sName + ",OU=Yoyodyne";
            Set setPrincipalUser = new HashSet();
     
            setPrincipalUser.add(new PofPrincipal(sUserDN));
            // Map the user to a role
            setPrincipalUser.add(new PofPrincipal((String) s_mapUserToRole.get(sName)));
     
            return new Subject(true, setPrincipalUser, new HashSet(), new HashSet());
            }
     
    
        /**
         * Assert that a Subject is associated with the calling thread with a
         * Principal representing the required role.
         *
         * @param sRoleRequired  the role required for the operation
         *
         * @throws SecurityException if a Subject is not associated with the
         *         calling thread or does not have the specified role Principal
         */
         public static void checkAccess(String sRoleRequired)
             {
             checkAccess(sRoleRequired, SecurityHelper.getCurrentSubject());
             }
     
         /**
         * Assert that a Subject contains a Principal representing the required
         * role.
         *
         * @param sRoleRequired  the role required for the operation
         *
         * @param subject        the Subject requesting access
         *
         * @throws SecurityException if a Subject is null or does not have the
         * specified role Principal
         */
         public static void checkAccess(String sRoleRequired, Subject subject)
             {
             if (subject == null)
                 {
                 throw new SecurityException("Access denied, authentication required");
                 }
     
             Map     mapRoleToId   = s_mapRoleToId;
             Integer nRoleRequired = (Integer) mapRoleToId.get(sRoleRequired);
     
             for (Iterator iter = subject.getPrincipals().iterator(); iter.hasNext();)
                 {
                 Principal principal = (Principal) iter.next();
                 String    sName     = principal.getName();
     
                 if (sName.startsWith("role_"))
                     {
                     Integer nRolePrincipal = (Integer) mapRoleToId.get(sName);
     
                     if (nRolePrincipal == null)
                         {
                         // invalid role
                         break;
                         }
     
                     if (nRolePrincipal.intValue() >= nRoleRequired.intValue())
                         {
                         return;
                         }
                     }
                 }
     
             throw new SecurityException("Access denied, insufficient privileges");
             }
     
     
         // ----- constants  -----------------------------------------------------
     
         public static final String ROLE_READER = "role_reader";
     
         public static final String ROLE_WRITER = "role_writer";
     
         public static final String ROLE_ADMIN  = "role_admin";
     
         /**
          * The cache name for security examples
          */
          public static final String SECURITY_CACHE_NAME = "security";
        
         /**
         * The name of the InvocationService used by security examples.
         */
         public static String INVOCATION_SERVICE_NAME = "ExtendTcpInvocationService";
     
     
         // ----- static data  ---------------------------------------------------
     
         /**
         * The map keyed by user name with the value being the user's role.
         * Represents which user is in which role.
         */
         private static Map s_mapUserToRole = new HashMap();
     
         /**
         * The map keyed by role name with the value the role id.
         * Represents the numeric role identifier.
         */
         private static Map s_mapRoleToId = new HashMap();
     
     
         // ----- static initializer ---------------------------------------------
     
         static
             {
             // User to role mapping
             s_mapUserToRole.put("BuckarooBanzai", ROLE_ADMIN);
             s_mapUserToRole.put("JohnWhorfin", ROLE_WRITER);
             s_mapUserToRole.put("JohnBigboote", ROLE_READER);
     
             // Role to Id mapping
             s_mapRoleToId.put(ROLE_ADMIN, Integer.valueOf(9));
             s_mapRoleToId.put(ROLE_WRITER, Integer.valueOf(2));
             s_mapRoleToId.put(ROLE_READER, Integer.valueOf(1));
             }
        }
    

10.2.2 Create an Identity Transformer

An identity transformer (com.tangosol.net.security.IdentityTransformer) is a client-side component that converts a subject or principal into an identity token. The token must be a type that Coherence can serialize. Coherence automatically serializes the token at run time and sends it as part of the connection request to the proxy.

To create an identity transformer implementation:

  1. Create a new Java class in the Security project named PasswordIdentityTransformer.

    See "Creating a Java Class" for detailed information.

  2. Import the IdentityTransformer interface. Ensure that the PasswordIdentityTransformer class implements the IdentityTransformer interface.

  3. Implement the transformIdentity method so that it performs the following tasks:

    • Tests whether the subject exists and is complete

    • Gets the principal names from the subject and saves them in a String array

    • Constructs the token, as a combination of the password plus the principal names, that can be serialized as POF types

Example 10-2 illustrates a possible implementation of the PasswordIdentityTransformer class.

Example 10-2 Sample Identity Transformer Implementation

package com.oracle.handson;
 
 
import com.tangosol.net.security.IdentityTransformer;
 
import java.security.Principal;
import java.util.Iterator;
import java.util.Set;
 
import javax.security.auth.Subject;
 
/**
* PasswordIdentityTransformer creates a security token that contains the
* required password and then adds a list of Principal names.
*
*/
public class PasswordIdentityTransformer
        implements IdentityTransformer
 
    {
    // ----- IdentityTransformer interface ----------------------------------
 
    /**
    * Transform a Subject to a token that asserts an identity.
    *
    * @param subject the Subject representing a user.
    *
    * @return the token that asserts identity.
    *
    * @throws SecurityException if the identity transformation fails.
    */
    public Object transformIdentity(Subject subject, Service service)
            throws SecurityException
        {
  // The service is not needed so the service argument is being ignored.
  // It could be used, for example, if there were different token types
  // required per service.

        if (subject == null)
            {
            throw new SecurityException("Incomplete Subject");
            }
 
        Set setPrincipals = subject.getPrincipals();
 
        if (setPrincipals.isEmpty())
            {
            throw new SecurityException("Incomplete Subject");
            }
 
        String[] asPrincipalName = new String[setPrincipals.size() + 1];
        int      i                = 0;
 
        asPrincipalName[i++] = System.getProperty("coherence.password",
                "secret-password");
 
        for (Iterator iter = setPrincipals.iterator(); iter.hasNext();)
            {
            asPrincipalName[i++] = ((Principal) iter.next()).getName();
            }
 
        // The token consists of the password plus the principal names as an
        // array of pof-able types, in this case strings.
        return asPrincipalName;
        }
    }

10.2.3 Create an Identity Asserter

An identity asserter (com.tangosol.net.security.IdentityAsserter) is a cluster-side component on the cache server that hosts an extend proxy service. The asserter validates that the token created by the identity transformer on the extend client contains the required credentials to access the cluster.

To create an identity asserter implementation:

  1. Create a new Java class in the Security project named PasswordIdentityAsserter.

    See "Creating a Java Class" for detailed information.

  2. Import the IdentityAsserter interface. Ensure that the PasswordIdentityAsserter class implements the IdentityAsserter interface.

  3. Implement the assertIdentity method such that it:

    • Validates that the token contains the correct password and that the password is the first name in the token

Example 10-3 illustrates a a possible implementation of the PasswordIdentityAsserter class.

Example 10-3 Sample Identity Asserter Implementation

package com.oracle.handson;
 
 
import com.tangosol.io.pof.PofPrincipal;
 
import com.tangosol.net.security.IdentityAsserter;
 
import java.util.HashSet;
import java.util.Set;
 
import javax.security.auth.Subject;
 
 
/**
* PasswordIdentityAsserter asserts that the security token contains the
* required password and then constructs a Subject based on a list of
* Principal names.
*
*/
public class PasswordIdentityAsserter
        implements IdentityAsserter
    {
    // ----- IdentityAsserter interface -------------------------------------
 
    /**
    * Asserts an identity based on a token-based identity assertion.
    *
    * @param oToken the token that asserts identity.
    *
    * @return a Subject representing the identity.
    *
    * @throws SecurityException if the identity assertion fails.
    */
    public Subject assertIdentity(Object oToken, Service service)
            throws SecurityException
        {
    // The service is not needed so the service argument is being ignored.
    // It could be used, for example, if there were different token types
    // required per service.
        if (oToken instanceof Object[])
            {
            String   sPassword        = System.getProperty(
                    "coherence.password", "secret-password");
            Set      setPrincipalUser = new HashSet();
            Object[] asName           = (Object[]) oToken;
 
            // first name must be password
            if (((String) asName[0]).equals(sPassword))
                {
                // prints the user name to server shell to ensure we are
                // communicating with it and to ensure user is validated
                System.out.println("Password validated for user: " + asName[1]);
                for (int i = 1, len = asName.length; i < len; i++)
                    {
                    setPrincipalUser.add(new PofPrincipal((String)asName[i]));
                    }
 
                return new Subject(true, setPrincipalUser, new HashSet(),
                        new HashSet());
                }
            }
        throw new SecurityException("Access denied");
        }
    }

10.2.4 Create the Password File

Create a Java file that requires a password to get a reference to a cache. Use the SecurityExampleHelper.login("BuckarooBanzai") to call the login method in the SecurityExampleHelper file to generate a token. At run time, the user name is associated with its subject defined in the SecurityExampleHelper class. A token is generated from this subject by the PasswordIdentityTransformer class and validated by the PasswordIdentityAsserter class as part of the connection request. If the validation succeeds, then a connection to the proxy and a reference to the cache is granted. Use the Subject.doas method to make the subject available in the security context.

  1. Create a new Java class with a main method in the Security project named PasswordExample.

    See "Creating a Java Class" for detailed information.

  2. Implement the main method to get a reference to the cache.

  3. Use the SecurityExampleHelper.login method to get a subject for user BuckarooBanzai

  4. Implement the doAs method to make the subject part of the Java security context. The subject will be available to any subsequent code. In this case, the doAs method is implemented to validate whether the user can access the cache based on its defined role.

Example 10-4 illustrates a possible implementation of PasswordExample.

Example 10-4 Sample Implementation to Run the Password Example

package com.oracle.handson;
 
import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
 
import java.io.IOException;
 
import java.security.PrivilegedExceptionAction;
 
import javax.security.auth.Subject;
 
 
/**
* This class shows how a Coherence Proxy can require a password to get a
* reference to a cache.
* <p>
* The PasswordIdentityTransformer will generate a security token that
* contains the password. The PasswordIdentityAsserter will validate the
* security token to enforce the password. The token generation and
* validation occurs automatically when a connection to the proxy is made.
*
*/
public class PasswordExample
    {
    // ----- static methods -------------------------------------------------
 
    /**
    * Get a reference to the cache. Password will be required.
    */
    public static void main (String[] args){
         getCache();  
           }
      public static void getCache()
        {
        System.out.println("------password example begins------");
 
        Subject subject = SecurityExampleHelper.login("BuckarooBanzai");
 
        try
            {
            NamedCache cache = (NamedCache) Subject.doAs(
                    subject, new PrivilegedExceptionAction()
                {
                public Object run()
                        throws Exception
                    {
                    NamedCache cache;
 
                    cache = CacheFactory.getCache(
                            SecurityExampleHelper.SECURITY_CACHE_NAME);
                    System.out.println("------password example succeeded------");
                    return cache;
                    }
                });
            }
        catch (Exception e)
            {
            // get exception if the password is invalid
            System.out.println("Unable to connect to proxy");
            e.printStackTrace();
            }
        System.out.println("------password example completed------");
        }
     
    }

10.2.5 Enable the Identity Transformer and Asserter

Configure an operational override file (tangosol-coherence-override.xml) to identify the classes that define the identity transformer (the class that transforms a Subject to a token on the extend client) and the identity asserter (the class that validates the token on the cluster).

  1. Open the tangosol-coherence-override.xml file from the Project Explorer window. You can find the file under Security/appClientModule.

  2. Use the identity-transformer and identity-asserter elements within the security-config stanza to identify the full path to the PasswordIdentityTransformer and PasswordIdentityAsserter implementation classes, respectively. Set the subject-scope parameter to true to associate the identity from the current security context with the cache and remote invocation service references that are returned to the client.

Example 10-5 illustrates a possible implementation of the tangosol-coherence-override.xml file.

Example 10-5 Specifying an Identity Transformer and an Asserter

<?xml version='1.0'?> 
<coherence  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
            xmlns="http://xmlns.oracle.com/coherence/coherence-operational-config"
            xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-operational-config coherence-operational-config.xsd"
            xml-override="/tangosol-coherence-override.xml">
  <security-config>
    <identity-transformer>
      <class-name>com.oracle.handson.PasswordIdentityTransformer</class-name>
    </identity-transformer>
    <identity-asserter>
      <class-name>com.oracle.handson.PasswordIdentityAsserter</class-name>
    </identity-asserter>
    <subject-scope>true</subject-scope>
  </security-config>
</coherence>

10.2.6 Create a Cache Configuration File for the Extend Client

The cache configuration file for the extend client routes cache operations to an extend proxy in the cluster. At run time, cache operations are not executed locally; instead, they are sent to the extend proxy service.

To create a cache configuration file for an extend client:

  1. Open the coherence-cache-config.xml file from the Project explorer window. You can find the file under Security/appClientModule.

  2. Save the file as client-cache-config.xml.

  3. Write the extend client cache configuration. The following list highlights some key elements:

    • Use the cache-name element to define security as the name of the cache. Note that there must be a cache defined in the cluster-side cache configuration that is also named security.

    • Use the remote-cache-scheme stanza to define the details about the remote cache.

    • Use the address and port elements in the tcp-initiator stanza to identify the extend proxy service that is listening on the localhost address at port 9099.

    • Use defaults and serializer with a value of pof to call the serializer for the custom POF configuration file (which you will create later in this chapter).

Example 10-6 illustrates a possible implementation for the client-cache-config.xml file.

Example 10-6 Sample Extend Client Cache Configuration File

<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">
  <defaults>
    <serializer>pof</serializer>
  </defaults>
 
  <caching-scheme-mapping>
    <cache-mapping>
      <cache-name>security</cache-name>
      <scheme-name>examples-remote</scheme-name>
    </cache-mapping>
  </caching-scheme-mapping>
 
  <caching-schemes>
    <remote-cache-scheme>
      <scheme-name>examples-remote</scheme-name>
      <service-name>ExtendTcpCacheService</service-name>
      <initiator-config>
        <tcp-initiator>
          <remote-addresses>
            <socket-address>
              <address system-property="tangosol.coherence.proxy.address">localhost</address>
              <port system-property="tangosol.coherence.proxy.port">9099</port>
            </socket-address>
          </remote-addresses>
        </tcp-initiator>
      </initiator-config>
    </remote-cache-scheme>
 
    <remote-invocation-scheme>
      <scheme-name>remote-invocation-scheme</scheme-name>
      <service-name>ExtendTcpInvocationService</service-name>
      <initiator-config>
        <tcp-initiator>
          <remote-addresses>
            <socket-address>
              <address system-property="tangosol.coherence.proxy.address">localhost</address>
              <port system-property="tangosol.coherence.proxy.port">9099</port>
            </socket-address>
          </remote-addresses>
          <connect-timeout>2s</connect-timeout>
        </tcp-initiator>
        <outgoing-message-handler>
          <request-timeout>5s</request-timeout>
        </outgoing-message-handler>
      </initiator-config>
    </remote-invocation-scheme>
  </caching-schemes>
</cache-config>

10.2.7 Create a Cache Configuration File for the Extend Proxy

To create a cache configuration file for the extend proxy service:

  1. Open the coherence-cache-config.xml file from the Project explorer window. You can find the file under Security/appClientModule.

  2. Save the file as examples-cache-config.xml.

  3. Configure the extend proxy cache configuration file. The following list highlights some key elements:

    • Use the cache-name element to define security as the name of the cache. Note that there must be a cache defined in the extend client cache configuration that is also named security.

    • Use the address and port elements in the acceptor-config stanza to identify the extend proxy service that is listening on the localhost address at port 9099

    • Use the autostart element with the tangosol.coherence.extend.enabled system property to prevent the cache server from running a proxy service.

    • Use defaults and serializer with a value of pof to call the serializer for the custom POF configuration file (which you will create later in this chapter)

Example 10-7 illustrates a possible implementation for the examples-cache-config.xml file.

Example 10-7 Sample Cache Configuration File for the Proxy Server

<?xml version="1.0"?>
 
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">
  <defaults>
    <serializer>pof</serializer>
  </defaults>
 
  <caching-scheme-mapping>
    <cache-mapping>
      <cache-name>security</cache-name>
      <scheme-name>ExamplesPartitionedPofScheme</scheme-name>
    </cache-mapping>
  </caching-scheme-mapping>
 
  <caching-schemes>
    <distributed-scheme>
      <scheme-name>ExamplesPartitionedPofScheme</scheme-name>
      <service-name>PartitionedPofCache</service-name>
      <backing-map-scheme>
        <local-scheme>
          <!-- each node will be limited to 32MB -->
          <high-units>32M</high-units>
          <unit-calculator>binary</unit-calculator>
        </local-scheme>
      </backing-map-scheme>
      <autostart>true</autostart>
    </distributed-scheme>
 
    <!--
    Proxy Service scheme that allows remote clients to connect to the
    cluster over TCP/IP.
    -->
    <proxy-scheme>
      <scheme-name>secure-proxy</scheme-name>
      <service-name>ProxyService</service-name>
 
      <thread-count system-property="tangosol.coherence.extend.threads">2</thread-count>
 
      <acceptor-config>
        <tcp-acceptor>
          <local-address>
            <address system-property="tangosol.coherence.extend.address">localhost</address>
            <port system-property="tangosol.coherence.extend.port">9099</port>
          </local-address>
        </tcp-acceptor>
      </acceptor-config>
 
       <autostart system-property="tangosol.coherence.extend.enabled">false</autostart>
    </proxy-scheme>
  </caching-schemes>
</cache-config>

10.2.8 Create a Start-Up Configuration for a Cache Server

Create a start-up configuration for a cache server cluster node. The configuration must include the system properties to designate a proxy service and the cluster-side cache configuration file. You must also include the application class files and the XML configuration files on the class path.

To create a start-up file for a cache server:

  1. Create a start-up configuration in Eclipse. Right-click the Security project and select Run As then Run Configurations. In the Name field, enter SecurityCacheServer.

  2. In the Main tab, click Browse in the Project field to select the Security project. Select the Include system libraries when searching for a main class and click the Search button in the Main class field. Enter DefaultCacheServer in the Select type field of the Select Main Type dialog box. Select DefaultCacheServer - com.tangosol.net and click OK. Click Apply.

  3. In the Coherence tab, enter the name and absolute path to the cluster-side cache configuration file (in this case, C:\home\oracle\workspace\Security\appClientModule\examples-cache-config.xml). Select Enabled (cache server) in the Local storage field. Enter a unique value (such as 3155) in the Cluster port field. Click Apply.

  4. In the Classpath tab, click User Entries then Add External JARs to add the coherence.jar file to the list. Click Apply. The Classpath tab should look similar to Figure 10-1.

    Figure 10-1 Class Path for the Security Cache Server

    Classpath for the Security Cache Server
    Description of "Figure 10-1 Class Path for the Security Cache Server"

    Note:

    Ensure that the XML configuration files (in the C:\home\oracle\workspace\Security and Security\build\classes folders) appear before the coherence.jar file on the class path. The classloader must encounter the custom POF configuration file (which must be named pof-config.xml and will be created later in this chapter) before it references the one in the coherence.jar file.

  5. In the Common tab, click Browse in the Shared file field to select the Security project. Click Apply.

10.2.9 Create a Start-Up Configuration for a Cache Server with a Proxy Service

Create a configuration to start an extend proxy service on a cache server in the cluster. The extend client connects to this service. The configuration must include the system properties to designate a proxy service and the cluster-side cache configuration file. You must also include the application class files and the XML configuration files on the class path.

For these examples, the cache server with proxy service start-up configuration will have the same configuration as the cache server start-up configuration, but it will include the system property to enable the extend proxy.

To create a start-up file for a cache server with a proxy service:

  1. Create a start-up configuration in Eclipse. Right-click the Security project and select Run As then Run Configurations. In the Name field, enter SecurityRunProxy.

  2. In the Main tab, click Browse in the Project field to select the Security project. Select the Include system libraries when searching for a main class and click the Search button in the Main class field. Enter DefaultCacheServer in the Select type field of the Select Main Type dialog box. Select DefaultCacheServer - com.tangosol.net and click OK. Click Apply.

  3. In the Coherence tab, enter the name and absolute path to the cluster-side cache configuration file (in this case, C:\home\oracle\workspace\Security\appClientModule\examples-cache-config.xml). Select Enabled (cache server) in the Local storage field. Enter a unique value (such as 3155) in the Cluster port field. Click Apply.

  4. In the Arguments tab, enter the system property -Dtangosol.coherence.extend.enabled=true to designate a proxy service in the VM arguments field.

    Figure 10-2 Arguments Tab for the Security Proxy Server

    Arguments Tab for the Security Proxy Server
    Description of "Figure 10-2 Arguments Tab for the Security Proxy Server"

  5. In the Classpath tab, click User Entries then Add External JARs to add the coherence.jar file to the list. Click Apply. The Classpath tab should look similar to Figure 10-3.

    Figure 10-3 Class Path for the Proxy Server

    Class Path for the Proxy Server
    Description of "Figure 10-3 Class Path for the Proxy Server"

    Note:

    Ensure that the XML configuration files (in the C:\home\oracle\workspace\Security and Security\build\classes folders) appear before the coherence.jar file on the class path. The classloader must encounter the custom POF configuration file (which must be named pof-config.xml and will be created later in this chapter) before it references the one in the coherence.jar file.

  6. In the Common tab, click Browse in the Shared file field to select the Security project. Click Apply.

10.2.10 Run the Password Example

Run the password example to generate and validate the token, and pass it to the proxy service.

  1. Create a run configuration for the PasswordExample.java file.

    1. Right click PasswordExample.java in the Project Explorer, and select Run As then Run Configurations.

    2. Click the New launch configuration icon. Ensure that PasswordExample appears in the Name field, Security appears in the Project field, and com.oracle.handson.PasswordExample appears in the Main class field. Click Apply.

    3. In the Coherence tab, enter the path to the client cache configuration file, C:\home\oracle\workspace\Security\appClientModule\client-cache-config.xml in the Cache configuration descriptor field. Select Disabled (cache client) in the Local cache field. Enter a unique value, such as 3155, in the Cluster port field.

    4. In the Classpath tab, click Add External JARs to add the coherence.jar file to User Entries. Use the Up and Down buttons to move the Security folder to the top of User Entries. When you are finished, the Classpath tab should look similar to Figure 10-4.

      Figure 10-4 Classpath Tab for the PasswordExample Program

      Classpath Tab for the PasswordExample Program
      Description of "Figure 10-4 Classpath Tab for the PasswordExample Program"

  2. Stop any running cache servers. See "Stopping Cache Servers" for more information.

  3. Run the security proxy server, the security cache server then the PasswordExample.java program.

    1. Right click the project and select Run As then Run Configurations. Run the SecurityRunProxy configuration from the Run Configurations dialog box.

    2. Right click the project and select Run As then Run Configurations. Run the SecurityCacheServer configuration from the Run Configurations dialog box.

    3. Right click the PasswordExample.java file in the Project Explorer and select Run As then Run Configurations. Select PasswordExample in the Run Configurations dialog box and click Run.

The output of the PasswordExample program should be similar to Example 10-8 in the Eclipse console. It indicates the start of the password example, the opening of the socket to the proxy server, and the completion of the example.

Example 10-8 Password Example Output in the Eclipse Console

------password example begins------
2011-03-15 11:37:48.484/0.296 Oracle Coherence 3.7.0.0 <Info> (thread=main, member=n/a): Loaded operational configuration from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/tangosol-coherence.xml"
2011-03-15 11:37:48.531/0.343 Oracle Coherence 3.7.0.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml"
2011-03-15 11:37:48.547/0.359 Oracle Coherence 3.7.0.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "file:/C:/home/oracle/workspace/Security/build/classes/tangosol-coherence-override.xml"
2011-03-15 11:37:48.547/0.359 Oracle Coherence 3.7.0.0 <D5> (thread=main, member=n/a): Optional configuration override "/custom-mbeans.xml" is not specified
 
Oracle Coherence Version 3.7.0.0 Build 22913
 Grid Edition: Development mode
Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
 
2011-03-15 11:37:48.719/0.531 Oracle Coherence GE 3.7.0.0 <Info> (thread=main, member=n/a): Loaded cache configuration from "file:/C:/home/oracle/workspace/Security/appClientModule/client-cache-config.xml"
2011-03-15 11:37:48.844/0.656 Oracle Coherence GE 3.7.0.0 <Info> (thread=ExtendTcpCacheService:TcpInitiator, member=n/a): Loaded POF configuration from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/pof-config.xml"
2011-03-15 11:37:48.875/0.687 Oracle Coherence GE 3.7.0.0  <Info> (thread=ExtendTcpCacheService:TcpInitiator, member=n/a): Loaded included POF configuration from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/coherence-pof-config.xml"
2011-03-15 11:37:48.922/0.734 Oracle Coherence GE 3.7.0.0 <D5> (thread=ExtendTcpCacheService:TcpInitiator, member=n/a): Started: TcpInitiator{Name=ExtendTcpCacheService:TcpInitiator, State=(SERVICE_STARTED), ThreadCount=0, Codec=Codec(Format=POF), Serializer=com.tangosol.io.pof.ConfigurablePofContext, PingInterval=0, PingTimeout=30000, RequestTimeout=30000, ConnectTimeout=30000, SocketProvider=SystemSocketProvider, RemoteAddresses=[/130.35.99.213:9099], SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}}
2011-03-15 11:37:48.922/0.734 Oracle Coherence GE 3.7.0.0 <D5> (thread=main, member=n/a): Connecting Socket to 130.35.99.213:9099
2011-03-15 11:37:48.938/0.750 Oracle Coherence GE 3.7.0.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.213:9099
------password example succeeded------
------password example completed------

The response in the proxy server shell should be similar to Example 10-9. It lists the CN and OU values from the distinguished name and whether the password was validated.

Example 10-9 Response from the Cache Server Running the Proxy Service Shell

...
Started DefaultCacheServer...
 
2011-03-15 11:37:33.797/21.250 Oracle Coherence GE 3.7.0.0 <D5> (thread=Cluster, member=1): Member(Id=2, Timestamp=2011-03-15 11:37:33.649, Address=130.35.99.213:8090, MachineId=49877, Location=site:URL, machine_name,process:4160, Role=CoherenceServer) joined Cluster with senior member 1
2011-03-15 11:37:33.844/21.297 Oracle Coherence GE 3.7.0.0 <D5> (thread=Cluster, member=1): Member 2 joined Service Management with senior member 1
2011-03-15 11:37:34.313/21.766 Oracle Coherence GE 3.7.0.0 <D5> (thread=Cluster, member=1): Member 2 joined Service PartitionedPofCache with senior member 1
2011-03-15 11:37:34.344/21.797 Oracle Coherence GE 3.7.0.0 <D5> (thread=DistributedCache:PartitionedPofCache, member=1): 3> Transferring primary PartitionSet{0, 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, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127} to member 2 requesting 128
2011-03-15 11:37:34.375/21.828 Oracle Coherence GE 3.7.0.0 <D4> (thread=DistributedCache:PartitionedPofCache, member=1): 1> Transferring 129 out of 129 partitions to a node-safe backup 1 at member 2 (under 129)
2011-03-15 11:37:34.406/21.859 Oracle Coherence GE 3.7.0.0 <D5> (thread=DistributedCache:PartitionedPofCache, member=1): Transferring 0KB of backup[1] for PartitionSet{128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256} to member 2
Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne
Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne
Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne

10.3 Including Role-Based Access Control to the Cluster

This section describes how to create an example that uses role-based policies to access the cluster. The code logs in to get a subject with a user ID assigned to a particular role. It gets a cache reference running in the context of the subject and then attempt various cache operations. Depending on the role granted to the user, the cache operation is allowed or denied. Note that the role mapping and role-based authorization in the example is simplified and not intended for real security use.

For example, a user with a writer role can use the put and get methods. A user with a reader role can use the get method, but not the put method. A user with a writer role cannot destroy a cache; however, a user with an admin role can.

Note that when the cache reference is created in the context of a subject that identity is permanently associated with that reference. Any use of that cache reference is on behalf of that identity.

The example will use the PasswordIdentityTransformer and PasswordIdentityAsserter classes that you created in the previous section. The PasswordIdentityTransformer class generates a security token that contains the password, the user ID, and the roles. The PasswordIdentityAsserter class (running in the proxy) validates the security token to enforce the password and construct a subject with the proper user ID and roles. The production and assertion of the security token happens automatically.

To create the example:

  1. Define Which User Roles Are Entitled to Access Cache Methods

  2. Apply the Entitlements to the Cache Service

  3. Create the Access Control Example Program

  4. Edit the Cluster-Side Cache Configuration File

  5. Run the Access Control Example

10.3.1 Define Which User Roles Are Entitled to Access Cache Methods

Create a Java file that enables access to cache methods on the basis of a user's role. To do this, you can apply access permissions to a wrapped NamedCache using the Subject object passed from the client by using Coherence*Extend. The implementation allows only clients with a specified role to access the wrapped NamedCache.

The class that you create in this section extends the com.tangosol.net.cache.WrapperNamedCache class. This class is a convenience function that enables you to secure the methods on the NamedCache interface.

To determine which user role can access a cache method, include a call to the SecurityExampleHelper.checkAccess method in each cache method's implementation. As the argument to checkAccess, provide the user role that is permitted to access the method. Close the implementation with a call to super.

For example, the following code indicates that users with the admin role can destroy the cache.

    public void destroy()
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_ADMIN);
        super.destroy();
        }

In this example, users with the reader role can call the aggregate method.

    public Object aggregate(Filter filter, EntryAggregator agent)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.aggregate(filter, agent);
        }

To create a file that determines which user role can call cache methods:

  1. Create a new Java class named EntitledNamedCache in the Security project.

    See "Creating a Java Class" if you need detailed information.

  2. Ensure that the class imports and extends WrapperNamedCache.

  3. Import the Filter, MapListener, ValueExtractor, Collection, Comparator, Map, and Set classes. The methods in WrapperNamedCache (and by extension, EntitledNamedCache) use arguments with these types.

  4. Implement the methods in EntitledNamedCache such that only a user with a specific role can call the method.

Example 10-10 illustrates a possible implementation of EntitledNamedCache.

Example 10-10 Entitled Named Cache

package com.oracle.handson;
 
import com.tangosol.net.NamedCache;
import com.tangosol.net.security.SecurityHelper;
 
import com.tangosol.net.cache.WrapperNamedCache;
 
import com.tangosol.util.Filter;
import com.tangosol.util.MapEvent;
import com.tangosol.util.MapListener;
import com.tangosol.util.ValueExtractor;
 
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
 
import javax.security.auth.Subject;
 
 
/**
* Example WrapperNamedCache that demonstrates how entitlements can be applied
* to a wrapped NamedCache using the Subject passed from the client through
* Coherence*Extend. This implementation only allows clients with a specified
* role to access the wrapped NamedCache.
*
*/
public class EntitledNamedCache
        extends WrapperNamedCache
    {
    /**
    * Create a new EntitledNamedCache.
    *
    * @param cache  the wrapped NamedCache
    */
    public EntitledNamedCache(NamedCache cache)
        {
        super(cache, cache.getCacheName());
        }
 
 
    // ----- NamedCache interface -------------------------------------------
 
    /**
    * {@inheritDoc}
    */
    public void release()
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        super.release();
        }
 
    /**
    * {@inheritDoc}
    */
    public void destroy()
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_ADMIN);
        super.destroy();
        }
 
    /**
    * {@inheritDoc}
    */
    public Object put(Object oKey, Object oValue, long cMillis)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        return super.put(oKey, oValue, cMillis);
        }
 
    /**
    * {@inheritDoc}
    */
    public void addMapListener(MapListener listener)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        super.addMapListener(new EntitledMapListener(listener));
        }
 
    /**
    * {@inheritDoc}
    */
    public void removeMapListener(MapListener listener)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        super.removeMapListener(listener);
        }
 
    /**
    * {@inheritDoc}
    */
    public void addMapListener(MapListener listener, Object oKey, boolean fLite)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        super.addMapListener(new EntitledMapListener(listener), oKey, fLite);
        }
 
    /**
    * {@inheritDoc}
    */
    public void removeMapListener(MapListener listener, Object oKey)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        super.removeMapListener(listener, oKey);
        }
 
    /**
    * {@inheritDoc}
    */
    public void addMapListener(MapListener listener, Filter filter, boolean fLite)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        super.addMapListener(new EntitledMapListener(listener), filter, fLite);
        }
 
    /**
    * {@inheritDoc}
    */
    public void removeMapListener(MapListener listener, Filter filter)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        super.removeMapListener(listener, filter);
        }
 
    /**
    * {@inheritDoc}
    */
    public int size()
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.size();
        }
 
    /**
    * {@inheritDoc}
    */
    public void clear()
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        super.clear();
        }
 
    /**
    * {@inheritDoc}
    */
    public boolean isEmpty()
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.isEmpty();
        }
 
    /**
    * {@inheritDoc}
    */
    public boolean containsKey(Object oKey)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.containsKey(oKey);
        }
 
    /**
    * {@inheritDoc}
    */
    public boolean containsValue(Object oValue)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.containsValue(oValue);
        }
 
    /**
    * {@inheritDoc}
    */
    public Collection values()
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.values();
        }
 
    /**
    * {@inheritDoc}
    */
    public void putAll(Map map)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        super.putAll(map);
        }
 
    /**
    * {@inheritDoc}
    */
    public Set entrySet()
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.entrySet();
        }
 
    /**
    * {@inheritDoc}
    */
    public Set keySet()
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.keySet();
        }
 
    /**
    * {@inheritDoc}
    */
    public Object get(Object oKey)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.get(oKey);
        }
 
    /**
    * {@inheritDoc}
    */
    public Object remove(Object oKey)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        return super.remove(oKey);
        }
 
    /**
    * {@inheritDoc}
    */
    public Object put(Object oKey, Object oValue)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        return super.put(oKey, oValue);
        }
 
    /**
    * {@inheritDoc}
    */
    public Map getAll(Collection colKeys)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.getAll(colKeys);
        }
 
    /**
    * {@inheritDoc}
    */
    public boolean lock(Object oKey, long cWait)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        return super.lock(oKey, cWait);
        }
 
    /**
    * {@inheritDoc}
    */
    public boolean lock(Object oKey)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        return super.lock(oKey);
        }
 
    /**
    * {@inheritDoc}
    */
    public boolean unlock(Object oKey)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        return super.unlock(oKey);
        }
 
    /**
    * {@inheritDoc}
    */
    public Set keySet(Filter filter)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.keySet(filter);
        }
 
    /**
    * {@inheritDoc}
    */
    public Set entrySet(Filter filter)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.entrySet(filter);
        }
 
    /**
    * {@inheritDoc}
    */
    public Set entrySet(Filter filter, Comparator comparator)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.entrySet(filter, comparator);
        }
 
    /**
    * {@inheritDoc}
    */
    public void addIndex(ValueExtractor extractor, boolean fOrdered, Comparator comparator)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        super.addIndex(extractor, fOrdered, comparator);
        }
 
    /**
    * {@inheritDoc}
    */
    public void removeIndex(ValueExtractor extractor)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        super.removeIndex(extractor);
        }
 
    /**
    * {@inheritDoc}
    */
    public Object invoke(Object oKey, EntryProcessor agent)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        return super.invoke(oKey, agent);
        }
 
    /**
    * {@inheritDoc}
    */
    public Map invokeAll(Collection collKeys, EntryProcessor agent)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        return super.invokeAll(collKeys, agent);
        }
 
    /**
    * {@inheritDoc}
    */
    public Map invokeAll(Filter filter, EntryProcessor agent)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        return super.invokeAll(filter, agent);
        }
 
    /**
    * {@inheritDoc}
    */
    public Object aggregate(Collection collKeys, EntryAggregator agent)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.aggregate(collKeys, agent);
        }
 
    /**
    * {@inheritDoc}
    */
    public Object aggregate(Filter filter, EntryAggregator agent)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return super.aggregate(filter, agent);
        }
 
    // ----- inner class ----------------------------------------------------
 
    /**
    * Example MapListener that adds authorization to map events.
    */
    public class EntitledMapListener
            implements MapListener
        {
        // ----- constructors -------------------------------------------
 
        /**
        * Construct an EntitledMapListener with the current subject.
        * The subject will not be available in the security context
        * when events are received by the proxy at runtime.
        *
        * @param  listener  the MapListener
        */
        public EntitledMapListener(MapListener listener)
            {
            m_listener = listener;
            m_subject  = SecurityHelper.getCurrentSubject();
            }
 
 
        // ----- MapListener interface ----------------------------------
 
        /**
        * {@inheritDoc}
        */
        public void entryInserted(MapEvent mapEvent)
            {
            try
                {
                SecurityExampleHelper.checkAccess(
                    SecurityExampleHelper.ROLE_WRITER, m_subject);
 
                }
            catch (SecurityException e)
                {
                System.out.println("Access denied for entryInserted");
                return;
                }
            m_listener.entryInserted(mapEvent);
            }
 
        /**
        * {@inheritDoc}
        */
        public void entryUpdated(MapEvent mapEvent)
            {
            try
                {
                SecurityExampleHelper.checkAccess(
                    SecurityExampleHelper.ROLE_WRITER, m_subject);
 
                }
            catch (SecurityException e)
                {
                System.out.println("Access denied for entryUpdated");
                return;
                }
            m_listener.entryUpdated(mapEvent);
            }
 
        /**
        * {@inheritDoc}
        */
        public void entryDeleted(MapEvent mapEvent)
            {
            try
                {
                SecurityExampleHelper.checkAccess(
                    SecurityExampleHelper.ROLE_WRITER, m_subject);
 
                }
            catch (SecurityException e)
                {
                System.out.println("Access denied for entryDeleted");
                return;
                }
            m_listener.entryDeleted(mapEvent);
            }
 
 
        //  ----- data members ------------------------------------------
 
        /**
        * Subject from security context when the MapListener was registered
        */
        private Subject m_subject;
 
        /**
        * Registered listener
        */
        private MapListener m_listener;
        }
 
    // ----- helper methods -------------------------------------------------
 
    /**
    * Return the wrapped NamedCache.
    *
    * @return  the wrapped CacheService
    */
    public NamedCache getNamedCache()
        {
        return (NamedCache) getMap();
        }
    }

10.3.2 Apply the Entitlements to the Cache Service

Create a file that demonstrates how access entitlements can be applied to a wrapped CacheService using the Subject passed from the client through Coherence*Extend. The implementation delegates access control for cache operations to the EntitledNamedCache you created in the previous section.

The class that you create extends the com.tangosol.net.WrapperCacheService class. This is a convenience function that allows you to secure the methods on the CacheService. It also provides a mechanism to delegate between the cache service on the proxy and the client request.

Implement the methods ensureCache, releaseCache, and destroyCache to ensure that only users with specific roles can use them. In the implementations, include a call to the SecurityExampleHelper.checkAccess method with a specific user role as its argument. For example, the following code ensures that only users with the role admin can destroy the cache.

    public void destroyCache(NamedCache map)
        {
        if (map instanceof EntitledNamedCache)
            {
            EntitledNamedCache cache =  (EntitledNamedCache) map;
            SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_ADMIN);
            map = cache.getNamedCache();
            }
        super.destroyCache(map);
        }

To create a file that applies entitlements to access the cache service:

  1. Create a new Java class named EntitledCacheService in the Security project.

  2. Ensure that the class imports and extends the WrapperCacheService class.

  3. Implement the ensureCache, releaseCache, and destroyCache methods to ensure that only users with specific roles can use them.

Example 10-11 illustrates a possible implementation of EntitledCacheService.

Example 10-11 Entitled Cache Service

package com.oracle.handson;
 
import com.tangosol.net.CacheService;
import com.tangosol.net.NamedCache;
import com.tangosol.net.WrapperCacheService;
 
/**
* Example WrapperCacheService that demonstrates how entitlements can be
* applied to a wrapped CacheService using the Subject passed from the
* client through Coherence*Extend. This implementation delegates access control
* for cache operations to the EntitledNamedCache.
*
*/
public class EntitledCacheService
        extends WrapperCacheService
    {
    /**
    * Create a new EntitledCacheService.
    *
    * @param service     the wrapped CacheService
    */
    public EntitledCacheService(CacheService service)
        {
        super(service);
        }
 
 
    // ----- CacheService interface -----------------------------------------
 
    /**
    * {@inheritDoc}
    */
    public NamedCache ensureCache(String sName, ClassLoader loader)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
        return new EntitledNamedCache(super.ensureCache(sName, loader));
        }
 
    /**
    * {@inheritDoc}
    */
    public void releaseCache(NamedCache map)
        {
        if (map instanceof EntitledNamedCache)
            {
            EntitledNamedCache cache =  (EntitledNamedCache) map;
            SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_READER);
            map = cache.getNamedCache();
            }
        super.releaseCache(map);
        }
 
    /**
    * {@inheritDoc}
    */
    public void destroyCache(NamedCache map)
        {
        if (map instanceof EntitledNamedCache)
            {
            EntitledNamedCache cache =  (EntitledNamedCache) map;
            SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_ADMIN);
            map = cache.getNamedCache();
            }
        super.destroyCache(map);
        }
    }

10.3.3 Create the Access Control Example Program

Create a file to run the access control example. The role policies are defined in the SecurityExampleHelper class. The EntitledCacheService and EntitledNamedCache classes enforce the policies.

The program should specify various users as arguments to the SecurityHelperFile.login method, and then attempt to perform cache read, write, and destroy operations. Based on the entitlement policies defined in the EntitledCacheService and EntitledNamedCache classes, the operations succeed or fail.

  1. Create a new Java class with a main method in the Security project named AccessControlExample.

    See "Creating a Java Class" for detailed information.

  2. Implement the main method to access the cache.

  3. Specify users defined in the SecurityExampleHelper file as arguments to its login method.

  4. For each user, execute read (get), write (put), and destroy operations on the cache and provide success or failure messages in response.

Example 10-12 illustrates a possible implementation of the AccessControlExample.java class.

Example 10-12 Sample Program to Run the Access Control Example

package com.oracle.handson;
 
import com.tangosol.net.CacheFactory;
import com.tangosol.net.InvocationService;
import com.tangosol.net.NamedCache;
 
import com.tangosol.util.MapEvent;
import com.tangosol.util.MapListener;
 
import java.security.PrivilegedExceptionAction;
 
import javax.security.auth.Subject;
 
/**
* This class demonstrates simplified role based access control.
* <p>
* The role policies are defined in SecurityExampleHelper. Enforcmenent
* is done by EntitledCacheService and EntitledNamedCache.
*
*/
public class AccessControlExample
    {
    // ----- static methods -------------------------------------------------
 
     public static void main (String[] args){
           accessCache();
             }
    /**
    * Demonstrate role based access to the cache.
    */
    public static void accessCache()
        {
        System.out.println("------cache access control example begins------");
 
        Subject subject = SecurityExampleHelper.login("JohnWhorfin");
 
        // Someone with writer role can write and read
        try
            {
            NamedCache cache = (NamedCache) Subject.doAs(
                    subject, new PrivilegedExceptionAction()
                {
                public Object run()
                        throws Exception
                    {
                    return CacheFactory.getCache(SecurityExampleHelper.SECURITY_CACHE_NAME);
                    }
                });
            cache.put("myKey", "myValue");
            cache.get("myKey");
            System.out.println("    Success: read and write allowed");
            }
        catch (Exception e)
            {
            // get exception if not allowed to perform the operation
            e.printStackTrace();
            }
 
        // Someone with reader role can read but not write
        subject = SecurityExampleHelper.login("JohnBigboote");
        try
            {
            NamedCache cache = (NamedCache) Subject.doAs(
                    subject, new PrivilegedExceptionAction()
                {
                public Object run()
                        throws Exception
                    {
                    return CacheFactory.getCache(SecurityExampleHelper.SECURITY_CACHE_NAME);
                    }
                });
            cache.get("myKey");
            System.out.println("    Success: read allowed");
            cache.put("anotherKey", "anotherValue");
            }
        catch (Exception e)
            {
            // get exception if not allowed to perform the operation
            System.out.println("    Success: Correctly cannot write");
            }
 
        // Someone with writer role cannot call destroy
        subject = SecurityExampleHelper.login("JohnWhorfin");
        try
            {
            NamedCache cache = (NamedCache) Subject.doAs(
                    subject, new PrivilegedExceptionAction()
                {
                public Object run()
                        throws Exception
                    {
                    return CacheFactory.getCache(SecurityExampleHelper.SECURITY_CACHE_NAME);
                    }
                });
            cache.destroy();
            }
        catch (Exception e)
            {
            // get exception if not allowed to perform the operation
            System.out.println("    Success: Correctly cannot " +
                    "destroy the cache");
            }
 
        // Someone with admin role can call destroy
        subject = SecurityExampleHelper.login("BuckarooBanzai");
        try
            {
            NamedCache cache = (NamedCache) Subject.doAs(
                    subject, new PrivilegedExceptionAction()
                {
                public Object run()
                        throws Exception
                    {
                    return CacheFactory.getCache(SecurityExampleHelper.SECURITY_CACHE_NAME);
                    }
                });
            cache.destroy();
            System.out.println("    Success: Correctly allowed to " +
                    "destroy the cache");
            }
        catch (Exception e)
            {
            // get exception if not allowed to perform the operation
            e.printStackTrace();
            }
        System.out.println("------cache access control example completed------");
        } 
    }

10.3.4 Edit the Cluster-Side Cache Configuration File

Edit the cluster-side cache configuration file examples-cache-config.xml. Specify the full path of the class name of the cache service in the cache-service-proxy stanza under proxy-config. The cache-service-proxy stanza contains the configuration information for a cache service proxy managed by a proxy service.

In this case, the cache service class name is com.oracle.handson.EntitledCacheService proxy and the param-type is com.tangosol.net.CacheService.

Example 10-13 illustrates the XML code to add to the configuration.

Example 10-13 Cache Service Proxy Configuration for a Cluster-Side Cache Configuration

...
      <proxy-config>
        <cache-service-proxy>
          <class-name>com.oracle.handson.EntitledCacheService</class-name>
          <init-params>
            <init-param>
              <param-type>com.tangosol.net.CacheService</param-type>
              <param-value>{service}</param-value>
            </init-param>
         </init-params>
        </cache-service-proxy>
      </proxy-config>
...

10.3.5 Run the Access Control Example

Run the access control example to demonstrate how access to the cache can be granted or denied, based on a user's role.

  1. Create a run configuration for the AccessControlExample.java.

    1. Right click AccessControlExample in the Project Explorer. Select Run As then Run Configurations.

    2. Click Oracle Coherence then New launch configuration icon. Ensure that AccessControlExample appears in the Name field, Security appears in the Project field, and com.oracle.handson.AccessControlExample appears in the Main class field. Click Apply.

    3. In the Coherence tab, enter the path to the client-cache-config.xml file in the Cache configuration descriptor field. Select Disabled (cache client) in the Local storage field.

    4. In the Classpath tab, click Add External JARs to add the coherence.jar file to User Entries. The Classpath tab should look similar to Figure 10-5. Click Apply then Close.

      Figure 10-5 Classpath Tab for the AccessControlExample Program

      Classpath Tab for the AccessControlExample Program
      Description of "Figure 10-5 Classpath Tab for the AccessControlExample Program"

  2. Stop any running cache servers. See "Stopping Cache Servers" for more information.

  3. Run the security proxy server, the security cache server then the AccessControlExample.java program.

    1. Right click the project and select Run As then Run Configurations. Run the SecurityRunProxy configuration from the Run Configurations dialog box.

    2. Right click the project and select Run As then Run Configurations. Run the SecurityCacheServer configuration from the Run Configurations dialog box.

    3. Right click the AccessControlExample.java file in the Project Explorer and select Run As then Run Configurations. Run the AccessControlExample configuration from the Run Configurations dialog box.

The output is similar to Example 10-14 in the Eclipse console. The messages correspond to the various users specified in AccessControlExample executing read, write, and destroy operations on the cache.

  • The Success: read and write allowed message corresponds to the user with role writer attempting to read from and write to the cache

  • The Success: read allowed message corresponds to the user with role reader attempting to read from the cache

  • The Success: Correctly cannot write message corresponds to the user with role reader attempting to write to the cache

  • The Success: Correctly cannot destroy the cache message corresponds to the user with role writer attempting to destroy the cache

  • The Success: Correctly allowed to destroy the cache message corresponds to the user with role admin attempting to destroy the cache

Example 10-14 Access Control Example Output in the Eclipse Console

------cache access control example begins------
2011-03-15 14:05:12.953/0.313 Oracle Coherence 3.7.0.0 <Info> (thread=main, member=n/a): Loaded operational configuration from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/tangosol-coherence.xml"
2011-03-15 14:05:13.000/0.360 Oracle Coherence 3.7.0.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml"
2011-03-15 14:05:13.000/0.360 Oracle Coherence 3.7.0.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "file:/C:/home/oracle/workspace/Security/appClientModule/tangosol-coherence-override.xml"
2011-03-15 14:05:13.000/0.360 Oracle Coherence 3.7.0.0 <D5> (thread=main, member=n/a): Optional configuration override "/custom-mbeans.xml" is not specified
 
Oracle Coherence Version 3.7.0.0 Build 22913
 Grid Edition: Development mode
Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
 
2011-03-15 14:05:13.156/0.516 Oracle Coherence GE 3.7.0.0 <Info> (thread=main, member=n/a): Loaded cache configuration from "file:/C:/home/oracle/workspace/Security/appClientModule/client-cache-config.xml"
2011-03-15 14:05:13.281/0.641 Oracle Coherence GE 3.7.0.0 <Info> (thread=ExtendTcpCacheService:TcpInitiator, member=n/a): Loaded POF configuration from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/pof-config.xml"
2011-03-15 14:05:13.312/0.672 Oracle Coherence GE 3.7.0.0 <Info> (thread=ExtendTcpCacheService:TcpInitiator, member=n/a): Loaded included POF configuration from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/coherence-pof-config.xml"
2011-03-15 14:05:13.359/0.719 Oracle Coherence GE 3.7.0.0 <D5> (thread=ExtendTcpCacheService:TcpInitiator, member=n/a): Started: TcpInitiator{Name=ExtendTcpCacheService:TcpInitiator, State=(SERVICE_STARTED), ThreadCount=0, Codec=Codec(Format=POF), Serializer=com.tangosol.io.pof.ConfigurablePofContext, PingInterval=0, PingTimeout=30000, RequestTimeout=30000, ConnectTimeout=30000, SocketProvider=SystemSocketProvider, RemoteAddresses=[/130.35.99.213:9099], SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}}
2011-03-15 14:05:13.375/0.735 Oracle Coherence GE 3.7.0.0 <D5> (thread=main, member=n/a): Connecting Socket to 130.35.99.213:9099
2011-03-15 14:05:13.375/0.735 Oracle Coherence GE 3.7.0.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.213:9099
    Success: read and write allowed
2011-03-15 14:05:13.484/0.844 Oracle Coherence GE 3.7.0.0 <D5> (thread=ExtendTcpCacheService:TcpInitiator, member=n/a): Started: TcpInitiator{Name=ExtendTcpCacheService:TcpInitiator, State=(SERVICE_STARTED), ThreadCount=0, Codec=Codec(Format=POF), Serializer=com.tangosol.io.pof.ConfigurablePofContext, PingInterval=0, PingTimeout=30000, RequestTimeout=30000, ConnectTimeout=30000, SocketProvider=SystemSocketProvider, RemoteAddresses=[/130.35.99.213:9099], SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}}
2011-03-15 14:05:13.484/0.844 Oracle Coherence GE 3.7.0.0 <D5> (thread=main, member=n/a): Connecting Socket to 130.35.99.213:9099
2011-03-15 14:05:13.484/0.844 Oracle Coherence GE 3.7.0.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.213:9099
    Success: read allowed
    Success: Correctly cannot write
    Success: Correctly cannot destroy the cache
2011-03-15 14:05:13.546/0.906 Oracle Coherence GE 3.7.0.0 <D5> (thread=ExtendTcpCacheService:TcpInitiator, member=n/a): Started: TcpInitiator{Name=ExtendTcpCacheService:TcpInitiator, State=(SERVICE_STARTED), ThreadCount=0, Codec=Codec(Format=POF), Serializer=com.tangosol.io.pof.ConfigurablePofContext, PingInterval=0, PingTimeout=30000, RequestTimeout=30000, ConnectTimeout=30000, SocketProvider=SystemSocketProvider, RemoteAddresses=[/130.35.99.213:9099], SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}}
2011-03-15 14:05:13.546/0.906 Oracle Coherence GE 3.7.0.0 <D5> (thread=main, member=n/a): Connecting Socket to 130.35.99.213:9099
2011-03-15 14:05:13.546/0.906 Oracle Coherence GE 3.7.0.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.213:9099
    Success: Correctly allowed to destroy the cache
------cache access control example completed------

Example 10-15 lists the output in the shell where the cache server is running the proxy service. Notice that the security exceptions in the output correspond to the Success: Correctly cannot write and Success: Correctly cannot destroy the cache messages in the Eclipse console.

Example 10-15 Output for the Cache Server Running the Proxy Service

Started DefaultCacheServer...
 
2011-03-15 14:04:53.296/20.781 Oracle Coherence GE 3.7.0.0 <D5> (thread=Cluster, member=1): Member(Id=2, Timestamp=2011-03-15 14:04:53.147, Address=130.35.99.213:8090, MachineId=49877, Location=site:URL, machine_name,process:3456, Role=CoherenceServer) joined Cluster with senior member 1
2011-03-15 14:04:53.359/20.844 Oracle Coherence GE 3.7.0.0 <D5> (thread=Cluster, member=1): Member 2 joined Service Management with senior member 1
2011-03-15 14:04:53.796/21.281 Oracle Coherence GE 3.7.0.0 <D5> (thread=Cluster, member=1): Member 2 joined Service PartitionedPofCache with senior member 1
2011-03-15 14:04:53.859/21.344 Oracle Coherence GE 3.7.0.0 <D5> (thread=DistributedCache:PartitionedPofCache, member=1): 3> Transferring primary PartitionSet{0, 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, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127} to member 2 requesting 128
2011-03-15 14:04:53.890/21.375 Oracle Coherence GE 3.7.0.0 <D4> (thread=DistributedCache:PartitionedPofCache, member=1): 1> Transferring 129 out of 129 partitions to a node-safe backup 1 at member 2 (under 129)
2011-03-15 14:04:53.906/21.391 Oracle Coherence GE 3.7.0.0 <D5> (thread=DistributedCache:PartitionedPofCache, member=1): Transferring 0KB of backup[1] for PartitionSet{128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256} to member 2
Password validated for user: role_writer
Password validated for user: role_writer
Password validated for user: role_writer
Password validated for user: role_reader
Password validated for user: role_reader
Password validated for user: role_reader
2011-03-15 14:05:13.500/40.985 Oracle Coherence GE 3.7.0.0 <D5> (thread=Proxy:ProxyService:TcpAcceptorWorker:1, member=1): An exception occurred while processing a PutRequest for Service=Proxy:ProxyService:TcpAcceptor: java.lang.SecurityException: Access denied, insufficient privileges
   at com.oracle.handson.SecurityExampleHelper.checkAccess(SecurityExampleHelper.java:104)
   at com.oracle.handson.SecurityExampleHelper.checkAccess(SecurityExampleHelper.java:58)
   at com.oracle.handson.EntitledNamedCache.put(EntitledNamedCache.java:69)
   at com.tangosol.coherence.component.net.extend.proxy.NamedCacheProxy.put$Router(NamedCacheProxy.CDB:1)
   at com.tangosol.coherence.component.net.extend.proxy.NamedCacheProxy.put(NamedCacheProxy.CDB:2)
   at com.tangosol.coherence.component.net.extend.messageFactory.NamedCacheFactory$PutRequest.onRun(NamedCacheFactory.CDB:6)
   at com.tangosol.coherence.component.net.extend.message.Request.run(Request.CDB:4)
   at com.tangosol.coherence.component.net.extend.proxy.NamedCacheProxy.onMessage(NamedCacheProxy.CDB:11)
   at com.tangosol.coherence.component.net.extend.Channel$MessageAction.run(Channel.CDB:13)
   at java.security.AccessController.doPrivileged(Native Method)
   at javax.security.auth.Subject.doAs(Subject.java:337)
   at com.tangosol.coherence.component.net.extend.Channel.execute(Channel.CDB:29)
   at com.tangosol.coherence.component.net.extend.Channel.receive(Channel.CDB:26)
   at com.tangosol.coherence.component.util.daemon.queueProcessor.service.Peer$DaemonPool$WrapperTask.run(Peer.CDB:9)
   at com.tangosol.coherence.component.util.DaemonPool$WrapperTask.run(DaemonPool.CDB:32)
   at com.tangosol.coherence.component.util.DaemonPool$Daemon.onNotify(DaemonPool.CDB:63)
   at com.tangosol.coherence.component.util.Daemon.run(Daemon.CDB:42)
   at java.lang.Thread.run(Thread.java:619)
 
2011-03-15 14:05:13.515/41.000 Oracle Coherence GE 3.7.0.0 <D5> (thread=Proxy:ProxyService:TcpAcceptorWorker:0, member=1): An exception occurred while processing a DestroyCacheRequest for Service=Proxy:ProxyService:TcpAcceptor: java.lang.SecurityException: Access denied, insufficient privileges
   at com.oracle.handson.SecurityExampleHelper.checkAccess(SecurityExampleHelper.java:104)
   at com.oracle.handson.SecurityExampleHelper.checkAccess(SecurityExampleHelper.java:58)
   at com.oracle.handson.EntitledCacheService.destroyCache(EntitledCacheService.java:63)
   at com.tangosol.coherence.component.net.extend.messageFactory.CacheServiceFactory$DestroyCacheRequest.onRun(CacheServiceFactory.CDB:6)
   at com.tangosol.coherence.component.net.extend.message.Request.run(Request.CDB:4)
   at com.tangosol.coherence.component.net.extend.proxy.serviceProxy.CacheServiceProxy.onMessage(CacheServiceProxy.CDB:9)
   at com.tangosol.coherence.component.net.extend.Channel$MessageAction.run(Channel.CDB:13)
   at java.security.AccessController.doPrivileged(Native Method)
   at javax.security.auth.Subject.doAs(Subject.java:337)
   at com.tangosol.coherence.component.net.extend.Channel.execute(Channel.CDB:29)
   at com.tangosol.coherence.component.net.extend.Channel.receive(Channel.CDB:26)
   at com.tangosol.coherence.component.util.daemon.queueProcessor.service.Peer$DaemonPool$WrapperTask.run(Peer.CDB:9)
   at com.tangosol.coherence.component.util.DaemonPool$WrapperTask.run(DaemonPool.CDB:32)
   at com.tangosol.coherence.component.util.DaemonPool$Daemon.onNotify(DaemonPool.CDB:63)
   at com.tangosol.coherence.component.util.Daemon.run(Daemon.CDB:42)
   at java.lang.Thread.run(Thread.java:619)
 
Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne
Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne
Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne

10.4 Including Role-Based Access Control to an Invocable Object

An invocation service cluster service enables extend clients to execute invocable objects on the cluster. This example demonstrates how you can use role-based policies to determine which users can run the invocable objects.

For example, a user with a writer role can run an invocable object. A user with a reader role cannot.

In this example, you create a simple invocable object that can be called from a client program. Since the invocable object will be serializable, you must also list it in a POF configuration file. You also create an invocation service program that tests whether a user can execute methods on the service based on the user's role.

As in the previous example, this example uses the PasswordIdentityTransformer class to generate a security token that contains the password, the user ID, and the roles. The PasswordIdentityAsserter (running in the proxy) will be used to validate the security token to enforce the password and construct a subject with the proper user ID and roles. The production and assertion of the security token happens automatically.

To create the example:

  1. Create an Invocable Object

  2. Create an Entitled Invocation Service

  3. Create the Access Invocation Service Example Program

  4. Edit the Cluster-Side Cache Configuration File

  5. Create a POF Configuration File

  6. Edit the Run Configurations for the Servers

  7. Run the Access Invocation Service Example

10.4.1 Create an Invocable Object

Create an implementation of a simple invocable object that will be used by an entitled invocation service. For example, the invocable object can be written to increment and return an integer.

To create an invocable object:

  1. Create a new Java class named ExampleInvocable in the Security project.

    See "Creating a Java Class" for detailed information.

  2. Import the Invocable and InvocationService interfaces. Because this class will be working with serializable objects, import the PortableObject, PofReader and PofWriter classes.

  3. Ensure that the ExampleInvocable class implements Invocable and PortableObject.

  4. Implement the ExampleInvocable class to increment an integer and return the result.

  5. Implement the PofReader.readExternal and PofWriter.writeExternal methods.

Example 10-16 illustrates a possible implementation of ExampleInvocable.java.

Example 10-16 A Sample Invocable Object

package com.oracle.handson;
 
 
import com.tangosol.io.pof.PortableObject;
import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
 
import com.tangosol.net.Invocable;
import com.tangosol.net.InvocationService;
 
import java.io.IOException;
 
 
/**
 * Invocable implementation that increments and returns a given integer.
 */
public class ExampleInvocable
        implements Invocable, PortableObject
    {
    // ----- constructors ---------------------------------------------
 
    /**
     * Default constructor.
     */
    public ExampleInvocable()
        {
        }
 
 
    // ----- Invocable interface --------------------------------------
 
    /**
     * {@inheritDoc}
     */
    public void init(InvocationService service)
        {
        m_service = service;
        }
 
    /**
     * {@inheritDoc}
     */
    public void run()
        {
        if (m_service != null)
            {
            m_nValue++;
            }
        }
 
    /**
     * {@inheritDoc}
     */
    public Object getResult()
        {
        return new Integer(m_nValue);
        }
 
 
    // ----- PortableObject interface ---------------------------------
 
    /**
     * {@inheritDoc}
     */
    public void readExternal(PofReader in)
            throws IOException
        {
        m_nValue = in.readInt(0);
        }
 
    /**
     * {@inheritDoc}
     */
    public void writeExternal(PofWriter out)
            throws IOException
        {
        out.writeInt(0, m_nValue);
        }
 
 
    // ----- data members ---------------------------------------------
 
    /**
     * The integer value to increment.
     */
    private int m_nValue;
 
    /**
     * The InvocationService that is executing this Invocable.
     */
    private transient InvocationService m_service;
    }

10.4.2 Create an Entitled Invocation Service

This example shows how a remote invocation service can be wrapped to provide access control. Access entitlements can be applied to a wrapped InvocationService using the Subject passed from the client by using Coherence*Extend. This implementation enables only clients with a specified role to access the wrapped invocation service.

The class that you create should extend the com.tangosol.net.WrapperInvocationService class. This is a convenience function that enables you to secure the methods on InvocationService. It also provides a mechanism to delegate between the invocation service on the proxy and the client request.

To create an entitled invocation service:

  1. Create a new Java class named EntitledInvocationService in the Security project.

  2. Import the Invocable, InvocationObserver, InvocationService, WrapperInvocationService, Map, and Set interfaces. Ensure that the EntitledInvocationService class extends the WrapperInvocationService class.

  3. Implement the query and execute methods. In the implementations, include a call to the SecurityExampleHelper.checkAccess method to determine whether a specified user role, in this case, ROLE_WRITER, can access these operations.

Example 10-17 illustrates a possible implementation of EntitledInvocationService.java.

Example 10-17 A Sample Entitled Invocation Service

package com.oracle.handson;
 
 
import com.tangosol.net.Invocable;
import com.tangosol.net.InvocationObserver;
import com.tangosol.net.InvocationService;
import com.tangosol.net.WrapperInvocationService;
 
import java.util.Map;
import java.util.Set;
 
 
/**
* Example WrapperInvocationService that demonstrates how entitlements can be
* applied to a wrapped InvocationService using the Subject passed from the
* client through Coherence*Extend. This implementation only allows clients with a
* specified role to access the wrapped InvocationService.
*
*/
public class EntitledInvocationService
        extends WrapperInvocationService
    {
    /**
    * Create a new EntitledInvocationService.
    *
    * @param service  the wrapped InvocationService
    */
    public EntitledInvocationService(InvocationService service)
        {
        super(service);
        }
 
 
    // ----- InvocationService interface ------------------------------------
 
    /**
    * {@inheritDoc}
    */
    public void execute(Invocable task, Set setMembers, InvocationObserver observer)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        super.execute(task, setMembers, observer);
        }
 
    /**
    * {@inheritDoc}
    */
    public Map query(Invocable task, Set setMembers)
        {
        SecurityExampleHelper.checkAccess(SecurityExampleHelper.ROLE_WRITER);
        return super.query(task, setMembers);
        }
    }

10.4.3 Create the Access Invocation Service Example Program

Create a program to run the access invocation service example. The objective of the program is to test whether various users defined in the SecurityExampleHelper class are able to access and run an invocable object. The enforcement of the role-based policies is provided by the EntitledInvocationService class.

To create a program to run the Access Invocation Service example:

  1. Create a Java class with a main method in the Security project named AccessInvocationServiceExample.java.

    See "Creating a Java Class" for detailed information.

  2. Among other classes, import ExampleInvocable, CacheFactory, and InvocationService.

  3. Implement the main method to invoke the accessInvocationService method.

  4. Implement the accessInvocationService class so that various users defined in the SecurityExampleHelper class attempt to log in to the service and run the object defined in ExampleInvocable. Use the SecurityExampleHelper.login method to test whether various users can access the invocable service.

Example 10-18 illustrates a possible implementation of AccessInvocationServiceExample.java.

Example 10-18 Sample Program to Run the Access Invocation Service Example

package com.oracle.handson;
 
import com.oracle.handson.ExampleInvocable;
 
import com.tangosol.net.CacheFactory;
import com.tangosol.net.InvocationService;
 
import java.security.PrivilegedExceptionAction;
 
import javax.security.auth.Subject;
 
 
/**
* This class demonstrates simplified role based access control for the
* invocation service.
* <p>
* The role policies are defined in SecurityExampleHelper. Enforcmenent
* is done by EntitledInvocationService.
*
*/
public class AccessInvocationServiceExample
    {
    /**
    * Invoke the example
    *
    * @param asArg  command line arguments (ignored in this example)
    */
    public static void main(String[] asArg)
        {
        accessInvocationService();
        }
 
    /**
    * Access the invocation service
    */
    public static void accessInvocationService()
        {
        System.out.println("------InvocationService access control example " +
                "begins------");
 
        // Someone with writer role can run invocables
        Subject subject = SecurityExampleHelper.login("JohnWhorfin");
 
        try
            {
            InvocationService service = (InvocationService) Subject.doAs(
                    subject, new PrivilegedExceptionAction()
                {
                public Object run()
                    {
                    return CacheFactory.getService(
                            SecurityExampleHelper.INVOCATION_SERVICE_NAME);
                    }
                });
             service.query(new ExampleInvocable(), null);
             System.out.println("    Success: Correctly allowed to " +
                    "use the invocation service");
            }
        catch (Exception e)
            {
            // get exception if not allowed to perform the operation
            e.printStackTrace();
            }
 
        // Someone with reader role cannot cannot run invocables
        subject = SecurityExampleHelper.login("JohnBigboote");
        try
            {
            InvocationService service = (InvocationService) Subject.doAs(
                    subject, new PrivilegedExceptionAction()
                {
                public Object run()
                    {
                    return CacheFactory.getService(
                            SecurityExampleHelper.INVOCATION_SERVICE_NAME);
                    }
                });
            service.query(new ExampleInvocable(), null);
            }
        catch (Exception ee)
            {
            System.out.println("    Success: Correctly unable to " +
                    "use the invocation service");
            }
        System.out.println("------InvocationService access control example " +
                "completed------");
        }
    }

10.4.4 Edit the Cluster-Side Cache Configuration File

Edit the examples-cache-config.xml file to add the full path to the invocation service to the invocation-service-proxy stanza under the proxy-config. The invocation-service-proxy stanza contains the configuration information for an invocation service proxy managed by a proxy service.

In this case, the invocation service class name is com.oracle.handson.EntitledInvocationService and its param-type is com.tangosol.net.InvocationService.

Example 10-19 Invocation Service Proxy Configuration for a Cluster-Side Cache

   ...
      <proxy-config>
       ...
        <invocation-service-proxy>
          <class-name>com.oracle.handson.EntitledInvocationService</class-name>
          <init-params>
            <init-param>
              <param-type>com.tangosol.net.InvocationService</param-type>
              <param-value>{service}</param-value>
            </init-param>
          </init-params>
        </invocation-service-proxy>
      </proxy-config>
   ...

10.4.5 Create a POF Configuration File

Create a POF configuration file to declare ExampleInvocable as a user type.

  1. Locate the pof-config.xml file under Security\appClientModule in the Project Explorer and open it in the Eclipse IDE.

  2. Enter the code to declare ExampleInvocable as a user type and save the file.

    The contents of the file should look similar to Example 10-20. The file will be saved to the C:\home\oracle\workspace\Security\appClientModule folder.

Example 10-20 POF Configuration File with ExampleInvocable User Type

<?xml version="1.0"?>
<pof-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-pof-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-pof-config coherence-pof-config.xsd">
  <user-type-list>
 
    <!-- coherence POF user types -->
    <include>coherence-pof-config.xml</include>
 
    <!-- com.tangosol.examples package -->
    <user-type>
      <type-id>1007</type-id>
      <class-name>com.oracle.handson.ExampleInvocable</class-name>
    </user-type>
 
  </user-type-list>
 
  <allow-interfaces>true</allow-interfaces>
  <allow-subclasses>true</allow-subclasses>
</pof-config>

10.4.6 Edit the Run Configurations for the Servers

The classloader must encounter the custom POF configuration file (which must be named pof-config.xml) before it references the one in the coherence.jar file. If it does not, then the custom POF configuration file will be ignored and the default file in the coherence.jar file will be used instead.

To ensure that the XML configuration files in the C:\home\oracle\workspace\Security\appModule are used, delete the Coherence37 library from the Bootstrap Entries section of the servers' class path. Also, position the coherence.jar file after the Security folder in the User Entries section.

  1. Right click the project in the Project Explorer and select Run As then Run Configurations.

  2. Select SecurityRunProxy. In the Classpath tab, remove Coherence37 from the list of Bootstrap Entries. Click Apply.

  3. Select SecurityCacheServer. In the Classpath tab, remove Coherence37 from the list of Bootstrap Entries. Click Apply.

When you are finished, the Classpath tab for SecurityRunProxy and SecurityCacheServer should look similar to Figure 10-6.

Figure 10-6 Class Path for the Cache Server and the Server Running the Proxy Service

Classpath for the Servers
Description of "Figure 10-6 Class Path for the Cache Server and the Server Running the Proxy Service"

10.4.7 Run the Access Invocation Service Example

Run the access invocation service example to demonstrate how access to the invocable object can be granted or denied, based on a user's role.

  1. Create a run configuration for the AccessInvocationServiceExample.java file.

    1. Right click AccessInvocationServiceExample.java in the Project Explorer and select Run As then Run Configurations.

    2. Click Oracle Coherence, then the New launch configuration icon. Ensure that AccessInvocationServiceExample appears in the Name field, Security appears in the Project field, and com.oracle.handson.AccessInvocationServiceExample.java appears in the Main class field. Click Apply.

    3. In the Coherence tab, enter the path to the client-cache-config.xml file in the Cache configuration descriptor field. Select Disabled (cache client) in the Local storage field.

    4. In the Classpath tab, remove Coherence37 from the Bootstrap Entries list. Click Add External JARs to add the coherence.jar file to User Entries. Move the Security folder to the top of User Entries followed by the coherence.jar file. When you are finished, the Classpath tab should look similar to Figure 10-7. Click Apply then Close.

      Figure 10-7 Classpath Tab for the AccessInvocationServiceExample Program

      Classpath Tab for the AccessInvocationServiceExample
      Description of "Figure 10-7 Classpath Tab for the AccessInvocationServiceExample Program"

  2. Stop any running cache servers. See "Stopping Cache Servers" for more information.

  3. Run the security proxy server, the security cache server then the AccessInvocationServiceExample.java program.

    1. Right click the project and select Run As then Run Configurations. Run the SecurityRunProxy configuration from the Run Configurations dialog box.

    2. Right click the project and select Run As then Run Configurations. Run the SecurityCacheServer configuration from the Run Configurations dialog box.

    3. Right click the AccessInvocationServiceExample.java file in the Project Explorer and select Run As then Run Configurations. Select AccessControlExample in the Run Configurations dialog box and click Run.

The output is similar to Example 10-21 in the Eclipse console. The messages correspond to the users specified in the AccessInvocationServiceExample class that are trying to run the invocable object ExampleInvocable.

  • The message Success: Correctly allowed to use the invocation service corresponds to the user with role writer attempting to run ExampleInvocable.

  • The message Success: Correctly unable to use the invocation service corresponds to the user with role reader attempting to run ExampleInvocable.

Example 10-21 Client Program Response in the Eclipse Console

------InvocationService access control example begins------
2011-03-15 17:54:40.468/0.453 Oracle Coherence 3.7.0.0 <Info> (thread=main, member=n/a): Loaded operational configuration from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/tangosol-coherence.xml"
2011-03-15 17:54:40.515/0.500 Oracle Coherence 3.7.0.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml"
2011-03-15 17:54:40.515/0.500 Oracle Coherence 3.7.0.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "file:/C:/home/oracle/workspace/Security/appClientModule/tangosol-coherence-override.xml"
2011-03-15 17:54:40.515/0.500 Oracle Coherence 3.7.0.0 <D5> (thread=main, member=n/a): Optional configuration override "/custom-mbeans.xml" is not specified
 
Oracle Coherence Version 3.7.0.0 Build 22913
 Grid Edition: Development mode
Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
 
2011-03-15 17:54:40.750/0.735 Oracle Coherence GE 3.7.0.0 <Info> (thread=main, member=n/a): Loaded cache configuration from "file:/C:/home/oracle/workspace/Security/appClientModule/client-cache-config.xml"
2011-03-15 17:54:40.968/0.953 Oracle Coherence GE 3.7.0.0 <Info> (thread=ExtendTcpInvocationService:TcpInitiator, member=n/a): Loaded POF configuration from "file:/C:/home/oracle/workspace/Security/appClientModule/pof-config.xml"
2011-03-15 17:54:40.984/0.969 Oracle Coherence GE 3.7.0.0 <Info> (thread=ExtendTcpInvocationService:TcpInitiator, member=n/a): Loaded included POF configuration from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/coherence-pof-config.xml"
2011-03-15 17:54:41.062/1.047 Oracle Coherence GE 3.7.0.0 <D5> (thread=ExtendTcpInvocationService:TcpInitiator, member=n/a): Started: TcpInitiator{Name=ExtendTcpInvocationService:TcpInitiator, State=(SERVICE_STARTED), ThreadCount=0, Codec=Codec(Format=POF), Serializer=com.tangosol.io.pof.ConfigurablePofContext, PingInterval=0, PingTimeout=5000, RequestTimeout=5000, ConnectTimeout=2000, SocketProvider=SystemSocketProvider, RemoteAddresses=[/130.35.99.213:9099], SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}}
2011-03-15 17:54:41.078/1.063 Oracle Coherence GE 3.7.0.0 <D5> (thread=main, member=n/a): Connecting Socket to 130.35.99.213:9099
2011-03-15 17:54:41.078/1.063 Oracle Coherence GE 3.7.0.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.213:9099
    Success: Correctly allowed to use the invocation service
2011-03-15 17:54:41.140/1.125 Oracle Coherence GE 3.7.0.0 <D5> (thread=ExtendTcpInvocationService:TcpInitiator, member=n/a): Started: TcpInitiator{Name=ExtendTcpInvocationService:TcpInitiator, State=(SERVICE_STARTED), ThreadCount=0, Codec=Codec(Format=POF), Serializer=com.tangosol.io.pof.ConfigurablePofContext, PingInterval=0, PingTimeout=5000, RequestTimeout=5000, ConnectTimeout=2000, SocketProvider=SystemSocketProvider, RemoteAddresses=[/130.35.99.213:9099], SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}}
2011-03-15 17:54:41.156/1.141 Oracle Coherence GE 3.7.0.0 <D5> (thread=main, member=n/a): Connecting Socket to 130.35.99.213:9099
2011-03-15 17:54:41.156/1.141 Oracle Coherence GE 3.7.0.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.213:9099
    Success: Correctly unable to use the invocation service
------InvocationService access control example completed------

Example 10-22 lists the output in the shell where the cache server is running the proxy service. Notice that the security exception in the output corresponds to the Success: Correctly unable to use the invocation service message in the Eclipse console, where the user with role reader attempts to run ExampleInvocable.

Example 10-22 Proxy Service Response in the Eclipse Console

...
Started DefaultCacheServer...
 
2011-03-15 17:54:08.375/37.750 Oracle Coherence GE 3.7.0.0 <D5> (thread=Cluster, member=1): Member(Id=2, Timestamp=2011-03-15 17:54:08.226, Address=130.35.99.213:8090, MachineId=49877, Location=site:URL, machine_name,process:5340, Role=CoherenceServer) joined Cluster with senior member 1
2011-03-15 17:54:08.437/37.812 Oracle Coherence GE 3.7.0.0 <D5> (thread=Cluster, member=1): Member 2 joined Service Management with senior member 1
2011-03-15 17:54:08.890/38.265 Oracle Coherence GE 3.7.0.0 <D5> (thread=Cluster, member=1): Member 2 joined Service PartitionedPofCache with senior member 1
2011-03-15 17:54:08.921/38.296 Oracle Coherence GE 3.7.0.0 <D5> (thread=DistributedCache:PartitionedPofCache, member=1): 3> Transferring primary PartitionSet{0, 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, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127} to member 2 requesting 128
2011-03-15 17:54:08.968/38.343 Oracle Coherence GE 3.7.0.0 <D4> (thread=DistributedCache:PartitionedPofCache, member=1): 1> Transferring 129 out of 129 partitions to a node-safe backup 1 at member 2 (under 129)
2011-03-15 17:54:09.000/38.375 Oracle Coherence GE 3.7.0.0 <D5> (thread=DistributedCache:PartitionedPofCache, member=1): Transferring 0KB of backup[1] for PartitionSet{128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256} to member 2
Password validated for user: role_writer
Password validated for user: role_writer
Password validated for user: role_reader
Password validated for user: role_reader
2011-03-15 17:54:41.156/70.531 Oracle Coherence GE 3.7.0.0 <D5> (thread=Proxy:ProxyService:TcpAcceptorWorker:1, member=1): An exception occurred while processing a InvocationRequest for Service=Proxy:ProxyService:TcpAcceptor: java.lang.SecurityException: Access denied, insufficient privileges
   at com.oracle.handson.SecurityExampleHelper.checkAccess(SecurityExampleHelper.java:104)
   at com.oracle.handson.SecurityExampleHelper.checkAccess(SecurityExampleHelper.java:58)
   at com.oracle.handson.EntitledInvocationService.query(EntitledInvocationService.java:50)
   at com.tangosol.coherence.component.net.extend.messageFactory.InvocationServiceFactory$InvocationRequest.onRun(InvocationServiceFactory.CDB:12)
   at com.tangosol.coherence.component.net.extend.message.Request.run(Request.CDB:4)
   at com.tangosol.coherence.component.net.extend.proxy.serviceProxy.InvocationServiceProxy.onMessage(InvocationServiceProxy.CDB:9)
   at com.tangosol.coherence.component.net.extend.Channel$MessageAction.run(Channel.CDB:13)
   at java.security.AccessController.doPrivileged(Native Method)
   at javax.security.auth.Subject.doAs(Subject.java:337)
   at com.tangosol.coherence.component.net.extend.Channel.execute(Channel.CDB:29)
   at com.tangosol.coherence.component.net.extend.Channel.receive(Channel.CDB:26)
   at com.tangosol.coherence.component.util.daemon.queueProcessor.service.Peer$DaemonPool$WrapperTask.run(Peer.CDB:9)
   at com.tangosol.coherence.component.util.DaemonPool$WrapperTask.run(DaemonPool.CDB:32)
   at com.tangosol.coherence.component.util.DaemonPool$Daemon.onNotify(DaemonPool.CDB:63)
   at com.tangosol.coherence.component.util.Daemon.run(Daemon.CDB:42)
   at java.lang.Thread.run(Thread.java:619)