Oracle Coherence 内存网格

第 10 章 — Coherence 内存集群的安全访问

 

作者:金辉 (Oracle)


右箭头 第 1 章 — 漫说内存网格
右箭头 第 2&3 章 — 安装和配置开发环境
右箭头 第 4 章 — 开始第一个 Coherence 程序
右箭头 第 5 章 — 在 Coherence 中使用 POF 对象
右箭头 第 6 章 — 分布式内存集群的数据灌注、查询和统计
右箭头 第 7 章 — 监听器处理内存集群中变化的对象
右箭头 第 8 章 — 使用 ZFS 加密
右箭头 第 9 章 — 内存集群与数据库的互操作
右箭头 第 10 章 — Coherence 内存集群的安全访问
右箭头 第 11 章 — 基于内存集群的事件处理

本章练习介绍客户端如何安全的访问内存集群,演示使用如下 API

  • SecurityHelper
  • PofPrincipal
  • IdentityTransformer
  • IdentityAsserter
  • 和 WrapperCacheService APIs

Coherence*Extend 机制运行大范围的访问 Coherence 缓存。包含桌面应用,远程服务器和跨越广域网的机器间连接。

10.1 介绍

 

Coherence*Extend 由运行在集群外部的客户端和集群内部的代理服务构成,运行在一个或多个缓存服务器上。客户端API路由所有请求到代理,代理响应客户请求授权访问集群各服务,如 partitioned 或 replicated cache service 或 invocation service。

由于 extend 的客户端在集群之外,如何安全的访问集群是个大问题。本节介绍三种技术,基于口令、授权服务和调用服务。

更多的安全技术不在此处详细讨论,参见《Oracle Fusion Middleware Securing Oracle Coherence》

10.2 基于 Token-Based 安全

 

实现基于令牌的安全访问。按照如下方式进行配置:

  • 客户应用程序文件,描述访问集群的功能;
  • 缓存配置文件,extend client 和 extend proxy 分别有自己的配置文件;
  • 重写操作和运行时的配置信息;
  • 服务器启动文档,为集群内的扩展代理进行配置。
  • POF 配置描述符,指定当使用 POF 序列化方式时的客户数据类型。

使用基于令牌的安全,必须提供身份转换和断言的实现。身份转换在客户端产生令牌,断言在集群侧验证。

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

  1. Use a Security Helper File
  2. Create an Identity Transformer
  3. Create an Identity Asserter
  4. Create the Password File
  5. Enable the Identity Transformer and Asserter
  6. Create a Cache Configuration File for the Extend Client
  7. Create a Cache Configuration File for the Extend Proxy
  8. Create a Start-Up Configuration for a Cache Server with a Proxy Service
  9. Create a Start-Up Configuration for a Cache Server
  10. Run the Password Example

10.2.1 使用安全 Helper 文件

 

本节的实例引用了 security helper 文件,定义了基于角色的安全策略和访问控制。

在 security helper 文件里定义几种不同的角色:role_reader、role_writer和role_admin。同时定义不同用户到角色上,比如 BuckarooBanzai 配置为 ROLE_ADMIN, 同时为每个角色定义唯一的 ID,比如 ROLE_ADMIN 为 9。Helper 同时定义缓存的名称和调用服务的名称。

这个文件的核心功能是 login 和 checkAccess 方法。Login 方法使用用户名和构造的唯一名 (distinguished name DN)。通过名字关联到角色。PofPrincipal 提供了具体实现。

checkAccess 方法则演示授权部分的代码实现。里面决定用户是否能够通过给定的角色访问缓存。

创业一个新的工程和 security helper 文件的步骤如下:

1.    创建一个新工程 (Application Client Project)chapter10_Security

在 Configuration 配置下拉列表找那个,确保选中的是 CoherenceConfig。

10-figure-01

取消 Create a default main;

10-figure-02

在 Coherence 页,选择仅仅 Coherence12.1.2。

