カスタマイズされたログイン・マッピング・モジュールを作成することによって、 Java Authentication and Authorization Service (JAAS) ログイン構成をカスタマイズできます。
WebSphere Application Server ltpaLoginModule モジュールおよび AuthenLoginModule モジュールは、 共用状態を使用して、LoginModule が状態情報を変更できるようにする機能により、状態情報を保管します。 ltpaLoginModule は次のコードを使用して、 login() メソッドでコールバック配列を初期化します。 コールバック配列は、配列が共用状態の領域で定義されない場合のみ、ltpaLoginModule によって作成されます。
次のコード例では、Java 2 Platform, Enterprise Edition (J2EE) ID の可用性に依存して、System Authorization Facility (SAF) ID に対するマッピングを制御します。 このコードでは、共用状態において Constants.WSPRINCIPAL_KEY 値を使用しています。 この値は、WebSphere Application Server 1 ログイン・モジュールによりコード内に配置されます。 カスタムの LoginModule は、ltpaLoginModule モジュールの後、さらに MapPlatformSubject モジュールの直前に挿入できます。 これを行う場合は、コールバック配列または他の共用状態の値を使用して、z/OS ユーザー ID へのマッピングの制御に使用する値を取得します。
// // 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; } }