You can customize Java™ Authentication and Authorization (JAAS) login configurations by writing a customized login mapping module.
WebSphere® Application Server for z/OS® has the ability to provide mapping from a remote method invocation (RMI) inbound request with LDAP credentials to the system authorization facility (SAF) Identity. The use case is a WebSphere Application Server on any platform that is configured to LDAP and sends a RMI/IIOP request to a second server configured to a SAF User Registry. The WebSphere Application Server (release 5.1 and higher) sends a RMI request using LTPA Token representing the LDAP identity to the WebSphere Application Server for z/OS that is configured to SAF. The following figure illustrates this mapping.
The following sample is a JAAS RMI inbound login module that opens the LTPA token, extracts the LDAP userid, strips the cn=userID, and sets the WSCredential to the SAF userid. The sample inbound login module that is configured to the second server is only supported on WebSphere Application Server for z/OS release 6.1 and higher.
The sample is really a hash login module in which the com.ibm.wsspi.security.token.AttributeNameConstants.WSCREDENTIAL_UNIQUEID is set to the desired z/OS SAF Identity. TheltpaLoginModule checks for a com.ibm.wsspi.security.token.AttributeNameConstants.WSCREDENTIAL_UNIQUEID that has been passed and uses the ID specified to build the appropriate security context for the user.
This sample module is configured as a RMI_INBOUND JAAS login module and must be specified at the top of the list followed by the ltpaLoginModule.
In the following sample, a Java 2 Platform, Enterprise Edition (J2EE) identity is available to control the mapping to a SAF identity. The sample uses the hash login of Constants.WSCREDENTIAL value in the shared state.
// 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 } }
The sample mapping module creates a mapping from an RMI inbound request with LDAP credentials to SAF identity. This module can be customized to perform the mapping.