10-figure-03

2.    新建一个 Java文件:SecurityExampleHelper.java

Java 包路径是:com.oracle.handson.chapter10.

3.    示例代码见 appClientModule\com\oracle\handson\chapter10\SecurityExampleHelper.java

见下面

package com.oracle.handson.chapter10;

import com.tangosol.io.pof.PofPrincipal;
import com.tangosol.net.security.SecurityHelper;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import java.security.Principal;
import javax.security.auth.Subject;

/**
 * This class provides extremely simplified role based policies and access
 * control.
*/
public class SecurityExampleHelper {
	// ----- static methods -------------------------------------------------

	/**
	 * Login the user.
	 * 
	 * @param sName
	 *            the user name
	 * 
	 * @return the authenticated user
	 */
	public static Subject login(String sName) {
		// For simplicity, just create a Subject. Normally, this would be
		// done using JAAS.
		String sUserDN = "CN=" + sName + ",OU=Yoyodyne";
		Set setPrincipalUser = new HashSet();

		setPrincipalUser.add(new PofPrincipal(sUserDN));
		// Map the user to a role
		setPrincipalUser.add(new PofPrincipal((String) s_mapUserToRole.get(sName)));

		return new Subject(true, setPrincipalUser, new HashSet(), new HashSet());
	}

	/**
	 * Assert that a Subject is associated with the calling thread with a
	 * Principal representing the required role.
	 * 
	 * @param sRoleRequired
	 *            the role required for the operation
	 * 
	 * @throws SecurityException
	 *             if a Subject is not associated with the calling thread or
	 *             does not have the specified role Principal
	 */
	public static void checkAccess(String sRoleRequired) {
		checkAccess(sRoleRequired, SecurityHelper.getCurrentSubject());
	}

	/**
	 * Assert that a Subject contains a Principal representing the required
	 * role.
	 * 
	 * @param sRoleRequired
	 *            the role required for the operation
	 * 
	 * @param subject
	 *            the Subject requesting access
	 * 
	 * @throws SecurityException
	 *             if a Subject is null or does not have the specified role
	 *             Principal
	 */
	public static void checkAccess(String sRoleRequired, Subject subject) {
		if (subject == null) {
			throw new SecurityException("Access denied, authentication required");
		}

		Map mapRoleToId = s_mapRoleToId;
		Integer nRoleRequired = (Integer) mapRoleToId.get(sRoleRequired);

		for (Iterator iter = subject.getPrincipals().iterator(); iter.hasNext();) {
			Principal principal = (Principal) iter.next();
			String sName = principal.getName();

			if (sName.startsWith("role_")) {
				Integer nRolePrincipal = (Integer) mapRoleToId.get(sName);

				if (nRolePrincipal == null) {
					// invalid role
					break;
				}

				if (nRolePrincipal.intValue() >= nRoleRequired.intValue()) {
					return;
				}
			}
		}
		throw new SecurityException("Access denied, insufficient privileges");
	}
	// ----- constants -----------------------------------------------------
	public static final String ROLE_READER = "role_reader";
	public static final String ROLE_WRITER = "role_writer";
	public static final String ROLE_ADMIN = "role_admin";
	/**
	 * The cache name for security examples
	 */
	public static final String SECURITY_CACHE_NAME = "security";
	/**
	 * The name of the InvocationService used by security examples.
	 */
	public static String INVOCATION_SERVICE_NAME = "ExtendTcpInvocationService";
	// ----- static data ---------------------------------------------------
	/**
	 * The map keyed by user name with the value being the user's role.
	 * Represents which user is in which role.
	 */
	private static Map s_mapUserToRole = new HashMap();
	/**
	 * The map keyed by role name with the value the role id. Represents the
	 * numeric role identifier.
	 */
	private static Map s_mapRoleToId = new HashMap();
	// ----- static initializer ---------------------------------------------
	static {
		// User to role mapping
		s_mapUserToRole.put("BuckarooBanzai", ROLE_ADMIN);
		s_mapUserToRole.put("JohnWhorfin", ROLE_WRITER);
		s_mapUserToRole.put("JohnBigboote", ROLE_READER);
		// Role to Id mapping
		s_mapRoleToId.put(ROLE_ADMIN, Integer.valueOf(9));
		s_mapRoleToId.put(ROLE_WRITER, Integer.valueOf(2));
		s_mapRoleToId.put(ROLE_READER, Integer.valueOf(1));
	}
}

 

