![[z/OS]](../images/ngzos.gif)
LDAP から System Authorization Facility (SAF) マッピング・モジュールへの 1 対 1 のカスタム・マッピング
カスタマイズされたログイン・マッピング・モジュールを作成することによって、 Java™ Authentication and Authorization Service (JAAS) ログイン構成をカスタマイズできます。
WebSphere® Application Server for z/OS® には、LDAP クレデンシャルを持つリモート・メソッド呼び出し (RMI) インバウンド要求から System Authorization Facility (SAF) ID にマッピングを提供する機能があります。 ユース・ケースは、LDAP に構成されるすべてのプラットフォームでの WebSphere Application Server であり、RMI/IIOP 要求を SAF ユーザー・レジストリーに構成されている 2 番目のサーバーに送信します。WebSphere Application Server (リリース 5.1 以降) は、LDAP ID を表す LTPA トークンを使用して RMI 要求を、SAF に構成されている WebSphere Application Server for z/OS に送信します。以下の図は、このマッピングを示しています。

以下のサンプルは、LTPA トークンをオープンする JAAS RMI インバウンド・ログイン・モジュールです。 このサンプルでは、LDAP ユーザー ID を抽出し、cn=userID をストリップし、WSCredential を SAF ユーザー ID に設定します。2 番目のサーバーに構成されるサンプルのインバウンド・ログイン・モジュールは、WebSphere Application Server for z/OS リリース 6.1 以降でのみサポートされます。
このサンプルは、実際は、com.ibm.wsspi.security.token.AttributeNameConstants.WSCREDENTIAL_UNIQUEID が、必要な z/OS SAF ID に設定される、ハッシュ・ログイン・モジュールです。 ltpaLoginModule は、渡された com.ibm.wsspi.security.token.AttributeNameConstants.WSCREDENTIAL_UNIQUEID をチェックし、指定された ID を使用して、ユーザーに適切なセキュリティー・コンテキストをビルドします。
このサンプル・モジュールは RMI_INBOUND JAAS ログイン・モジュールとして構成されます。 また、次に ltpaLoginModule がくるリストの先頭で、このモジュールを指定する必要があります。
次のサンプルでは、SAF ID へのマッピングを制御するために、Java Platform, Enterprise Edition (Java EE) ID を使用できます。 サンプルでは、Constants.WSCREDENTIAL 値のハッシュ・ログインを共有状態で使用します。
// This program may be used, executed, 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;
import com.ibm.wsspi.security.token.WSSecurityPropagationHelper;
import com.ibm.websphere.security.auth.callback.WSCredTokenCallbackImpl;
import com.ibm.websphere.security.auth.WSLoginFailedException;
/**
* **
* SampleLTPASAFMappingModule demonstrates a custom login module
* that maps the existing LTPA Token from the shared state to a UNIQUE ID to avoid the Login. * Typical use is a inbound LTPA Token generated from a different realm (perhaps a non zOS Realm)
* into a SAF User Registry.
* The LTPA Token is used as the trusted identity token.
*
* ***
*
* @author IBM Corporation
* @version 1.0
* @since 1.0
*/
public class SampleLTPASAFMappingModule 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.SampleLTPASAFMappingModule";
private final static int MAXIMUM_NAME_LENGTH = 8;
/*
* **
* 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;
/*
* **
* Specifies the inbound realm that this Login Module is to handle. If there is no incoming
* property setting for onlyThisRealm, then this Login module will process all inbound
* request regardless of the incoming idenity realm within the LTPA token.
* ***
*/
private String onlyThisRealm = null;
/*
* **
* 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 SampleLTPASAFMappingModule()
{
}
/**
* **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");
}
// obtain the value for realm before anything else to see if this login module should
// only process a particular inbound realm or if it should process all realm. A null value
// singles all realms will be processed.
if (newOptions.containsKey("realm"))
{
onlyThisRealm = (String) newOptions.get("realm");
onlyThisRealm = onlyThisRealm.trim();
}
// 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 (debugEnabled)
{
debug(new Object[] { "initialize() exit", subject, callbackHandler, sharedState, options });
}
}
/**
*
* @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");
}
// Handle the callback
javax.security.auth.callback.Callback callbacks[] =
new javax.security.auth.callback.Callback[1];
callbacks[0] = new com.ibm.websphere.security.auth.callback.WSCredTokenCallbackImpl("");
try
{
callbackHandler.handle(callbacks);
}
catch (Exception e)
{
if (debugEnabled)
{
debug(new Object[] { "Caught exception in callbackhandler: ", e });
}
return false;
}
byte[] credToken = ((WSCredTokenCallbackImpl) callbacks[0]).getCredToken();
String uid = null;
String realm = null;
String uniqueID = null;
//This routine will only process a LTPA token. If there is no inbound token,
//then this routine will return true and allow the other LM should handle
//this request
if (credToken != null)
{
try
{
uniqueID = WSSecurityPropagationHelper.validateLTPAToken(credToken);
realm = WSSecurityPropagationHelper.getRealmFromUniqueID (uniqueID);
uid = createSAFIdenityName (uniqueID);
if (debugEnabled)
{
debug("using uniqueID: "+ uniqueID+ " inbound realm: "+ realm +
"uid: " + uid );
}
}
catch (Exception e)
{
if (debugEnabled)
{
debug(new Object[] { "Caught exception in callbackhandler: ", e });
}
return false;
}
}
else return true; // let the other LM handle this request.
//onlyThisRealm is a input property setting to see if we want this login module
//to handle only 1 inputed realm request. If onlyThisRealm is null,
//then we will process all request and continue on. If the onlyThisRealm does
//not match the inbound request extracted from LTPA, then we will let the other
//LM handle this request.
//
if ((onlyThisRealm != null) && (!realm.trim().equals(onlyThisRealm))) {
if (debugEnabled)
{
debug("inbound realm of "+realm+" does not match option realm of "+onlyThisRealm);
}
return true;
}
try {
// Retrieves the default InitialContext for this server.
javax.naming.InitialContext ctx = new javax.naming.InitialContext();
// Retrieves the local UserRegistry object.
com.ibm.websphere.security.UserRegistry reg =
(com.ibm.websphere.security.UserRegistry) ctx.lookup("UserRegistry");
// Retrieves the registry uniqueID based on the uid that is specified
// in the NameCallback.
String uniqueid = reg.getUniqueUserId(uid);
if (debugEnabled)
{
debug("uniqueid "+uniqueid);
}
// Retrieves the display name from the user registry based on the uniqueID.
String securityName = reg.getUserSecurityName(uid);
if (debugEnabled)
{
debug("securityName "+securityName);
}
// Retrieves the groups associated with this uniqueID.
java.util.List groupList = reg.getUniqueGroupIds(uid);
// Creates the java.util.Hashtable with the information that you gathered
// from the UserRegistry.
// By setting this hashtable, the LTPA login module will propertly
// setup the SAF Identity.
java.util.Hashtable hashtable = new java.util.Hashtable();
hashtable.put(com.ibm.wsspi.security.token.AttributeNameConstants.
WSCREDENTIAL_UNIQUEID, uniqueid);
hashtable.put(com.ibm.wsspi.security.token.AttributeNameConstants.
WSCREDENTIAL_SECURITYNAME, securityName);
hashtable.put(com.ibm.wsspi.security.token.AttributeNameConstants.
WSCREDENTIAL_GROUPS, groupList);
// Adds the hashtable to the shared state of the Subject.
sharedState.put(com.ibm.wsspi.security.token.AttributeNameConstants.
WSCREDENTIAL_PROPERTIES_KEY,hashtable);
}
catch (Exception e)
{
if (debugEnabled)
{
debug(new Object[] { "Caught exception in callbackhandler: ", e });
}
WSLoginFailedException e2 = new WSLoginFailedException("SampleLTPASAFMappingModule detected an error. "+e);
throw e2;
}
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);
}
}
}
/*
* **
* TODO
* Private method to generate the SAF Idenity from the LDAP identity. This routine may
* need to be modify by your installation standards to extract the SAF Idenity from
* the LDAP IDentity. OR this routine could be written to map the LDAP to SAF identity.
* ***
*/
private String createSAFIdenityName(String name)
{
if (debugEnabled)
{
debug("createSAFIdenityName() entry");
}
if (debugEnabled)
{
debug("Using name='" + name + "' from principal");
}
name = name.toUpperCase();
int index = name.indexOf("/") + 1; // index of the first character after the first /
System.out.println(index);
System.out.println(name);
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 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);
}
System.out.println(name);
if (name.indexOf("CN=") >= 0)
name = name.substring((name.indexOf("CN=")+3),name.length());
if (name.indexOf(",") > 0)
name = name.substring(0,name.indexOf(","));
// 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);
}
}
if (debugEnabled)
{
debug("createSAFIdenityName() exit");
}
return name; //return a SAF Idenity Name
}
}
サンプルのマッピング・モジュールは、LDAP クレデンシャルを持つ RMI インバウンド要求から SAF ID へのマッピングを作成します。マッピングの実行のためにこのモジュールをカスタマイズすることができます。