![[z/OS]](../images/ngzos.gif)
配置联合存储库的定制系统授权工具 (SAF) 映射模块
可以配置系统授权工具 (SAF) 映射模块,以将带有 SAF 用户注册表适配器的联合存储库用于 SAF 授权。SAF 映射模块将联合存储库中的标识映射到 SAF 标识。
开始之前
由于联合存储库可以具有用户注册表链,例如,SAF、轻量级目录访问协议 (LDAP) 或基于文件的存储库,因此映射模块可清楚地识别必须与 SAF 授权配合使用的 SAF 标识。为了通过联合存储库对操作系统注册表或其他注册表(例如,LDAP)启用 SAF 授权,SAF 标识映射是必需的。此标识映射会设置 SAF 所需的其他凭证信息。
可以通过编写定制的登录映射模块来定制 Java™ 认证和授权 (JAAS) 登录配置。WebSphere® Application Server ltpaLoginModule 模块和 AuthenLoginModule 模块使用映射模块中提供的共享状态变量,为 SAF 授权设置相应的标识。

- 如果需要使用此映射模块,那么必须编译 SampleVMMSAFMappingModule。
- 如果要使用 SAF 分布式标识映射功能,那么不必配置映射模块。
关于此任务
在以下代码示例中,依赖 Java Platform, Enterprise Edition (Java EE) 标识的可用性来控制到 SAF 标识的映射。此代码使用共享状态中的 Constants.WSPRINCIPAL_KEY 值。此值由
WebSphere Application Server 登录模块放入代码中。可以将定制登录模块插入到 ltpaLoginModule 模块之后,且刚好位于 MapPlatformSubject 模块之前。如果添加定制登录模块,请使用回调数组或其他共享状态值来获取用于控制到 z/OS® 用户标识的映射的值。
//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.wim;
import com.ibm.websphere.security.auth.WSPrincipal;
import com.ibm.websphere.security.cred.WSCredential;
import com.ibm.websphere.wim.util.PrincipalUtil;
import com.ibm.websphere.wim.exception.WIMException;
import com.ibm.websphere.wim.ras.WIMLogger;
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 java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
/**
*
* SampleVMMSAFMappingModule demonstrates a custom login module
* that maps the existing WSPrincipal from the shared state to a z/OS
* user id for a Federated Repository.
*
*
*
* 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 SampleVMMSAFMappingModule implements LoginModule
{
public final static String CLASSNAME = SampleVMMSAFMappingModule.class.getName();
private static final Logger trcLogger = WIMLogger.getTraceLogger(CLASSNAME);
boolean logEnabled = trcLogger.isLoggable(Level.FINER);
/*
* 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.wim.SampleVMMSAFMappingModule";
/*
* 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 = 7;
/*
* 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;
javax.security.auth.Subject runas_subject, caller_subject;
/*
* 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 SampleVMMSAFMappingModule()
{
}
/**
* 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 from the uniqueID obtained
// from the WSPrincipal name if the property "useWSPrincipalName" is set to true
if (debugEnabled)
{
debug("Using name from WSPrincipal to obtain ZOS_USERID");
}
final String principalName=stripRealm(principal.getName());
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
// 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;
//name=doMapUser(principalName);
name=doCustomMapUser(principalName);
succeeded = true;
audit = realm + "/" + name + " MappingModule:" + MAPPING_MODULE_NAME;
}
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/uniqueName.
*/
private String createSAFIdentityName(String name)
{
if (debugEnabled)
{
debug("createSAFIdentityName() entry "+ name);
}
name=stripRealm(name).toUpperCase();
//Get the CN.if uniqueName of format "uid=test,o=com" strip to "test".
if (name.indexOf("=") >= 0)
name = name.substring((name.indexOf("=")+1),name.length());
if (name.indexOf(",") > 0)
name = name.substring(0,name.indexOf(","));
//handle case where realm appended :<realmname>\\userid
if(name.indexOf("\\")>0){
name = name.substring((name.indexOf("\\")+1),name.length());
}
// 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/uniqueName shortened to " + name);
}
}
if (debugEnabled)
{
debug("createSAFIdentityName() exit");
}
return name; //return a SAF Idenity Name
}
/*
* Private Helper method to Strip realm information if any.
*/
private String stripRealm(String 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
{
name = name.substring(index);
}
return name;
}
/**
* Private helper method to deduce the z/OS ID to be mapped.
* You can use this when you want to map RACF users & other repository users in specific ways.
* This uses a Federated Repository util method, isRACFUser to identify if the user is from RACF registry.
*/
private String doCustomMapUser(String principalName){
boolean isRACFUser=true;
String mapID=null;
try{
isRACFUser=PrincipalUtil.isRACFUser(principalName);
}
catch(WIMException we){
debug("Exception happened while checking isRACFuser"+we);
//Handle it as per need.
}
mapID=isRACFUser==true?createSAFIdentityName(principalName):doMapUser(principalName);
return mapID;
}
/**
* Private helper method to deduce the z/OS ID to be mapped.
* You can use this directly if you want to map z/OS ID irrespective of RACF/Non-RACF users.
*
*/
private String doMapUser(String principalName){
//If loginUser belongs to other registry like LDAP make sure an MVS user exists to match the derived name.
//Or just map to any existing RACF user as in the commented line below.
// name="USER237";
//if user exists in RACF then Map that user.
String uniqueName=null;
String mapID=null;
try {
javax.naming.InitialContext ctx = new javax.naming.InitialContext();
// Retrieves the local UserRegistry object.
final com.ibm.websphere.security.UserRegistry reg = (com.ibm.websphere.security.UserRegistry) ctx.lookup("UserRegistry");
// Retrieves the registry uniqueName based on the principalName
uniqueName = reg.getUniqueUserId(principalName);
} catch ( Exception e ) {
debug("Exception thrown while getting uniqueID: "+e);
}
mapID=createSAFIdentityName(uniqueName);
return mapID;
}
}
样本映射模块在联合存储库的用户注册表标识和 z/OS 标识之间创建映射。此用户注册表标识用作 z/OS 用户标识。如果需要不同的映射,那么可以定制此模块(如作为先前代码样本一部分的 else 子句中所示)以执行映射。
注: 联合存储库实用程序方法(称为 PrincipalUtil.isRACFUser)用于标识用户是否来自 RACF® 注册表。如果用户是 RACF 用户,那么此方法返回 true。仅当在联合存储库下配置当前用户注册表时,才使用此方法。