10.2.2 创建一个身份转换 (Identity Transformer)

 

一个身份转换 (identity transformer) (com.tangosol.net.security.IdentityTransformer)是一个客户端的组件,转换身份标识到令牌当中。令牌是一个 Coherence 支持的序列化对象。Coherence 在运行时自动的序列化令牌,并作为请求的一部分发送给代理。创建一个身份转换步骤如下:

1.    创建一个 Java 类:PasswordIdentityTransformer。

2.    Import IdentityTransformer 接口。

确保 PasswordIdentityTransformer 实现 IdentityTransformer 接口。

3.    实现 transformIdentity 方法,处理下面的任务:

  • 测试主题是否存在和完整
  • 从主题中获得当事人的名称,保存到字符串数组里
  • 构造令牌,组合当事人名字和密码,能够被序列化成 POF 类型
package com.oracle.handson.chapter10;

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;
	}
}

 

10.2.3 创建身份断言 (Identity Asserter)

 

一个身份断言 (identity asserter) (com.tangosol.net.security.IdentityAsserter) 是运行在缓存服务器上的,集群侧的组件。断言验证从客户端传来的令牌。创建一个身份断言的实现:

1.    创建一个 Java 类:PasswordIdentityAsserter。

2.    Import IdentityAsserter 接口。确保 PasswordIdentityAsserter 实现了 IdentityAsserter 接口。

3.    实现 assertIdentity 方法:

a)    验证令牌中包含合适的口令。

package com.oracle.handson.chapter10;

import com.tangosol.net.Service;
import com.tangosol.net.security.IdentityAsserter;
import com.tangosol.io.pof.PofPrincipal;
import java.util.HashSet;
import java.util.Set;
import javax.security.auth.Subject;

/**
 * PasswordIdentityAsserter asserts that the security token contains the
 * required password and then constructs a Subject based on a list of Principal
 * names.
 * 
 */
public class PasswordIdentityAsserter implements IdentityAsserter {
	// ----- IdentityAsserter interface -------------------------------------

	/**
	 * Asserts an identity based on a token-based identity assertion.
	 * 
	 * @param oToken
	 *            the token that asserts identity.
	 * 
	 * @return a Subject representing the identity.
	 * 
	 * @throws SecurityException
	 *             if the identity assertion fails.
	 */
	public Subject assertIdentity(Object oToken, Service service) throws SecurityException {
		// The service is not needed so the service argument is being ignored.
		// It could be used, for example, if there were different token types
		// required per service.
		if (oToken instanceof Object[]) {
			String sPassword = System.getProperty("coherence.password", "secret-password");
			Set setPrincipalUser = new HashSet();
			Object[] asName = (Object[]) oToken;

			// first name must be password
			if (((String) asName[0]).equals(sPassword)) {
				// prints the user name to server shell to ensure we are
				// communicating with it and to ensure user is validated
				System.out.println("Password validated for user: " + asName[1]);
				for (int i = 1, len = asName.length; i < len; i++) {
					setPrincipalUser.add(new PofPrincipal((String) asName[i]));
				}
				return new Subject(true, setPrincipalUser, new HashSet(), new HashSet());
			}
		}
		throw new SecurityException("Access denied");
	}
}

 

10.2.4 创建密码文件

 

