![[z/OS]](../images/ngzos.gif)
定制系统授权工具映射模块
可以通过编写定制的登录映射模块来定制 Java™ 认证和授权 (JAAS) 登录配置。
注: 如果要使用 SAF 分布式身份映射功能,那么不需要配置映射模块。
WebSphere® Application Server ltpaLoginModule 模块和 AuthenLoginModule 模块使用共享状态来保存状态信息,并且允许 LoginModules 修改状态信息。ltpaLoginModule 使用以下代码初始化 login() 方法中的回调数组。仅当未在共享状态区域中定义数组时,才由 ltpaLoginModule 创建回调数组。
在以下代码示例中,将依赖 Java Platform, Enterprise Edition (Java EE) 标识的可用性来控制到系统授权工具 (SAF) 标识的映射。此代码使用共享状态中的 Constants.WSPRINCIPAL_KEY 值。此值由 WebSphere Application Server 1 登录模块放入代码中。您可以将定制 LoginModule 插入到 ltpaLoginModule 模块后,MapPlatformSubject 模块前。如果执行此操作,请使用回调数组或其他共享状态值以获取一个值,该值用于控制到 z/OS® 用户标识的映射。
以下是样本 SAFMappingModule:
//
// This program may be used, run, copied, modified and
// distributed without royalty for the purpose of developing,
// using, marketing, or distributing.
//
//
package com.ibm.websphere.security;
import com.ibm.websphere.security.auth.CredentialDestroyedException;
import com.ibm.websphere.security.auth.WSPrincipal;
import com.ibm.websphere.security.cred.WSCredential;
import com.ibm.wsspi.security.auth.callback.Constants;
import com.ibm.wsspi.security.token.AttributeNameConstants;
import java.lang.reflect.Array;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.CredentialExpiredException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
/**
*
* SampleSAFMappingModule demonstrates a custom login module
* that maps the existing WSPrincipal from the shared state to a z/OS
* user id.
*
*
*
* The following values will be set into the shared state if authentication
* succeeds. If authentication fails, this login module will still indicate
* success, but no values are set into the shared state.
*
* AttributeNameConstants.ZOS_USERID
* AttributeNameConstants.ZOS_AUDIT_STRING
* AttributeNameConstants.CALLER_PRINCIPAL_CLASS
*
* This login module does not use any callbacks, nor does it modify the Subject
* in any way.
*
* @author IBM Corporation
* @version 1.0
* @since 1.0
*/
public class SampleSAFMappingModule implements LoginModule
{
/*
* Constant that represents the name of this mapping module. Whenever this sample
* code is used to create a class with a different name, this value should be changed.
*
* By default, this value is used as part of the sample audit token, and for debugging
* purposes.
*/
private final static String MAPPING_MODULE_NAME = "com.ibm.websphere.security.SampleSAFMappingModule";
/*
* Constant that represents the maximum length of the ZOS_USERID. Current MVS naming
* restrictions limit this to eight characters.
*
* When the option to useWSPrincipalName is chosen, the name from the WSPrincipal is
* shortened to this many characters.
*/
private final static int MAXIMUM_NAME_LENGTH = 8;
/*
* Specifies whether or not to use this module's default mapping behavior, which is
* to use the WSPrincipal name to generate the ZOS_USERID. This depends on the
* value of the "useWSPrincipalName" option passed in from the LoginContext.
*/
private boolean useWSPrincipalName = true;
/*
* Specifies whether debugging is enabled for this Login Module. This depends on the
* value of the "debug" option passed in from the LoginContext.
*/
private boolean debugEnabled = false;
/*
* Stores the Subject passed from the LoginContext.
*/
private Subject subject;
/*
* Stores the CallbackHandler passed from the LoginContext.
*/
private CallbackHandler callbackHandler;
/*
* Stores the shared state Map passed from the LoginContext.
*/
private Map sharedState;
/*
* Stores the options Map passed from the LoginContext.
*/
private Map options;
/*
* This value is used to store the success or failure of the login() method so
* that commit() and abort() can act differently in the two cases if so desired.
*/
private boolean succeeded = false;
/**
* Construct an uninitialized mapping module object.
*/
public SampleSAFMappingModule()
{
}
/**
* Initialize this login module.
*
* This is called by the LoginContext after this login module is
* instantiated. The relevant information is passed from the LoginContext
* to this login module. If the login module does not understand any of the data
* stored in the sharedState and options parameters,
* they can be ignored.
*
* @param subject
* The subject that this LoginContext is authenticating
* @param callbackHandler
* A CallbackHandler for communicating with the end user
* to gather login information (e.g., username and password).
* @param sharedState
* The state shared with other configured login modules.
* @param options
* The options specified in the login configuration for this particular login module.
*/
public void initialize(Subject newSubject, CallbackHandler newCallbackHandler,
Map newSharedState, Map newOptions)
{
// obtain the value for debug before anything else so that tracing can be used within
// this method
if (newOptions.containsKey("debug"))
{
String debugEnabledString = (String) newOptions.get("debug");
if (debugEnabledString != null && debugEnabledString.toLowerCase().equals("true"))
{
debugEnabled = true;
}
}
if (debugEnabled)
{
debug("initialize() entry");
}
// this login module is not going to use any of these objects except for the sharedState,
// but for consistency with most login modules, we will save a reference to all of them
this.subject = newSubject;
this.callbackHandler = newCallbackHandler;
this.sharedState = newSharedState;
this.options = newOptions;
if (options.containsKey("useWSPrincipalName"))
{
String useWSPrincipalNameString = (String) options.get("useWSPrincipalName");
if (useWSPrincipalNameString != null && useWSPrincipalNameString.toLowerCase().equals("false"))
{
useWSPrincipalName = false;
}
}
if (debugEnabled)
{
debug(new Object[] { "initialize() exit", subject, callbackHandler, sharedState, options });
}
}
/**
* Method to map the WSPrincipal to a ZOS_USERID
*
* This method derives a ZOS_USERID and stores it into the Shared State for use by a later
* Login Module.
*
* @return true if the authentication succeeded, or false
* if this Login Module should be ignored
* @exception LoginException
* if the authentication fails, which is impossible for this Login Module
*/
public boolean login() throws LoginException
{
if (debugEnabled)
{
debug("login() entry");
}
if (sharedState.containsKey(AttributeNameConstants.ZOS_USERID))
{
// we don't want to override this value if, for whatever reason, another Login Module
// has already placed it into the shared state, but we still consider this a success
// because the exit criteria for this module has been met
if (debugEnabled)
{
debug("ZOS_USERID already exists: so no additional work is needed");
}
succeeded = true;
}
else if (!sharedState.containsKey(Constants.WSPRINCIPAL_KEY) ||
!sharedState.containsKey(Constants.WSCREDENTIAL_KEY))
{
// if there isn't a Principal or Credential in the shared state, we can't do
// anything so we'll return false to inform the LoginContext to ignore this module
if (debugEnabled)
{
debug("Principal or Credential is unavailable: skipping this Login Module");
}
succeeded = false;
}
else
{
if (debugEnabled)
{
debug("Principal and Credential are available: continue with login");
}
String name = null;
String audit = null;
String principalClass = null;
// extract the WSPrincipal and WSCredential from the shared state
WSPrincipal principal = (WSPrincipal) sharedState.get(Constants.WSPRINCIPAL_KEY);
WSCredential credential = (WSCredential) sharedState.get(Constants.WSCREDENTIAL_KEY);
if (useWSPrincipalName)
{
// this sample mapping module provides a method to obtain the ZOS_USERID directly
// from the WSPrincipal name if the property "useWSPrincipalName" is set to true
if (debugEnabled)
{
debug("Using name from WSPrincipal to obtain ZOS_USERID");
}
name = createName(principal);
String realm = getRealm(credential);
// for this example, a sample audit token will be created that combines the ZOS_USERID,
// the realm, and the name of this mapping module
//
// a custom audit token can be created using any available data rather than using
// this sample token
audit = realm + "/" + name + " MappingModule:" + MAPPING_MODULE_NAME;
// A Subject may contain more than one Principal. This value specifies the
// class of the Principal to be returned when the Subject is asked for the
// Caller Principal. If a custom Principal class has been placed into the
// Subject, that class name can be specified here.
//
// Two predefined values for the Caller Principal Class are provided:
//
// - AttributeNameConstants.ZOS_CALLER_PRINCIPAL_CLASS
// the z/OS Principal class
//
// - AttributeNameConstants.DEFAULT_CALLER_PRINCIPAL_CLASS
// the default Principal class
principalClass = AttributeNameConstants.DEFAULT_CALLER_PRINCIPAL_CLASS;
succeeded = true;
}
else
{
if (debugEnabled)
{
debug("Using Custom logic to obtain ZOS_USERID");
}
// if the behavior provided by this mapping module to obtain the ZOS_USERID from the
// WSPrincipal name is not enough, custom mapping logic can be provided here
//
// to use this custom mapping logic, the property "useWSPrincipalName" must be set
// to false
// name = ...custom logic
// audit = ...custom logic
// principalClass = ...custom logic
// by default, no custom mapping is provided, so the success of this code path
// is false; if custom mapping is provided, the following variable should be
// modified to represent the success or failure of the custom mapping
succeeded = false;
}
if (succeeded)
{
// now that we have values for name, audit, and principalClass, we just need to set
// them into the shared state
sharedState.put(AttributeNameConstants.ZOS_USERID, name);
sharedState.put(AttributeNameConstants.ZOS_AUDIT_STRING, audit);
sharedState.put(AttributeNameConstants.CALLER_PRINCIPAL_CLASS, principalClass);
if (debugEnabled)
{
debug(new Object[] { "Values have been stored into the shared state", name,
audit, principalClass });
}
}
}
if (debugEnabled)
{
debug("login() exit");
}
return succeeded;
}
/**
* Method to commit the authentication result.
*
* This Login Module does not need to commit any data, so we will simply return.
*
* @return true if the original login succeeded, or false
* if the original login failed
* @exception LoginException
* if the commit fails, which cannot happen in this Login Module
*/
public boolean commit() throws LoginException
{
if (debugEnabled)
{
debug("commit() entry");
}
// the return value of commit() is the same as the success of the original login
boolean returnVal = succeeded;
cleanup();
if (debugEnabled)
{
debug("commit() exit");
}
return returnVal;
}
/**
* Method to abort the authentication process (Phase 2).
*
* No matter whether our original login succeeded or failed, this method cleans up
* our state and returns.
*
* @return true if the original login succeeded, or false
* if the original login failed
* @exception LoginException
* if the abort fails, which cannot happen in this Login Module
*/
public boolean abort() throws LoginException
{
if (debugEnabled)
{
debug("abort() entry");
}
// the return value of abort() is the same as the success of the original login
boolean returnVal = succeeded;
cleanup();
if (debugEnabled)
{
debug("abort() exit");
}
return returnVal;
}
/**
* Method which logs out a Subject.
*
* Since our commit method did not modify the Subject, we don't have anything to
* logout or clean up and can just return true.
*
* @return true if the logout succeeded
* @exception LoginException
* if the logout fails, which cannot happen in the Login Module
*/
public boolean logout() throws LoginException
{
if (debugEnabled)
{
debug("logout() entry");
}
// our local variables were cleanup up during the commit, so no further cleanup is needed
if (debugEnabled)
{
debug("logout() exit");
}
// since there is nothing to logout, we always succeed
return true;
}
/*
* Cleans up our local variables; the only cleanup required for
* this Login Module is to set our success variable back to false.
*/
private void cleanup()
{
if (debugEnabled)
{
debug("cleanup() entry");
}
// there's nothing to cleanup, really, so just reset our success variable
succeeded = false;
if (debugEnabled)
{
debug("cleanup() exit");
}
}
/*
* Private method to print trace information. This implementation uses System.out
* to print trace information to standard output, but a custom tracing system can
* be implemented here as well.
*/
private void debug(Object o)
{
System.out.println("Debug: " + MAPPING_MODULE_NAME);
if (o != null)
{
if (o.getClass().isArray())
{
int length = Array.getLength(o);
for (int i = 0; i < length; i++)
{
System.out.println("\t" + Array.get(o, i));
}
}
else
{
System.out.println("\t" + o);
}
}
}
/*
* Private method to obtain the realm name from the Credential and return it. This
* keeps the exception handling involved with obtaining the realm name out of the main
* login() logic.
*/
private String getRealm(WSCredential credential)
{
if (debugEnabled)
{
debug("getRealm() entry");
}
String realm = null;
try
{
realm = credential.getRealmName();
if (debugEnabled)
{
debug("Got realm='" + realm + "' from credential");
}
}
catch (Exception e)
{
// getRealmName throws CredentialExpiredException and CredentialDestroyedException
if (debugEnabled)
{
debug(new Object[] { "Caught exception in getRealm: ", e });
}
realm = "UNKNOWN_REALM";
}
if (debugEnabled)
{
debug("getRealm() exit");
}
return realm;
}
/*
* Private method to generate the ZOS_USERID from the WSPrincipal name.
*/
private String createName(WSPrincipal principal)
{
if (debugEnabled)
{
debug("createName() entry");
}
String name = principal.getName();
if (debugEnabled)
{
debug("Using name='" + name + "' from principal");
}
// WSPrincipal.getName() might return REALM/NAME, so parse the String to obtain just the name
int index = name.indexOf("/") + 1; // index of the first character after the first /
if (index >= name.length())
{
// this block handles the case where the first / is the last character in the String,
// it really shouldn't happen, but if it does we can just strip it off
name = name.substring(0, index - 1);
if (debugEnabled)
{
debug("Stripping trailing / from WSPrincipal name");
}
}
else
{
// index is either 0 (if no / exists in the name), or it is the position
// after the first / in the name
//
// either way, we will take the substring from that point until the end of the string
name = name.substring(index);
}
// shorten the name if its length exceeds the defined maximum
if (name.length() > MAXIMUM_NAME_LENGTH)
{
name = name.substring(0, MAXIMUM_NAME_LENGTH);
if (debugEnabled)
{
debug("WSPrincipal name shortened to " + name);
}
}
// MVS ids are all upper case
name = name.toUpperCase();
if (debugEnabled)
{
debug("createName() exit");
}
return name;
}
}
样本映射模块在轻量级目录访问协议 (LDAP) 标识和 z/OS 标识间创建映射。LDAP 标识用作 z/OS 用户标识。如果需要另一个映射,那么可以定制此模块(如 else 子句中所示)以执行映射。然后:
- 编译 Java 代码。确保信任该代码并以对待 APF 授权的模块的同样认真态度来对待它。将从 z/OS 控制器访问缺省 Java 授权和认证服务 (JAAS) 系统登录配置。
- 如果指定与 IBM® 提供的缺省值不同的映射类,那么必须将该类安装到应用程序服务器和 Deployment Manager 的 classes 目录中。将 Java 归档 (JAR) 文件放入单元中每个节点(包括 WebSphere Application Server Network Deployment 单元中的 Deployment Manager 节点)的 WAS_HOME/classes 目录中。