![[z/OS]](../images/ngzos.gif)
Modules de mappage SAF (System Authorization Facility) personnalisés
Vous pouvez personnaliser les configurations de connexion JAAS (Java™ Authentication and Authorization) en écrivant un module de mappage de connexions personnalisé.
Les modules WebSphere Application Server ltpaLoginModule et AuthenLoginModule utilisent l'état partagé pour enregistrer des informations d'état, et c'est le module LoginModules qui a la possibilité de modifier les informations d'état. Le module ltpaLoginModule initialise l'ensemble des appels de la méthode login() en utilisant le code suivant. L'ensemble d'appels ne peut être créé par le module ltpaLoginModule que si celui-ci n'est pas défini dans la zone d'état partagé.
Dans l'exemple de code suivant, la dépendance s'établit sur la disponibilité d'une identité Java EE (Java Platform, Enterprise Edition) pour contrôler le mappage vers une identité SAF (System Authorization Facility). Ce code utilise la valeur Constants.WSPRINCIPAL_KEY à l'état partagé. Cette dernière est placée dans le code par un module de connexion WebSphere Application Server 1. Vous pouvez insérer un module LoginModule personnalisé après le module ltpaLoginModule et juste avant le module MapPlatformSubject. Dans ce cas, utilisez un ensemble d'appels ou d'autres valeurs à l'état partagé afin d'obtenir une valeur permettant de contrôler le mappage vers l'ID utilisateur z/OS.
//
// Ce programme peut être utilisé, exécuté, copié, modifié et
// distribué sans paiement d'une redevance à des fins de développement,
// d'utilisation, de marketing ou de distribution.
//
//
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 représente un module de connexion personnalisé
* qui mappe le WSPrincipal existant depuis l'état partagé vers un
* ID utilisateur.
*
*
*
* Les valeurs suivantes seront définies à l'état partagé si l'authentification
* aboutit. Si l'authentification échoue, ce module de connexion indique encore
* que l'opération a abouti, mais aucune valeur n'est définie à l'état partagé.
*
* AttributeNameConstants.ZOS_USERID
* AttributeNameConstants.ZOS_AUDIT_STRING
* AttributeNameConstants.CALLER_PRINCIPAL_CLASS
*
* Ce module de connexion n'utilise pas d'appels et n'apporte aucune modification
* au sujet.
*
* @author IBM Corporation
* @version 1.0
* @since 1.0
*/
public class SampleSAFMappingModule implements LoginModule
{
/*
* Constante qui représente le nom de ce module de mappage. A chaque fois que cet exemple
* de code est utilisé pour créer une classe d'un nom différent, cette valeur doit être modifiée.
*
* Par défaut, cette valeur est utilisée comme partie intégrante de l'exemple de token d'audit et
* à des fins de débogage.
*/
private final static String MAPPING_MODULE_NAME = "com.ibm.websphere.security.SampleSAFMappingModule";
/*
* Constante qui représente la longueur maximale de ZOS_USERID. Les restrictions de
* nommage MVS en cours limitent celle-ci à huit caractères.
*
* Lorsque l'option useWSPrincipalName est choisie, le nom issu de WSPrincipal est
* raccourci à ce nombre de caractères.
*/
private final static int MAXIMUM_NAME_LENGTH = 8;
/*
* Indique s'il convient d'utiliser ce comportement de mappage par défaut du module qui
* consiste à utiliser le nom WSPrincipal pour générer ZOS_USERID. Cela dépend de la
* valeur de l'option "useWSPrincipalName" transmise à partir de LoginContext.
*/
private boolean useWSPrincipalName = true;
/*
* Indique si le débogage est activé pour ce module de connexion. Cela dépend de
* la valeur de l'option "debug" transmise à partir de LoginContext.
*/
private boolean debugEnabled = false;
/*
* Enregistre le sujet transmis à partir de LoginContext.
*/
private Subject subject;
/*
* Enregistre le CallbackHandler transmis à partir de LoginContext.
*/
private CallbackHandler callbackHandler;
/*
* Enregistre l'option Map à l'état partagé à partir de LoginContext.
*/
private Map sharedState;
/*
* Enregistre les options Map transmises à partir de LoginContext.
*/
private Map options;
/*
* Cette valeur permet d'enregistrer la réussite ou l'échec de la méthode login() afin
* que commit() et abort() puissent se comporter différemment dans les deux cas, si nécessaire.
*/
private boolean succeeded = false;
/**
* Construisez un objet de module de mappage non initialisé.
*/
public SampleSAFMappingModule()
{
}
/**
* Initialisez ce module de connexion.
*
* Il est appelé par le LoginContext après que ce module de connexion ait été
* instancié. Les informations appropriées sont transmises à partir de LoginContext
* vers ce module de connexion. Si le module de connexion ne comprend pas certaines des données
* stockées dans sharedState et dans les paramètres d'option,
* elles peuvent être ignorées.
*
* @param subject
* Le sujet authentifié par ce LoginContext
* @param callbackHandler
* Un CallbackHandler pour la communication avec l'utilisateur final
* pour collecter les informations de connexion (nom d'utilisateur et mot de passe).
* @param sharedState
* L'état partagé avec d'autres modules de connexion configurés.
* @param options
* Les options spécifiées dans la configuration de connexion pour ce module de connexion.
*/
public void initialize(Subject newSubject, CallbackHandler newCallbackHandler,
Map newSharedState, Map newOptions)
{
// obtenir avant toute la valeur de débogage pouvant être utilisée dans cette
// méthode
if (newOptions.containsKey("debug"))
{
String debugEnabledString = (String) newOptions.get("debug");
if (debugEnabledString != null && debugEnabledString.toLowerCase().equals("true"))
{
debugEnabled = true;
}
}
if (debugEnabled)
{
debug("initialize() entry");
}
// Ce module de connexion ne va utiliser aucun de ces objets, sauf dans le cas de sharedState,
// mais une référence à ceux-ci sera enregistrée, dans un souci de cohérence avec la plupart des modules de connexion
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 });
}
}
/**
* Méthode permettant de mapper WSPrincipal vers ZOS_USERID
*
* Cette méthode est dérivée d'un ZOS_USERID et enregistre ce dernier à l'état partagé pour
* une utilisation avec le module de connexion.
*
* @return true si l'authentification aboutit ou false
* si ce module de connexion doit être ignoré
* @exception LoginException
* Si l'authentification échoue, ce qui est impossible pour ce module de connexion
*/
public boolean login() throws LoginException
{
if (debugEnabled)
{
debug("login() entry");
}
if (sharedState.containsKey(AttributeNameConstants.ZOS_USERID))
{
// nous ne souhaitons pas remplacer cette valeur si, pour une raison quelconque, un autre module de connexion
// l'a déjà placée à l'état partagé, mais nous considérons encore cela comme étant un succès
// car le critère d'exit a été rempli pour ce module
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))
{
// si aucun Principal ou Credential n'est à l'état partagé, rien ne peut être mis en oeuvre
// et la valeur false est renvoyée pour indiquer à LoginContext qu'il convient d'ignorer ce 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;
// extraire WSPrincipal et WSCredential à l'état partagé
WSPrincipal principal = (WSPrincipal) sharedState.get(Constants.WSPRINCIPAL_KEY);
WSCredential credential = (WSCredential) sharedState.get(Constants.WSCREDENTIAL_KEY);
if (useWSPrincipalName)
{
// cet exemple de module de mappage fournit une méthode pour l'obtention directe de ZOS_USERID
// à partir du nom WSPrincipal si la propriété "useWSPrincipalName" est définie sur true
if (debugEnabled)
{
debug("Using name from WSPrincipal to obtain ZOS_USERID");
}
name = createName(principal);
String realm = getRealm(credential);
// pour cet exemple, un exemple de jeton d'audit sera créé ; il combine le ZOS_USERID,
// le domaine et le nom de ce module de mappage
//
// un jeton d'audit personnalisé peut être créé en utilisant les données disponibles plutôt que
// cet exemple de jeton
audit = realm + "/" + name + " MappingModule:" + MAPPING_MODULE_NAME;
// Un sujet peut contenir plusieurs principaux. Cette valeur indique la
// classe du principal à renvoyer lorsqu'il est demandé au sujet de fournir
// le principal de l'appelant. Si une classe de principal personnalisée a été
// placée dans le sujet, ce nom de classe peut être spécifié ici.
//
// Deux valeurs prédéfinies sont fournies pour la classe du principal de l'appelant :
//
// - AttributeNameConstants.ZOS_CALLER_PRINCIPAL_CLASS
// classe du principal z/OS
//
// - AttributeNameConstants.DEFAULT_CALLER_PRINCIPAL_CLASS
// classe du principal par défaut
principalClass = AttributeNameConstants.DEFAULT_CALLER_PRINCIPAL_CLASS;
succeeded = true;
}
else
{
if (debugEnabled)
{
debug("Using Custom logic to obtain ZOS_USERID");
}
// si le comportement fourni par ce module de mappage pour l'obtention du ZOS_USERID à partir
// du nom WSPrincipal n'est pas suffisant, la logique de mappage personnalisé peut être fournie ici
//
// pour qu'il soit possible d'utiliser cette dernière, la propriété "useWSPrincipalName"
// doit être définie à la valeur false
// name = ...custom logic
// audit = ...custom logic
// principalClass = ...custom logic
// par défaut, aucun mappage personnalisé n'est fourni, la réussite de ce chemin de code
// est false ; si ce mappage est fourni, la variable suivante doit être modifiée afin de
// représenter la réussite ou l'échec du mappage personnalisé
succeeded = false;
}
if (succeeded)
{
// maintenant que vous disposons de valeurs pour le nom, l'audit, et principalClass, il
// convient simplement de les définir à l'état partagé
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;
}
/**
* Méthode de validation du résultat de l'authentification.
*
* Ce module de connexion n'a pas besoin de valider les données ; il les renvoie uniquement.
*
* @return true si la connexion d'origine a abouti, ou false
* si elle a échoué
* @exception LoginException
* si la validation échoue, ce qui ne peut pas arriver dans ce module de connexion
*/
public boolean commit() throws LoginException
{
if (debugEnabled)
{
debug("commit() entry");
}
// la valeur de commit() renvoyée est la même que la réussite de la connexion d'origine
boolean returnVal = succeeded;
cleanup();
if (debugEnabled)
{
debug("commit() exit");
}
return returnVal;
}
/**
* Méthode permettant d'abandonner le processus d'authentification (phase 2).
*
* Que la connexion d'origine ait abouti ou échoué, cette méthode nettoie l'état
* et renvoie les données.
*
* @return true si la connexion d'origine a abouti, ou false
* si elle a échoué
* @exception LoginException
* si l'abandon échoue, ce qui ne peut pas arriver dans ce module de connexion
*/
public boolean abort() throws LoginException
{
if (debugEnabled)
{
debug("abort() entry");
}
// la valeur d'abort() renvoyée est la même que la réussite de la connexion d'origine
boolean returnVal = succeeded;
cleanup();
if (debugEnabled)
{
debug("abort() exit");
}
return returnVal;
}
/**
* Méthode permettant de déconnecter un sujet.
*
* La méthode commit n'ayant pas modifié le sujet, il n'y a rien à déconnecter ou à * nettoyer et seule la valeur true peut être renvoyée.
*
* @return true si la déconnexion a abouti
* @exception LoginException
* si la déconnexion échoue, ce qui ne peut pas arriver dans ce module de connexion
*/
public boolean logout() throws LoginException
{
if (debugEnabled)
{
debug("logout() entry");
}
// les variables locales ont été nettoyées au cours de la validation, aucun nettoyage supplémentaire n'est nécessaire
if (debugEnabled)
{
debug("logout() exit");
}
// étant que rien ne peut être déconnecté, la procédure aboutit toujours
return true;
}
/*
* Nettoie les variables locales ; le seul nettoyage requis pour ce module de connexion
* consiste à redéfinir la valeur de réussite à la valeur false.
*/
private void cleanup()
{
if (debugEnabled)
{
debug("cleanup() entry");
}
// rien ne peut être nettoyé ; il suffit donc de redéfinir la variable de réussite
succeeded = false;
if (debugEnabled)
{
debug("cleanup() exit");
}
}
/*
* Méthode privée permettant d'imprimer les informations de trace. Cette implémentation utilise System.out
* pour imprimer les informations de trace dans la sortie standard, mais un système de traçage personnalisé peut
* être implémenté à cet emplacement également.
*/
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);
}
}
}
/*
* Méthode privée permettant d'obtenir le nom de domaine à partir du justificatif et de le renvoyer. Permet
* de prendre en compte le traitement de l'exception lors de l'extraction du nom de domaine à partir
* de la logique principale login().
*/
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 lance CredentialExpiredException et CredentialDestroyedException
if (debugEnabled)
{
debug(new Object[] { "Caught exception in getRealm: ", e });
}
realm = "UNKNOWN_REALM";
}
if (debugEnabled)
{
debug("getRealm() exit");
}
return realm;
}
/*
* Méthode privée permettant de générer le ZOS_USERID à partir du nom WSPrincipal.
*/
private String createName(WSPrincipal principal)
{
if (debugEnabled)
{
debug("createName() entry");
}
String name = principal.getName();
if (debugEnabled)
{
debug("Using name='" + name + "' from principal");
}
// WSPrincipal.getName() peut renvoyer NOM/DOMAINE ; il convient donc d'analyser la chaîne pour obtenir le nom uniquement
int index = name.indexOf("/") + 1; // index du premier caractère après la première /
if (index >= name.length())
{
// ce bloc gère le cas de figure où la première / est le dernier caractère de la chaîne ;
// cela ne doit pas se produire, mais dans le cas contraire, il suffit de le supprimer
name = name.substring(0, index - 1);
if (debugEnabled)
{
debug("Stripping trailing / from WSPrincipal name");
}
}
else
{
// l'index correspond à 0 (si aucune / n'apparaît dans le nom) ou à sa position
// après la première / dans le nom
//
// dans les deux cas, nous prendrons la sous-chaîne à partir de ce point jusqu'à la fin de la chaîne
name = name.substring(index);
}
// raccourcir le nom si sa longueur dépasse la limite définie
if (name.length() > MAXIMUM_NAME_LENGTH)
{
name = name.substring(0, MAXIMUM_NAME_LENGTH);
if (debugEnabled)
{
debug("WSPrincipal name shortened to " + name);
}
}
// Les ID MVS sont tous en majuscules
name = name.toUpperCase();
if (debugEnabled)
{
debug("createName() exit");
}
return name;
}
}
- Compilez le code Java. Assurez-vous que le code est digne de confiance et traité avec le même soin qu'un module autorisé par APF. La configuration de connexion du système JAAS (Java Authorization and Authentication Service) par défaut est accessible à partir du contrôleur z/OS.
- Si vous indiquez une classe de mappage autre que celle par défaut fournie par IBM®, vous devez l'installer dans le répertoire des classes du serveur d'applications et des gestionnaires de déploiement. Placez le fichier d'archive Java (JAR) dans le répertoire WAS_HOME/classes de chaque noeud de la cellule, y compris le noeud du gestionnaire de déploiement d'une cellule WebSphere Application Server, Network Deployment.