7.6.5. Sécurité supplémentaire pour l'authentification EJB
Par défaut, lorsque vous effectuez un appel distant à un EJB déployé sur le serveur d'applications, la connexion au serveur est authentifiée et toute demande reçue via cette connexion est exécutée en utilisant les informations d'identification qui ont authentifié la connexion. L'authentification au niveau de la connexion dépend des capacités des mécanismes sous-jacents SASL (Simple Authentication and Security Layer). Plutôt que d'écrire des mécanismes SASL personnalisés, vous pouvez ouvrir et authentifier une connexion au serveur, puis ajouter les jetons de sécurité supplémentaires avant d'appeler un EJB. Cette rubrique décrit comment passer des informations supplémentaires sur la connexion existante du client pour l'authentification de l'EJB.
Procédure 7.13. Information de sécurité pour l'authentification EJB
Créer l'intercepteur côté client
Cet intercepteur doit implémenterorg.jboss.ejb.client.EJBClientInterceptor
. L'intercepteur doit passer le token de sécurité supplémentaire par le mappage de données contextuelles, par l'intermédiaire d'un appelEJBClientInvocationContext.getContextData()
. Voici un exemple de code d'intercepteur côté client qui crée un token de sécurité supplémentaire :public class ClientSecurityInterceptor implements EJBClientInterceptor { public void handleInvocation(EJBClientInvocationContext context) throws Exception { Principal currentPrincipal = SecurityActions.securityContextGetPrincipal(); if (currentPrincipal != null) { Map<String, Object> contextData = context.getContextData(); contextData.put(ServerSecurityInterceptor.DELEGATED_USER_KEY, currentPrincipal.getName()); } context.sendRequest(); } public Object handleInvocationResult(EJBClientInvocationContext context) throws Exception { return context.getResult(); } }
Pour obtenir des informations sur la façon de connecter l'intercepteur client dans une application, voir Section 7.6.6, « Utiliser un intercepteur côté client dans une application ».Créer et configurer l'intercepteur du conteneur côté serveur
Les classes d'intercepteur de conteneur sont de simples Plain Old Java Objects (POJOs). Elles utilisent@javax.annotation.AroundInvoke
pour marquer la méthode qui est invoquée lors de l'invocation sur le bean. Pour plus d'informations sur les intercepteurs de conteneur, consulter : Section 7.6.1, « Intercepteurs de conteneurs ».Créer l'intercepteur de conteneur
Cet intercepteur récupère le jeton d'authentification de sécurité du contexte et le passe au domaine JAAS (Java Authentication and Autorisation Service) pour vérification. Voici un exemple de code d'intercepteur de conteneur :public class ServerSecurityInterceptor { private static final Logger logger = Logger.getLogger(ServerSecurityInterceptor.class); static final String DELEGATED_USER_KEY = ServerSecurityInterceptor.class.getName() + ".DelegationUser"; @AroundInvoke public Object aroundInvoke(final InvocationContext invocationContext) throws Exception { Principal desiredUser = null; UserPrincipal connectionUser = null; Map<String, Object> contextData = invocationContext.getContextData(); if (contextData.containsKey(DELEGATED_USER_KEY)) { desiredUser = new SimplePrincipal((String) contextData.get(DELEGATED_USER_KEY)); Collection<Principal> connectionPrincipals = SecurityActions.getConnectionPrincipals(); if (connectionPrincipals != null) { for (Principal current : connectionPrincipals) { if (current instanceof UserPrincipal) { connectionUser = (UserPrincipal) current; break; } } } else { throw new IllegalStateException("Delegation user requested but no user on connection found."); } } ContextStateCache stateCache = null; try { if (desiredUser != null && connectionUser != null && (desiredUser.getName().equals(connectionUser.getName()) == false)) { // The final part of this check is to verify that the change does actually indicate a change in user. try { // We have been requested to use an authentication token // so now we attempt the switch. stateCache = SecurityActions.pushIdentity(desiredUser, new OuterUserCredential(connectionUser)); } catch (Exception e) { logger.error("Failed to switch security context for user", e); // Don't propagate the exception stacktrace back to the client for security reasons throw new EJBAccessException("Unable to attempt switching of user."); } } return invocationContext.proceed(); } finally { // switch back to original context if (stateCache != null) { SecurityActions.popIdentity(stateCache);; } } }
Configurer l'intercepteur du conteneur
Pour plus d'informations sur la façon de configurer les intercepteurs de conteneurs côté serveur, consulter : Section 7.6.3, « Configurer un intercepteur de conteneur ».
Créer le JAAS LoginModule
Ce module personnalisé exécute l'authentification à l'aide de l'information de la connexion authentifiée existante ainsi qu'à l'aide qu'un jeton de sécurité supplémentaire. Voici un exemple de code réduit qui utilise le jeton de sécurité supplémentaire et qui exécute l'authentification. On peut trouver l'exemple de code complet dans le quickstartejb-security-interceptors
fourni dans JBoss EAP 6.3 ou version supérieure.public class DelegationLoginModule extends AbstractServerLoginModule { private static final String DELEGATION_PROPERTIES = "delegationProperties"; private static final String DEFAULT_DELEGATION_PROPERTIES = "delegation-mapping.properties"; private Properties delegationMappings; private Principal identity; @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { addValidOptions(new String[] { DELEGATION_PROPERTIES }); super.initialize(subject, callbackHandler, sharedState, options); String propertiesName; if (options.containsKey(DELEGATION_PROPERTIES)) { propertiesName = (String) options.get(DELEGATION_PROPERTIES); } else { propertiesName = DEFAULT_DELEGATION_PROPERTIES; } try { delegationMappings = loadProperties(propertiesName); } catch (IOException e) { throw new IllegalArgumentException(String.format("Unable to load properties '%s'", propertiesName), e); } } @SuppressWarnings("unchecked") @Override public boolean login() throws LoginException { if (super.login() == true) { log.debug("super.login()==true"); return true; } // Time to see if this is a delegation request. NameCallback ncb = new NameCallback("Username:"); ObjectCallback ocb = new ObjectCallback("Password:"); try { callbackHandler.handle(new Callback[] { ncb, ocb }); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } return false; // If the CallbackHandler can not handle the required callbacks then no chance. } String name = ncb.getName(); Object credential = ocb.getCredential(); if (credential instanceof OuterUserCredential) { // This credential type will only be seen for a delegation request, if not seen then the request is not for us. if (delegationAcceptable(name, (OuterUserCredential) credential)) { identity = new SimplePrincipal(name); if (getUseFirstPass()) { String userName = identity.getName(); if (log.isDebugEnabled()) log.debug("Storing username '" + userName + "' and empty password"); // Add the username and an empty password to the shared state map sharedState.put("javax.security.auth.login.name", identity); sharedState.put("javax.security.auth.login.password", ""); } loginOk = true; return true; } } return false; // Attempted login but not successful. }
Ajouter le LoginModule personnalisé à la chaîne
Vous devez ajouter le nouveau LoginModule personnalisé à l'endroit qui convient dans la chaîne pour qu'il soit invoqué dans le bon ordre. Dans cet exemple, leSaslPlusLoginModule
doit être mis dans la chaîne avant le LoginModule qui charge les rôles par l'optionpassword-stacking
.Configurer l'ordonnancement du LoginModule par le Management CLI
Ce qui suit est un exemple de commandes de Management CLI qui enchaînent leSaslPlusLoginModule
personnalisé avant que le LoginModuleRealmDirect
définisse l'optionpassword-stacking
./subsystem=security/security-domain=quickstart-domain:add(cache-type=default)
/subsystem=security/security-domain=quickstart-domain/authentication=classic:add
/subsystem=security/security-domain=quickstart-domain/authentication=classic/login-module=DelegationLoginModule:add(code=org.jboss.as.quickstarts.ejb_security_plus.SaslPlusLoginModule,flag=optional,module-options={password-stacking=useFirstPass})
/subsystem=security/security-domain=quickstart-domain/authentication=classic/login-module=RealmDirect:add(code=RealmDirect,flag=required,module-options={password-stacking=useFirstPass})
Configurer l'ordonnancement du LoginModule manuellement
Ce qui suit est un exemple de XML qui configure l'ordonnancement du LoginModule dans le sous-système desecurity
du fichier de configuration du serveur. LeSaslPlusLoginModule
doit précéder le LoginModuleRealmDirect
pour qu'il puisse vérifier l'utilisateur distant avant que les rôles utilisateurs ne soient chargés et l'optionpassword-stacking
définie.<security-domain name="quickstart-domain" cache-type="default"> <authentication> <login-module code="org.jboss.as.quickstarts.ejb_security_plus.SaslPlusLoginModule" flag="required"> <module-option name="password-stacking" value="useFirstPass"/> </login-module> <login-module code="RealmDirect" flag="required"> <module-option name="password-stacking" value="useFirstPass"/> </login-module> </authentication> </security-domain>
Créer le client distant
Dans l'exemple de code suivant, on assume que le fichieradditional-secret.properties
auquel accède le JAAS LoginModule ci-dessus contient la propriété suivante :quickstartUser=7f5cc521-5061-4a5b-b814-bdc37f021acc
Le code suivant montre comment créer un token de sécurité et comment le définir avant l'appel EJB. Le token secret est codé en dur dans des buts de démonstration uniquement. Ce client se contente d'afficher les résultats sur la console.import static org.jboss.as.quickstarts.ejb_security_plus.EJBUtil.lookupSecuredEJB; public class RemoteClient { /** * @param args */ public static void main(String[] args) throws Exception { SimplePrincipal principal = new SimplePrincipal("quickstartUser"); Object credential = new PasswordPlusCredential("quickstartPwd1!".toCharArray(), "7f5cc521-5061-4a5b-b814-bdc37f021acc"); SecurityActions.securityContextSetPrincipalCredential(principal, credential); SecuredEJBRemote secured = lookupSecuredEJB(); System.out.println(secured.getPrincipalInformation()); } }