This exercise describes how to apply security to a Oracle Coherence*Extend client and highlights the use of the Coherence SecurityHelper
, PofPrincipal
, IdentityTransformer
, IdentityAsserter
, and WrapperCacheService
APIs.
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:
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 Oracle Fusion Middleware Securing Oracle Coherence.
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:
Client application files, which describe the functionality that will access the cluster
Cache configuration files, where the extend client and the extend proxy each have their own cache configuration
Operational override file, which overrides the operational and run-time settings in the default operational deployment descriptor
Server start-up files, where there is a start-up file for the extend proxy and for the cache server in the cluster
POF configuration deployment descriptor, which specifies custom data types when using POF to serialize objects
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.
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:
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 Coherence12.1.2.
See "Creating a New Project in the Eclipse IDE" for detailed information on creating a project.
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.
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)); } }
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:
Create a new Java class in the Security
project named PasswordIdentityTransformer
.
See "Creating a Java Class" for detailed information.
Import the IdentityTransformer
interface. Ensure that the PasswordIdentityTransformer
class implements the IdentityTransformer
interface.
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; import com.tangosol.net.Service; /** * 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; } }
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:
Create a new Java class in the Security
project named PasswordIdentityAsserter
.
See "Creating a Java Class" for detailed information.
Import the IdentityAsserter
interface. Ensure that the PasswordIdentityAsserter
class implements the IdentityAsserter
interface.
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; import com.tangosol.net.Service; /** * 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"); } }
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.
Create a new Java class with a main
method in the Security
project named PasswordExample
.
See "Creating a Java Class" for detailed information.
Implement the main
method to get a reference to the cache.
Use the SecurityExampleHelper.login
method to get a subject for user BuckarooBanzai
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; import com.tangosol.net.Service; /** * 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------"); } }
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).
Open the tangosol-coherence-override.xml
file from the Project Explorer window. You can find the file under Security/appClientModule
.
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" encoding="UTF-8"?> <coherence xmlns="http://xmlns.oracle.com/coherence/coherence-operational-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-operational-config http://xmlns.oracle.com/coherence/coherence-operational-config/1.2/coherence-operational-config.xsd"> <!--coherence-version:12.1.2--> <security-config> <identity-asserter> <class-name>com.oracle.handson.PasswordIdentityAsserter</class-name> </identity-asserter> <identity-transformer> <class-name>com.oracle.handson.PasswordIdentityTransformer</class-name> </identity-transformer> <subject-scope>true</subject-scope> </security-config> </coherence>
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:
Open the coherence-cache-config.xml
file from the Project explorer window. You can find the file under Security/appClientModule
.
Save the file as client-cache-config.xml
.
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> <connect-timeout>2s</connect-timeout> <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> <outgoing-message-handler> <request-timeout>5s</request-timeout> </outgoing-message-handler> </initiator-config> </remote-invocation-scheme> </caching-schemes> </cache-config>
To create a cache configuration file for the extend proxy service:
Open the coherence-cache-config.xml
file from the Project explorer window. You can find the file under Security/appClientModule
.
Save the file as examples-cache-config.xml
.
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>
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:
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
.
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.
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.
The Classpath tab should look similar to Figure 10-1.
Figure 10-1 Class Path for the Security Cache Server
In the Common tab, click Browse in the Shared file field to select the Security project. Click Apply.
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:
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
.
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.
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.
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
The Classpath tab should look similar to Figure 10-3.
Figure 10-3 Class Path for the Proxy Server
In the Common tab, click Browse in the Shared file field to select the Security project. Click Apply.
Run the password example to generate and validate the token, and pass it to the proxy service.
Create a run configuration for the PasswordExample.java
file.
Right click PasswordExample.java
in the Project Explorer, and select Run As then Run Configurations.
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.
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.
The Classpath tab should look similar to Figure 10-4.
Figure 10-4 Classpath Tab for the PasswordExample Program
Stop any running cache servers. See "Stopping Cache Servers" for more information.
Run the security proxy server, the security cache server then the PasswordExample.java
program.
Right click the project and select Run As then Run Configurations. Run the SecurityRunProxy
configuration from the Run Configurations dialog box.
Right click the project and select Run As then Run Configurations. Run the SecurityCacheServer
configuration from the Run Configurations dialog box.
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------ 2012-08-21 17:58:37.081/0.390 Oracle Coherence 12.1.2.0 <Info> (thread=main, member=n/a): Loaded operational configuration from "jar:file:/C:/oracle/Middleware/Oracle_Home/coherence/lib/coherence.jar!/tangosol-coherence.xml" 2012-08-21 17:58:37.128/0.437 Oracle Coherence 12.1.2.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "jar:file:/C:/oracle/Middleware/Oracle_Home/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml" 2012-08-21 17:58:37.175/0.484 Oracle Coherence 12.1.2.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "file:/C:/home/oracle/workspace/Security/build/classes/tangosol-coherence-override.xml" 2012-08-21 17:58:37.175/0.484 Oracle Coherence 12.1.2.0 <D5> (thread=main, member=n/a): Optional configuration override "cache-factory-config.xml" is not specified 2012-08-21 17:58:37.191/0.500 Oracle Coherence 12.1.2.0 <D5> (thread=main, member=n/a): Optional configuration override "cache-factory-builder-config.xml" is not specified 2012-08-21 17:58:37.191/0.500 Oracle Coherence 12.1.2.0 <D5> (thread=main, member=n/a): Optional configuration override "/custom-mbeans.xml" is not specified Oracle Coherence Version 12.1.2.0 Build 36845 Grid Edition: Development mode Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. 2012-08-21 17:58:37.425/0.734 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Loaded cache configuration from "file:/C:/home/oracle/workspace/Security/appClientModule/client-cache-config.xml" 2012-08-21 17:58:37.769/1.078 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Created cache factory com.tangosol.net.ExtensibleConfigurableCacheFactory 2012-08-21 17:58:37.878/1.187 Oracle Coherence GE 12.1.2.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.DefaultSerializer, PingInterval=0, PingTimeout=0, RequestTimeout=0, ConnectTimeout=0, SocketProvider=com.oracle.common.internal.net.MultiplexedSocketProvider@10713de, RemoteAddresses=WrapperSocketAddressProvider{Providers=[[tpfaeffl-lap7/130.35.99.202:9099]]}, SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}} 2012-08-21 17:58:37.894/1.203 Oracle Coherence GE 12.1.2.0 <D5> (thread=main, member=n/a): Connecting Socket to 130.35.99.202:9099 2012-08-21 17:58:37.894/1.203 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.202: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
2012-08-21 17:57:44.941/1.750 Oracle Coherence 12.1.2.0 <Info> (thread=main, member=n/a): Loaded operational configuration from "jar:file:/C:/oracle/Middleware/Oracle_Home/coherence/lib/coherence.jar!/tangosol-coherence.xml" 2012-08-21 17:57:44.987/1.796 Oracle Coherence 12.1.2.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "jar:file:/C:/oracle/Middleware/Oracle_Home/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml" 2012-08-21 17:57:45.034/1.843 Oracle Coherence 12.1.2.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "file:/C:/home/oracle/workspace/Security/build/classes/tangosol-coherence-override.xml" 2012-08-21 17:57:45.034/1.843 Oracle Coherence 12.1.2.0 <D5> (thread=main, member=n/a): Optional configuration override "cache-factory-config.xml" is not specified 2012-08-21 17:57:45.050/1.859 Oracle Coherence 12.1.2.0 <D5> (thread=main, member=n/a): Optional configuration override "cache-factory-builder-config.xml" is not specified 2012-08-21 17:57:45.050/1.859 Oracle Coherence 12.1.2.0 <D5> (thread=main, member=n/a): Optional configuration override "/custom-mbeans.xml" is not specified Oracle Coherence Version 12.1.2.0 Build 36845 Grid Edition: Development mode Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. 2012-08-21 17:57:45.534/2.343 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Loaded cache configuration from "file:/C:/home/oracle/workspace/Security/appClientModule/examples-cache-config.xml" 2012-08-21 17:57:47.066/3.875 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Created cache factory com.tangosol.net.ExtensibleConfigurableCacheFactory 2012-08-21 17:57:48.097/4.906 Oracle Coherence GE 12.1.2.0 <D4> (thread=main, member=n/a): TCMP bound to /130.35.99.202:8088 using SystemDatagramSocketProvider 2012-08-21 17:57:51.550/8.359 Oracle Coherence GE 12.1.2.0 <Info> (thread=Cluster, member=n/a): Created a new cluster "cluster:0x47DB" with Member(Id=1, Timestamp=2012-08-21 17:57:48.222, Address=130.35.99.202:8088, MachineId=18578, Location=site:,machine:tpfaeffl-lap7,process:1500, Role=CoherenceServer, Edition=Grid Edition, Mode=Development, CpuCount=2, SocketCount=1) 2012-08-21 17:57:51.566/8.375 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Started cluster Name=cluster:0x47DB Group{Address=224.12.1.0, Port=3155, TTL=4} MasterMemberSet( ThisMember=Member(Id=1, Timestamp=2012-08-21 17:57:48.222, Address=130.35.99.202:8088, MachineId=18578, Location=site:,machine:tpfaeffl-lap7,process:1500, Role=CoherenceServer) OldestMember=Member(Id=1, Timestamp=2012-08-21 17:57:48.222, Address=130.35.99.202:8088, MachineId=18578, Location=site:,machine:tpfaeffl-lap7,process:1500, Role=CoherenceServer) ActualMemberSet=MemberSet(Size=1 Member(Id=1, Timestamp=2012-08-21 17:57:48.222, Address=130.35.99.202:8088, MachineId=18578, Location=site:,machine:tpfaeffl-lap7,process:1500, Role=CoherenceServer) ) MemberId|ServiceVersion|ServiceJoined|MemberState 1|12.1.2|2012-08-21 17:57:48.222|JOINED RecycleMillis=1200000 RecycleSet=MemberSet(Size=0 ) ) TcpRing{Connections=[]} IpMonitor{Addresses=0} 2012-08-21 17:57:51.706/8.515 Oracle Coherence GE 12.1.2.0 <D5> (thread=Invocation:Management, member=1): Service Management joined the cluster with senior service member 1 2012-08-21 17:57:51.800/8.609 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=1): Loaded Reporter configuration from "jar:file:/C:/oracle/Middleware/Oracle_Home/coherence/lib/coherence.jar!/reports/report-group.xml" 2012-08-21 17:57:52.144/8.953 Oracle Coherence GE 12.1.2.0 <D5> (thread=DistributedCache:PartitionedPofCache, member=1): Service PartitionedPofCache joined the cluster with senior service member 1 2012-08-21 17:57:52.378/9.187 Oracle Coherence GE 12.1.2.0 <Info> (thread=Proxy:ProxyService:TcpAcceptor, member=1): TcpAcceptor now listening for connections on 130.35.99.202:9099 2012-08-21 17:57:52.394/9.203 Oracle Coherence GE 12.1.2.0 <D5> (thread=Proxy:ProxyService:TcpAcceptor, member=1): Started: TcpAcceptor{Name=Proxy:ProxyService:TcpAcceptor, State=(SERVICE_STARTED), ThreadCount=2, HungThreshold=0, TaskTimeout=0, Codec=Codec(Format=POF), Serializer=com.tangosol.io.DefaultSerializer, PingInterval=0, PingTimeout=0, RequestTimeout=0, SocketProvider=com.oracle.common.internal.net.MultiplexedSocketProvider@fe8610, LocalAddress=WrapperSocketAddressProvider{Providers=[[tpfaeffl-lap7/130.35.99.202:9099]]}, SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}, ListenBacklog=0, BufferPoolIn=BufferPool(BufferSize=2KB, BufferType=DIRECT, Capacity=Unlimited), BufferPoolOut=BufferPool(BufferSize=2KB, BufferType=DIRECT, Capacity=Unlimited)} 2012-08-21 17:57:52.394/9.203 Oracle Coherence GE 12.1.2.0 <D5> (thread=Proxy:ProxyService, member=1): Service ProxyService joined the cluster with senior service member 1 2012-08-21 17:57:52.409/9.218 Oracle Coherence GE 12.1.2.0 <Info> (thread=NameService:TcpAcceptor, member=1): TcpAcceptor now listening for connections on 130.35.99.202:8088.3 2012-08-21 17:57:52.409/9.218 Oracle Coherence GE 12.1.2.0 <D5> (thread=NameService:TcpAcceptor, member=1): Started: TcpAcceptor{Name=NameService:TcpAcceptor, State=(SERVICE_STARTED), ThreadCount=0, Codec=Codec(Format=POF), Serializer=com.tangosol.net.internal.NameServicePofContext, PingInterval=0, PingTimeout=0, RequestTimeout=0, SocketProvider=com.oracle.common.internal.net.MultiplexedSocketProvider@fe8610, LocalAddress=WrapperSocketAddressProvider{Providers=[WrapperSocketAddressProvider{Providers=[Address=130.35.99.202:8088]}]}, SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}, ListenBacklog=0, BufferPoolIn=BufferPool(BufferSize=2KB, BufferType=DIRECT, Capacity=Unlimited), BufferPoolOut=BufferPool(BufferSize=2KB, BufferType=DIRECT, Capacity=Unlimited)} 2012-08-21 17:57:52.409/9.218 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=1): Services ( ClusterService{Name=Cluster, State=(SERVICE_STARTED, STATE_JOINED), Id=0, Version=12.1.2, OldestMemberId=1} InvocationService{Name=Management, State=(SERVICE_STARTED), Id=1, Version=12.1.2, OldestMemberId=1} PartitionedCache{Name=PartitionedPofCache, State=(SERVICE_STARTED), LocalStorage=enabled, PartitionCount=257, BackupCount=1, AssignedPartitions=257, BackupPartitions=0} ProxyService{Name=ProxyService, State=(SERVICE_STARTED), Id=3, Version=12.1.2, OldestMemberId=1} ) Started DefaultCacheServer... 2012-08-21 17:58:14.222/31.031 Oracle Coherence GE 12.1.2.0 <D5> (thread=Cluster, member=1): Member(Id=2, Timestamp=2012-08-21 17:58:14.066, Address=130.35.99.202:8090, MachineId=18578, Location=site:,machine:tpfaeffl-lap7,process:2212, Role=CoherenceServer) joined Cluster with senior member 1 2012-08-21 17:58:14.269/31.078 Oracle Coherence GE 12.1.2.0 <D5> (thread=Cluster, member=1): Member 2 joined Service Management with senior member 1 2012-08-21 17:58:14.425/31.234 Oracle Coherence GE 12.1.2.0 <D5> (thread=Cluster, member=1): Member 2 joined Service PartitionedPofCache with senior member 1 2012-08-21 17:58:14.534/31.343 Oracle Coherence GE 12.1.2.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 2012-08-21 17:58:14.737/31.546 Oracle Coherence GE 12.1.2.0 <D5> (thread=DistributedCache:PartitionedPofCache, member=1): 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 Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne
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:
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:
Create a new Java class named EntitledNamedCache
in the Security project.
See "Creating a Java Class" if you need detailed information.
Ensure that the class imports and extends WrapperNamedCache
.
Import the Filter
, MapListener
, ValueExtractor
, Collection
, Comparator
, Map
, Service
, and Set
classes. The methods in WrapperNamedCache
(and by extension, EntitledNamedCache
) use arguments with these types.
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.Service; 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(); } }
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:
Create a new Java class named EntitledCacheService
in the Security
project.
Ensure that the class imports and extends the WrapperCacheService
class.
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); } }
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.
Create a new Java class with a main
method in the Security
project named AccessControlExample
.
See "Creating a Java Class" for detailed information.
Implement the main
method to access the cache.
Specify users defined in the SecurityExampleHelper
file as arguments to its login
method.
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.net.Service; 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------"); } }
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> ...
Run the access control example to demonstrate how access to the cache can be granted or denied, based on a user's role.
Create a run configuration for the AccessControlExample.java
.
Right click AccessControlExample
in the Project Explorer. Select Run As then Run Configurations.
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.
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. Enter 3155
in the Cluster port field.
In the Classpath tab, ensure that User Entries looks similar to Figure 10-5. Click Apply then Close.
Figure 10-5 Classpath Tab for the AccessControlExample Program
Stop any running cache servers. See "Stopping Cache Servers" for more information.
Run the security proxy server, the security cache server then the AccessControlExample.java
program.
Right click the project and select Run As then Run Configurations. Run the SecurityRunProxy
configuration from the Run Configurations dialog box.
Right click the project and select Run As then Run Configurations. Run the SecurityCacheServer
configuration from the Run Configurations dialog box.
Right click the AccessControl
Example.java
file in the Project Explorer and select Run As then Run Configurations. Run the AccessControl
Example
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------ 2012-08-22 11:32:24.329/0.609 Oracle Coherence 12.1.2.0 <Info> (thread=main, member=n/a): Loaded operational configuration from "jar:file:/C:/oracle/Middleware/Oracle_Home/coherence/lib/coherence.jar!/tangosol-coherence.xml" 2012-08-22 11:32:24.407/0.687 Oracle Coherence 12.1.2.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "jar:file:/C:/oracle/Middleware/Oracle_Home/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml" 2012-08-22 11:32:24.454/0.734 Oracle Coherence 12.1.2.0 <Info> (thread=main, member=n/a): Loaded operational overrides from "file:/C:/home/oracle/workspace/Security/build/classes/tangosol-coherence-override.xml" 2012-08-22 11:32:24.470/0.750 Oracle Coherence 12.1.2.0 <D5> (thread=main, member=n/a): Optional configuration override "cache-factory-config.xml" is not specified 2012-08-22 11:32:24.470/0.750 Oracle Coherence 12.1.2.0 <D5> (thread=main, member=n/a): Optional configuration override "cache-factory-builder-config.xml" is not specified 2012-08-22 11:32:24.470/0.750 Oracle Coherence 12.1.2.0 <D5> (thread=main, member=n/a): Optional configuration override "/custom-mbeans.xml" is not specified Oracle Coherence Version 12.1.2.0 Build 36845 Grid Edition: Development mode Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. 2012-08-22 11:32:24.688/0.968 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Loaded cache configuration from "file:/C:/home/oracle/workspace/Security/appClientModule/client-cache-config.xml" 2012-08-22 11:32:25.220/1.500 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Created cache factory com.tangosol.net.ExtensibleConfigurableCacheFactory 2012-08-22 11:32:26.001/2.281 Oracle Coherence GE 12.1.2.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.DefaultSerializer, PingInterval=0, PingTimeout=0, RequestTimeout=0, ConnectTimeout=0, SocketProvider=com.oracle.common.internal.net.MultiplexedSocketProvider@1739f, RemoteAddresses=WrapperSocketAddressProvider{Providers=[[tpfaeffl-lap7/130.35.99.202:9099]]}, SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}} 2012-08-22 11:32:26.141/2.421 Oracle Coherence GE 12.1.2.0 <D5> (thread=main, member=n/a): Connecting Socket to 130.35.99.202:9099 2012-08-22 11:32:26.173/2.453 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.202:9099 Success: read and write allowed 2012-08-22 11:32:27.157/3.437 Oracle Coherence GE 12.1.2.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.DefaultSerializer, PingInterval=0, PingTimeout=0, RequestTimeout=0, ConnectTimeout=0, SocketProvider=com.oracle.common.internal.net.MultiplexedSocketProvider@1739f, RemoteAddresses=WrapperSocketAddressProvider{Providers=[[tpfaeffl-lap7/130.35.99.202:9099]]}, SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}} 2012-08-22 11:32:27.204/3.484 Oracle Coherence GE 12.1.2.0 <D5> (thread=main, member=n/a): Connecting Socket to 130.35.99.202:9099 2012-08-22 11:32:27.204/3.484 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.202:9099 Success: read allowed Success: Correctly cannot write Success: Correctly cannot destroy the cache 2012-08-22 11:32:27.470/3.750 Oracle Coherence GE 12.1.2.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.DefaultSerializer, PingInterval=0, PingTimeout=0, RequestTimeout=0, ConnectTimeout=0, SocketProvider=com.oracle.common.internal.net.MultiplexedSocketProvider@1739f, RemoteAddresses=WrapperSocketAddressProvider{Providers=[[tpfaeffl-lap7/130.35.99.202:9099]]}, SocketOptions{LingerTimeout=0, KeepAliveEnabled=true, TcpDelayEnabled=false}} 2012-08-22 11:32:27.501/3.781 Oracle Coherence GE 12.1.2.0 <D5> (thread=main, member=n/a): Connecting Socket to 130.35.99.202:9099 2012-08-22 11:32:27.501/3.781 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.202: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... 2012-08-22 11:32:02.985/93.234 Oracle Coherence GE 12.1.2.0 <D5> (thread=Cluster, member=1): Member(Id=2, Timestamp=2012-08-22 11:32:02.837, Address=130.35.99.202:8090, MachineId=18578, Location=site:,machine:tpfaeffl-lap7,process:4180, Role=CoherenceServer) joined Cluster with senior member 1 2012-08-22 11:32:03.048/93.297 Oracle Coherence GE 12.1.2.0 <D5> (thread=Cluster, member=1): Member 2 joined Service Management with senior member 1 2012-08-22 11:32:03.220/93.469 Oracle Coherence GE 12.1.2.0 <D5> (thread=Cluster, member=1): Member 2 joined Service PartitionedPofCache with senior member 1 2012-08-22 11:32:03.298/93.547 Oracle Coherence GE 12.1.2.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, ... ... 123, 124, 125, 126, 127} to member 2 requesting 128 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 2012-08-22 11:32:27.235/117.484 Oracle Coherence GE 12.1.2.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:105) at com.oracle.handson.SecurityExampleHelper.checkAccess(SecurityExampleHelper.java:59) at com.oracle.handson.EntitledNamedCache.put(EntitledNamedCache.java:68) 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:356) at com.tangosol.coherence.component.net.extend.Channel.execute(Channel.CDB:48) 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:66) at com.tangosol.coherence.component.util.Daemon.run(Daemon.CDB:51) at java.lang.Thread.run(Thread.java:722) 2012-08-22 11:32:27.391/117.640 Oracle Coherence GE 12.1.2.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:105) at com.oracle.handson.SecurityExampleHelper.checkAccess(SecurityExampleHelper.java:59) at com.oracle.handson.EntitledCacheService.destroyCache(EntitledCacheService.java:61) 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:356) at com.tangosol.coherence.component.net.extend.Channel.execute(Channel.CDB:48) 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:66) at com.tangosol.coherence.component.util.Daemon.run(Daemon.CDB:51) at java.lang.Thread.run(Thread.java:722) Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne Password validated for user: CN=BuckarooBanzai,OU=Yoyodyne
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:
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:
Create a new Java class named ExampleInvocable
in the Security
project.
See "Creating a Java Class" for detailed information.
Import the Invocable
and InvocationService
interfaces. Because this class will be working with serializable objects, import the PortableObject
, PofReader
and PofWriter
classes.
Ensure that the ExampleInvocable
class implements Invocable
and PortableObject
.
Implement the ExampleInvocable
class to increment an integer and return the result.
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 --------------------------------------------- /** * */ private static final long serialVersionUID = 1L; /** * 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; }
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:
Create a new Java class named EntitledInvocationService
in the Security
project.
Import the Invocable
, InvocationObserver
, InvocationService
, WrapperInvocationService
, Map
, and Set
interfaces. Ensure that the EntitledInvocationService
class extends the WrapperInvocationService
class.
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); } }
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:
Create a Java class with a main
method in the Security
project named AccessInvocationServiceExample.java
.
See "Creating a Java Class" for detailed information.
Among other classes, import ExampleInvocable
, CacheFactory
, and InvocationService
.
Implement the main
method to invoke the accessInvocationService
method.
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------"); } }
Edit the exam
ples-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> ...
Create a POF configuration file to declare ExampleInvocable
as a user type.
Locate the pof-config.xml
file under Security\appClientModule
in the Project Explorer and open it in the Eclipse IDE.
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 http://xmlns.oracle.com/coherence/coherence-pof-config/1.1/coherence-pof-config.xsd"> <user-type-list> <!-- include all "standard" 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>
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 make the application use the XML configuration files in the C:\home\oracle\workspace\Security\appModule
, ensure that the Coherence12.1.2 library does not appear in the Bootstrap Entries section of the servers' class path. Also, position the coherence.jar
file after the Security folder in the User Entries section.
If the Coherence12.1.2 library does appear in the Bootstrap Entries section of the servers' class path, follow these steps to remove it:
Right click the project in the Project Explorer and select Run As then Run Configurations.
Select SecurityRunProxy. In the Classpath tab, remove Coherence12.1.2 from the list of Bootstrap Entries if it appears. Click Add External Jars and select the coherence.jar
file from the Oracle_Home\coherence\lib
folder. Click Apply.
Select SecurityCacheServer. In the Classpath tab, remove Coherence12.1.2 from the list of Bootstrap Entries if it appears. Click Add External Jars and select the coherence.jar
file from the Oracle_Home\coherence\lib
folder. Click Apply.
Note:
Ensure that the XML configuration files (in the C:\home\oracle\workspace\Security
and the 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
) before it references the one in the coherence.jar
file.
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
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.
Create a run configuration for the AccessInvocationServiceExample.java
file.
Right click AccessInvocationServiceExample.java
in the Project Explorer and select Run As then Run Configurations.
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.
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.
In the Classpath tab, remove Coherence12.1.2 from the Bootstrap Entries list if it appears there. 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 if it does not already appear there. 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
Stop any running cache servers. See "Stopping Cache Servers" for more information.
Run the security proxy server, the security cache server then the AccessInvocationServiceExample.java
program.
Right click the project and select Run As then Run Configurations. Run the SecurityRunProxy
configuration from the Run Configurations dialog box.
Right click the project and select Run As then Run Configurations. Run the SecurityCacheServer
configuration from the Run Configurations dialog box.
Right click the AccessInvocationService
Example.java
file in the Project Explorer and select Run As then Run Configurations. Select AccessControl
Example
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------ 2012-08-22 12:32:55.548/0.516 Oracle Coherence 12.1.2.0 <Info> (thread=main, member=n/a): Loaded operational configuration from "jar:file:/C:/oracle/Middleware/Oracle_Home/coherence/lib/coherence.jar!/tangosol-coherence.xml" ... ... Oracle Coherence Version 12.1.2.0 Build 36845 Grid Edition: Development mode Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. ... ... 2012-08-22 12:32:56.501/1.469 Oracle Coherence GE 12.1.2.0 <D5> (thread=main, member=n/a): Connecting Socket to 130.35.99.202:9099 2012-08-22 12:32:56.501/1.469 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.202:9099 Success: Correctly allowed to use the invocation service ... ... 2012-08-22 12:32:56.595/1.563 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=n/a): Connected Socket to 130.35.99.202: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... 2012-08-22 12:32:31.782/23.766 Oracle Coherence GE 12.1.2.0 <D5> (thread=Cluster, member=1): Member(Id=2, Timestamp=2012-08-22 12:32:31.595, Address=130.35.99.202:8090, MachineId=18578, Location=site:,machine:tpfaeffl-lap7,process:4144, Role=CoherenceServer) joined Cluster with senior member 1 ... ... 121, 122, 123, 124, 125, 126, 127} to member 2 requesting 128 Password validated for user: role_writer Password validated for user: role_writer Password validated for user: role_reader Password validated for user: role_reader 2012-08-22 12:32:56.610/48.594 Oracle Coherence GE 12.1.2.0 <D5> (thread=Proxy:ProxyService:TcpAcceptorWorker:0, 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:105) at com.oracle.handson.SecurityExampleHelper.checkAccess(SecurityExampleHelper.java:59) 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:356) at com.tangosol.coherence.component.net.extend.Channel.execute(Channel.CDB:48) 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:66) at com.tangosol.coherence.component.util.Daemon.run(Daemon.CDB:51) at java.lang.Thread.run(Thread.java:722)