创建一个 Java 程序处理密码。使用 SecurityExampleHelper.login("BuckarooBanzai") 来调用 SecurityExampleHelper 里的 login 方法来产生令牌。在运行时,用户名关联到 SecurityExampleHelper 类里的主题。通过 PasswordIdentityTransformer 类产生一个令牌,并且通过 PasswordIdentityAsserter 来验证。如果验证成功,连接到代理的则被授权。用 Subject.doas 方法使主题在安全上下文中可用。

1.    创建一个有 main 方法的类:PasswordExample。

2.    在 main 方法中实现获得一个缓存的引用。

3.    使用 SecurityExampleHelper.login 方法获得用户 BuckarooBanzai 的主题。

4.    实现 doAs 方法,将主题作为 java 安全上下文的一部分。主题被后面的应用所使用。在这个例子里,doAs 方法实现了验证集群访问控制的角色。

package com.oracle.handson.chapter10;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.net.Service;

import java.io.IOException;
import java.security.PrivilegedExceptionAction;
import javax.security.auth.Subject;

/**
 * This class shows how a Coherence Proxy can require a password to get a
 * reference to a cache.
 * <p>
 * The PasswordIdentityTransformer will generate a security token that contains
 * the password. The PasswordIdentityAsserter will validate the security token
 * to enforce the password. The token generation and validation occurs
 * automatically when a connection to the proxy is made.
 * 
 */
public class PasswordExample {
	// ----- static methods -------------------------------------------------

	/**
	 * Get a reference to the cache. Password will be required.
	 */
	public static void main(String[] args) {
		getCache();
	}

	public static void getCache() {
		System.out.println("------password example begins------");

		Subject subject = SecurityExampleHelper.login("BuckarooBanzai");

		try {
			NamedCache cache = (NamedCache) Subject.doAs(subject, new PrivilegedExceptionAction() {
				public Object run() throws Exception {
					NamedCache cache;

					cache = CacheFactory.getCache(SecurityExampleHelper.SECURITY_CACHE_NAME);
					System.out.println("------password example succeeded------");
					return cache;
				}
			});
		} catch (Exception e) {
			// get exception if the password is invalid
			System.out.println("Unable to connect to proxy");
			e.printStackTrace();
		}
		System.out.println("------password example completed------");
	}
}

 

10.2.5 启用身份转换 Identity Transformer 和断言 Asserter

 

配置一个操作重写文件 (tangosol-coherence-override.xml) 去标识定义身份转换的类(该类在客户侧用于转换主题到令牌)和身份断言(该类在集群侧验证身份断言)。

1.    在项目浏览器里打开 tangosol-coherence-override.xml 文件

10-figure-04

2.    使用 identity-transformer 和 identity-asserter 单元标识两个实现类的全路径:PasswordIdentityTransformer 和 PasswordIdentityAsserter。设置主题范围参数为真,关联当前安全上下文的身份到缓存。

<?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>

 

10.2.6 为扩展客户端 (Client) 配置缓存配置文件

 

扩展客户端的缓存配置文件路由缓存操作到集群的扩展代理上。在运行时,缓存操作不在本地执行,他们被发网扩展代理服务。步骤如下:

1.    在项目浏览器里打开 coherence-cache-config.xml 文件

2.    保存成 client-cache-config.xml 文件

3.    写扩展客户端缓存配置文件。注意以下几点:

a)    使用 cache-name 单元,定义缓存的名字为 security。注意在集群侧必须同时也有一个缓存名字叫 security。

b)    使用 remote-cache-scheme 单元, 定义远程缓存的细节。

c)    使用 address 和 port 单元,在tcp-initiator配置里标识远程代理服务侦听的地址,本例是 localhos t端口 9099。

d)    使用 defaults 和 serializer,定义客户 with a value of pof to call the serializer for the custom POF configuration file (which you will create later in this chapter).

<?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>

 

10.2.7 为扩展代理(Proxy)创建缓存配置

 

为扩展缓存代理创建缓存配置,步骤如下:

1.    工程浏览器打开coherence-cache-config.xml文件。

2.    另存为examples-cache-config.xml。

3.    配置扩展代理的缓存配置文件,以下是配置重点:

a)    使用cache-name单元,定义安全的缓存名称,这个名字必须同时定义在扩展代理的缓存配置文件里。

b)    使用address和port单元,指定扩展代理监听的地址和端口,本例是localhost:9099。

c)    使用autostart单元,通过tangosol.coherence.extend.enabled系统属性来防止缓存服务器运行代理服务。

d)    使用defaults和serializer来调用客户POF配置序列化。

配置文件如下:

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

 

10.2.8 配置缓存服务器的启动配置

 

新建一个缓存集群节点的启动配置。配置必须包括系统属性指定代理服务和集群侧缓存配置文件。你必须包括应用的类和 XML 配置文件在 class path 上。步骤如下:

1.    在 Eclipse 里创建启动配置。右键点 chapter10_Security,Run As ->Run Configurations。起名 为chapter10CacheServerSecurityCacheServer.

2.    Main选卡

Project chapter10_Security
Main class com.tangosol.net.DefaultCacheServer
10-figure-05

 

3.    Coherence 选卡

集群侧缓存配置 examples-cache-config.xml
Local Storage Enabel(cache server)
Cluster port 3155
10-figure-06

 

4.    Classpath 选卡

10-figure-07

5.    Common 选卡

Shared file \chapter10_Security
10-figure-08

 

最后 Apply。

10.2.9 配置一个有代理服务的缓存服务启动配置

 

创建一个集群内带扩展代理的缓存服务器启动配置。扩展客户端连接这个服务。运行配置必须知道代理服务和集群配置。运行应用程序类和 XML 必须包含在 class path 当中。

简单点说,启动脚本必须包含缓存服务器启动配置,及确保扩展代理。步骤如下:

1.    在 Eclipse 里创建启动配置。右键点 chapter10_Security,Run As ->Run Configurations。起名为 chapter10CacheServerSecurityRunProxy。

2.    Main 选卡

Project chapter10_Security
Main class com.tangosol.net.DefaultCacheServer
10-figure-09

 

3.    Coherence选卡

集群侧缓存配置 examples-cache-config.xml(同10.2.8)
Local Storage Enabel(cache server)(同10.2.8)
Cluster port 3155 (同10.2.8)
10-figure-10

 

4.    Arguments 选卡

VM 参数 -Dtangosol.coherence.extend.enabled=true
10-figure-11

 

5.    Classpath 选卡

10-figure-12

6.    Common 选卡

 10-figure-13

10.2.10 运行实例程序

 

运行 password 程序产生产生和验证令牌,传给代理服务。

 

1.    为 PasswordExample.java 创建运行配置。

 

a)    右键 PasswordExample.java, Run As->Run Configurations。

b)    新运行配置 chapter10CacheClientPasswordExample

运行配置名称 chapter10CacheClientPasswordExample
Project chapter10_Security
Main Class com.oracle.handson.chapter10.PasswordExample
10-figure-14

 

c)    Coherence 选卡

Client 缓存配置 appClientModule/client-cache-config.xml
Local storage Disabled (cache client)
Cluster Port 3155
10-figure-15

 

d)    Classpath选卡

10-figure-16

2.    停止所有运行的缓存服务器

3.    运行安全代理服务,运行安全缓存服务,然后运行 PasswordExample

a)    运行 Proxy:chapter10CacheServerSecurityRunProxy

b)    运行 CacheServer:chapter10CacheServerSecurityCacheServer

c)    运行 Client: chapter10CacheClientPasswordExample

PasswordExample 输出如下:
 10-figure-17

 

d)    代理服务输出如下:

 10-figure-18

10.3 集群的基于角色访问控制

 

本节描述如何使用基于角色的策略访问集群。登录代码 获得主题和用户 ID,关联到具体角色。 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.

举例,一个用户有 writer 角色,可以 put 和 get 方法。一个用户有 reader 角色,能执行 get 方法,不能执行 put 方法。一个用户有 writer 角色不能删除一个缓存;只有有 admin 角色的用户才可以。

注意当缓存的引用被建立起来时,标识身份的主题会永久关联到引用的的上下文,任何使用这个缓存引用的都使用这个身份。

   
PasswordIdentityTransformer 前一章节
PasswordIdentityAsserter

 

10.3.1 定义哪些用户角色有资格访问缓存方法

 

新建一个 Java 程序,根据用户角色访问缓存。若要执行此操作,你可以对使用由客户端传递的主题对象进行包装。执行权限仅能访问指定的缓存。

本节创建的类扩展了 com.tangosol.net.cache.WrapperNamedCache 类。次类可以让你方便的使用命名缓存接口上安全方法。

若要确定哪个用户角色能够访问缓存方法,包括对 SecurityExampleHelper.checkAccess 的调用。参数 checkAccess,提供了 provide the user role that is permitted to access the method. Close the implementation with a call to super.

举例,下段代码示例管理员角色可以删除缓存。

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

 

本例中,用户使用 reader 角色能够调用 aggregate 方法。

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

 

1.    创建一个 Java 类 EntitledNamedCache

2.    扩展自 WrapperNamedCache。

10-figure-19

3.    Import 以下 Java 类,WrapperNamedCache 方法使用这些类型

  • Filter
  • MapListener
  • ValueExtractor
  • Collection
  • Comparator
  • Map
  • Service
  • Set

4.    实现 EntitledNamedCache 里的方法,指定的角色可以调用方法。

具体代码见

\chapter10_Security\appClientModule\com\oracle\handson\chapter10\EntitledNamedCache.java
10-figure-20

 

10.3.2 申请缓存的权利

 

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.

新创建的类扩展 com.tangosol.net.WrapperCacheService 类。这是一个允许缓存上安全方法的便利功能。同时提供了代理和客户端间的委托机制。

实现 ensureCache、releaseCache 和 destroyCache 方法,确保只有指定用户才能使用它们。在实现里,包含调用一个带有用户参数的 SecurityExampleHelper.checkAccess 方法。下面的例子,只有 admin 才能删除缓存。

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

 

创建申请权利的应用程序:

1.    Java 类:EntitledCacheService

2.    确保类 imports 和扩展了 WrapperCacheService 类

10-figure-21

3.    实现 ensureCache、releaseCache 和 destroyCache 方法,确保只有授权用户才能使用它。

具体代码见:

chapter10_Security\appClientModule\com\oracle\handson\chapter10\EntitledCacheService.java
 10-figure-22

 

10.3.3 创建访问控制示例程序

 

创建一个运行访问控制的示例程序。角色策略被定义在 SecurityExampleHelper 类里。EntitledCacheService 和 EntitledNamedCacheclasses 确保执行这个策略。

这个程序将用不同的用户做为参数传给 SecurityHelperFile.login 方法,尝试进行缓存的 read、write 和 destroy 操作。基于定义在 EntitledCacheService 和 EntitledNamedCache 的授权策略返回操作是否成功的标识。

1.    新 Java 类:AccessControlExample

2.    实现 main 方法

3.    指定用户给 login 方法

4.    每个用户执行 read(get)、write(put) 和 destroy 操作

具体见:

\chapter10_Security\appClientModule\com\oracle\handson\chapter10\AccessControlExample.java
 10-figure-23

 

10.3.4 编辑集群侧缓存配置文件

 

修改集群侧的缓存配置文件 examples-cache-config.xml。指定缓存服务完整的类名字路径。本例中,是 com.oracle.handson.chapter10.EntitledCacheServic 代理 param-type 是 com.tangosol.net.CacheService。

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

 

10-figure-24

10.3.5 运行访问控制程序

 

1.    为 AccessControlExample.java 创建运行配置

a)    项目浏览器里,右键 AccessControlExample,Run As -> Run Configurations。

b)    新运行配置 chapter10CacheClientAccessControlExample

运行配置 chapter10CacheClientAccessControlExample
Project chapter10_Security
Main Class com.oracle.handson.chapter10.AccessControlExample
10-figure-25

 

c)    Coherence 选卡

Client缓存配置 appClientModule/client-cache-config.xml
Local storage Disabled (cache client)
Cluster Port 3155
10-figure-26

 

d)    Classpath 选卡

10-figure-27

2.    停止所有运行的缓存服务器

3.    运行安全代理服务,安全缓存服务,然后运行 AccessControlExample 程序。

a)    运行 Proxy:chapter10CacheServerSecurityRunProxy

b)    运行 CacheServer:chapter10CacheServerSecurityCacheServer

c)    运行 Client: chapter10CacheClientAccessControlExample

10-figure-28

安全代理的控制台有如下输出

10-figure-29

10.4 调用对象的基于角色访问控制

 

一个调用服务集群服务能够扩展客户端执行调用集群对象的功能。这个例子演示如何通过基于角色的策略决定哪个用户可以运行调用对象。

举例,一个有 writer 角色的用户可以运行调用对象,有 reader 角色不行。在这个例子里,你创建一个简单调用对象,能够被客户端调用。所以一个调用对象必须是被序列化的。同时你要创建一个调用服务,测试哪个用户能基于角色执行服务上的方法。

同前面的例子,本例用 PasswordIdentityTransformer 类产生包含口令,用户ID和角色的安全令牌。PasswordIdentityAsserter(运行在代理上)将用来验证安全令牌,确保口令并构造相应用户 ID 和角色为主题对象。The production and assertion of the security token happens automatically.

10.4.1 创建调用对象

 

新建一个实现简单调用对象,这个对象将被有资格用于调用服务。举例,调用对象能被增加并返回整数。创建调用对象步骤如下:

1.    新建 Java类:ExampleInvocable

2.    Import Invocable 和 InvocationService 接口。因为这个类将是序列化对象,还有 import PortableObject、PofReader 和 PofWriter 类。

3.    确定 ExampleInvocable 实现了 Invocable 和 PortableObject 的方法。

4.    实现 ExampleInvocable 类,增加技术并返回结果。

5.    实现 PofReader.readExternal 和 PofWriter.writeExternal 方法。

10.4.2 创建授权调用服务

 

这个例子演示远程调用服务被封装提供成访问控制。访问授权单元 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.

这个类扩展自 com.tangosol.net.WrapperInvocationService 类,这是一个 This is a convenience function that enables you to secure the methods onInvocationService. It also provides a mechanism to delegate between the invocation service on the proxy and the client request.

实现一个授权调用服务:

1.    新建 Java 类:EntitledInvocationService

2.    Import Invocable、InvocationObserver、InvocationService、WrapperInvocationService、Map 和 Set 接口。确保 EntitledInvocationService 类扩展 extends WrapperInvocationService 类。

3.    实现 query 和 execute 方法。在实现中,包含调用 SecurityExampleHelper.checkAccess 方法却决定是否有合适的用户角色,本例ROLE_WRITER 可以访问这些操作。

4.    下面是代码示例 EntitledInvocationService

chapter10_Security\appClientModule\com\oracle\handson\chapter10\EntitledInvocationService.java

10.4.3 新建访问调用服务的示例程序

 

新建一个实例程序,运行访问调用服务。这个程序的目的是测试定义在 SecurityExampleHelper 的不同用户能够访问和运行调用服务。The enforcement of the role-based policies is provided by the EntitledInvocationService class.

创建一个运行访问调用服务端示例程序,步骤如下:

1.    建一个有 main 方法的 java 程序:AccessInvocationServiceExample

2.    Import 如下类或接口 ExampleInvocable、CacheFactory 和 InvocationService

3.    实现 main 方法,调用 accessInvocationService 方法。

4.    实现 accessInvocationService 类,使用不同用户尝试登录

示例程序见:

chapter10_Security\appClientModule\com\oracle\handson\chapter10\AccessInvocationServiceExample.java

10.4.4 编辑集群侧的配置文件

 

编辑 examples-cache-config.xml 文件,invocation 服务增加 proxy-config 的全路径。本例中,invocation 服务的类名是 com.oracle.handson.EntitledInvocationService,其参数类型是 com.tangosol.net.InvocationService。

10-figure-30

10-figure-31

10.4.5 新建一个 POF 配置

 

创建一个 POF 文件,声明 ExampleInvocable 作为一个用户类型。

1.    打开 pof-config.xml 文件

2.    声明 ExampleInvocable 为一个用户对象,并保存文件。

10-figure-32

10-figure-33

10.4.6 为缓存服务编辑运行配置

 

用户配置的 POF 配置文件必须先于定义在 coherence.jar 里的文件,确保优先做类加载 (classloader)。如果没有,客户端 POF 文件将被默认在 coherence.jar 里的文件所默认忽略。

确保应用使用的配置文件在 chapter10_Security\appModule 下,确保 Coherence12.1.2 库不出现在根启动的服务器 classpath 上。同样, coherence.jar 文件放在 chapter10_Security 用户目录后。

如果 Coherence12.1.2 库出现在服务器类路径的 Bootstrap 上,按照如下步骤删除:

1.    右键点工程,Run As->Run Configurations。

2.    复制 chapter10CacheServerSecurityRunProxy,命名为 chapter10CacheServerSecurityRunProxyII。移除 Coherence 12.1.2。点 Add External jars,选择 Oracle_Home\cohrence\lib 目录下的 coherence.jar

运行配置 chapter10CacheServerSecurityRunProxyII
Project chapter10_Security
Main Class com.tangosol.net.DefaultCacheServer
10-figure-34

 

3.    复制 chapter10CacheServerSecurityCacheServer,更名为 chapter10CacheServerSecurityCacheServerII。移除 Coherence 12.1.2。点 Add External jars,选择 Oracle_Home\cohrence\lib 目录下的 coherence.jar

10.4.7 运行访问调用服务示例

 

基于用户角色,运行访问调用服务端例子演示调用对象时是否被许可或拒绝:

1.    为 AccessInvocationServiceExample 创建一个运行配置

a)    右键 AccessInvocationServiceExample,Run As->Run Configurations。

b)    选 Oracle Coherence,新建 configuration。

name chapter10CacheClientAccessInvocationService
Main Class com.oracle.handson.chapter10.AccessInvocationServiceExample
Coherence
Cache 配置 client-cache-config.xml
Local storage Disabled (cache client)
Classpath
remove Coherence12.1.2
Add External Jars coherence.jar
10-figure-35

 

2.    停止所有运行的缓存服务

3.    运行 proxy,运行 cache,然后运行 chapter10CacheClientAccessInvocationService。

a)    运行 Proxy:chapter10CacheServerSecurityRunProxyII

b)    运行 CacheServer:chapter10CacheServerSecurityCacheServerII

c)    运行 Client: chapter10CacheClientAccessInvocationService

chapter10CacheClientAccessInvocationService输出如下

10-figure-36

chapter10CacheServerSecurityRunProxyII 输出如下

10-figure-37

关于作者

 niehao

金辉,在 Oracle 负责云和中间件产品的资深售前顾问和团队经理,有十多年的中间件和项目管理经验,专注在企业云构建,企业集成解决方案领域,熟悉业内主要的 SOA 集成产品。参加过北京马拉松和 TNF50 越野比赛。你可以通过邮件 arthur.jin@oracle.com 与他联系。