Affichage des articles dont le libellé est EJB. Afficher tous les articles
Affichage des articles dont le libellé est EJB. Afficher tous les articles

vendredi 18 février 2011

MDB: utiliser des EJB sécurisés







Allez encore un petit billet sur la sécurité et les MDB.

Lorsqu'un MDB reçoit un message, il peut arriver (et même dans la plupart des cas!) qu'il doive consommer des services qui sont exposés par d'autres EJB. Supposons que l'EJB en question comprenne un contrôle d'accès sur ses méthodes:





@Stateless
@Local(LocalMyService.class)
@DeclareRoles("authenticated")
@RolesAllowed("authenticated")
public class MyServiceImpl implements LocalMyService {




... et que ce service est injecté dans un MDB:





@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "user",
propertyValue = "mdbuser"),
@ActivationConfigProperty(propertyName = "password",
propertyValue = "mdbpassword"),
@ActivationConfigProperty(propertyName = "acknowledgeMode",
propertyValue = "Auto-acknowledge"),
@ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destination",
propertyValue = "/queue/myQueue")
})
public class MyMDB implements MessageListener {
@EJB
private LocalMyService service;

@Override
public void onMessage(Message message) {
[...]
service.myMethod();










Là l'affaire se corse car les messages JMS n'embarquent pas de contexte de sécurité (après recherche et sauf erreur de ma part), et lors de la réception d'un message le MDB se verra l'accès refusé au service. Et bien c'est
presque pas grave, car la spec EJB couvre ce problème et permet la substitution de rôle avec l'annotation javax.annotation.security.@RunAs qui prend en paramètre le nom du rôle à donner au bean (ici le MDB):








@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "user",
propertyValue = "mdbuser"),
@ActivationConfigProperty(propertyName = "password",
propertyValue = "mdbpassword"),
@ActivationConfigProperty(propertyName = "acknowledgeMode",
propertyValue = "Auto-acknowledge"),
@ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destination",
propertyValue = "/queue/myQueue")
})
@RunAs("authenticated")
public class MyMDB implements MessageListener {
@EJB
private LocalMyService service;

@Override
public void onMessage(Message message) {
[...]
service.myMethod();













Il est intéressant de noter que j'ai écrit "
presque pas grave"! Presque, car le conteneur JEE sélectionné pour le projet en question était JBoss 6.0.0.Final qui est directement impacté par le bug 
EJBTHREE-1945. Le sujet de bug est que le rôle est correctement substitué dans le premier appel de méthode sécurisé, mais si cette méthode fait elle-même appel à une autre méthode sécurisée , le rôle est perdu... autant dire que tout l'intérêt de RunAs est caduque! 






J'ai donc demandé s'il existait un moyen de contournement et je me suis inspiré de la réponse pour mon besoin en créant un intercepteur assigné au MDB qui authentifie le MDB avant l'invocation de la méthode onMessage:









public class RunAsInterceptor {
@AroundInvoke
public Object intercept(InvocationContext ctx) throws Throwable {
SecurityClient securityClient = null;

try {
securityClient = SecurityClientFactory.getSecurityClient();
securityClient.setSimple(userName, password);
securityClient.login();

return ctx.proceed();

} finally {
if (securityClient != null) {
securityClient.logout();
}
}
}
}







L'ombre au tableau est qu'une classe hors spec JEE issue de l'API propre à JBoss est impliquée (SecurityClient), je n'ai pas poussé pour trouver un moyen d'authentification avec les outils JEE stricts, si quelqu'un a ça en stock, je suis preneur! Je vous laisse le soin de définir comment paramétrer userName et password, moi je l'ai fait avec le chargement de propriétés dans le constructeur sans arguments. 






L'utilisation dans le MDB:






@MessageDriven(activationConfig = {
[...]
})
@Interceptors({RunAsInterceptor.class})
public class MyMDB implements MessageListener {



L'avantage de cette conception est que lorsque les gens de JBoss se décideront à résoudre ce problème qui date de 2009, il n'y aura qu'à dégager l'intercepteur et le remplacer par @Runas!



Mais bon, c'est la tour de Babel!


Message Driven Bean: configuration de l'accès à une Destination sécurisée









Pour des besoins évidents, on m'a demandé de sécuriser l'accès aux Destinations JMS (Queue/Topic), c'est à dire les composants doivent s'authentifier pour pouvoir publier ou consommer des  message.

Quand la connexion est créée programmatiquement, pas de soucis:




connectionFactory.createConnection(jmsUserName, jmsPassword);


Toutefois, la question s'est posée lorsque j'ai du déployer un MDB et après avoir fouillé dans la JSR EJB spec 3.1, je n'ai pas trouvé comment indiquer dans les méta données les valeurs user et password. Puis en élargissant le champ de mes recherches avec Google, je suis tombé sur la config suivante qui répond au besoin:




@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "user",
propertyValue = "mdbuser"),
@ActivationConfigProperty(propertyName = "password",
propertyValue = "mdbpassword"),
@ActivationConfigProperty(propertyName = "acknowledgeMode",
propertyValue = "Auto-acknowledge"),
@ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destination",
propertyValue = "/queue/myQueue")
})
public class MyMDB implements MessageListener {
D'accord, c'était quasi-évident... mais comme je trouve que ça manque un peu de documentation, je remets une couche!


vendredi 1 juin 2007

WebServices EJB 3: Trop fastoche




Bon alors on va commencer par un petit tuto pour développer et publier un service web à l'aide des possibilités offertes par Java et les EJB 3, contrairement à ce qui était précedemment nécessaire, c'est très rapide et super facile... (ok ça fait un peu recette de cuisine)



Pour rappel, au niveau de l'architecture, un conteneur d'application va héberger notre service et son implémentation est assurée par un EJB sans etat (stateless).



En ce qui concerne les pré-requis, vous devez vous assurer de disposer de:

  • Le JDK dans sa version 1.5

  • JBoss IDE 2 beta 2 (bon jamais la version stable??) disponible en bundle ici

  • JBoss AS 4.2.0GA en téléchargement ici

Une fois que tout est installé, on va commencer par ouvrir JBoss IDE dans un workspace vierge et y intégrer l'installation de JBoss AS:



  1. Ouvrir la perspective JBoss AS

  2. Dans la vue "JBoss Server" : clic droit -> New server

  3. Sélectionner JBoss Inc/JBoss AS 4.0:

  4. Donner l'emplacement du serveur et sélectionner la configuration (default sera très bien pour notre tuto):

  5. Dans la vue "JBoss Server" : clic droit sur le serveur créé et le lancer en debug, la sortie sur la console ne doit normalement pas contenir d'erreur et finir par:


12:08:14,062 INFO [Server] JBoss (MX MicroKernel) [4.2.0.GA (build: SVNTag=JBoss_4_2_0_GA date=200705111440)] Started in 47s:812ms



  1. Créons maintenant notre projet. il s'agit d'un projet EJB 3.0/EJB 3.0 Project:

Sélectionner ensuite la configuration de serveur que nou avons précédemment défini (default).



A la création du projet, nous rencontron l'erreur (de jeunesse?) suivante:




Severity and Description Path Resource Location Creation Time Id
Project TestWS is missing required library: 'E:\j2ee\jboss-4.2.0.GA\server\default\deploy\ejb3.deployer\jboss-ejb3x.jar' TestWS Build path 1180700028078 24


Ce qui signifie que le buildpath est configuré pour trouver le fichier jboss-ejb3x.jar dans un répertoire alors qu'il ne s'y trouve pas... Pas de panique, vous pouvez trouver le fuyard dans le répertoire $JBOSS_HOME/server/default/lib. Une fois le jar manquant copié dans le répertoire attendu, demander à Eclipse de rafraîchir le contenu du projet pour que la présence du fichier soit détectée et l'erreur disparaît.



  1. Afin que les bibliothèques nécessaires à la création de services web JBoss soient chargées, par un clic droit sur la racine du projet, faire apparaître le menu contextuel et cliquer sur JBossWS/Add JBossWS nature. Une boîte de dialogue apparaît, les options par défaut conviennent à notre exemple:

  2. Créons maintenant une classe de base Hello dans le package org.tbt.wstuto:


package
org.tbt.wstuto;



public class Hello {

}


  1. Maintenant, nous allons un peu l'agrémenter en utilisant les annotations et en ajoutant une méthode pour notre service:


package
org.tbt.wstuto;



import javax.ejb.Stateless;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;

@Stateless // C'est un EJB sans état
@WebService // C'est un service Web
public class Hello {
@WebMethod
public int world(@WebParam(name="param")int iParam){
// Ca c'est spéciale dédicace pour VLT!
System.out.println("Goodbye cruel world...");
return 0;
}
}




  1. Nous allons maintenant configurer l'IDE pour créer notre jar gràce a la packaging configuration définie comme suit:

  2. Demander de créer le package par la commande Project/Run packaging configuration et le jar apparaît dans l'arborescence de votre projet.

  3. Reste maintenant à déployer votre service sur le conteneur: en perspective JBoss AS, faire glisser le jar sur notre serveur default dans la vue JBoss server afin de le faire apparaître dans les modules. Pour finir demander la publication du jar:

Dans la console, devrait apparaître:


14:48:40,640 INFO [TomcatDeployer] deploy, ctxPath=/HelloService, warUrl=.../tmp/deploy/TestWS.jar-ws46100.war/
14:48:41,109 INFO [JmxKernelAbstraction] creating wrapper delegate for: org.jboss.ejb3.stateless.StatelessContainer
14:48:41,140 INFO [JmxKernelAbstraction] installing MBean: jboss.j2ee:jar=TestWS.jar,name=Hello,service=EJB3 with dependencies:
14:48:41,625 INFO [EJBContainer] STARTED EJB: org.tbt.wstuto.Hello ejbName: Hello
14:48:41,671 INFO [EJB3Deployer] Deployed: file:/E:/j2ee/jboss-4.2.0.GA/server/default/deploy/TestWS.jar
14:48:41,703 INFO [WSDLFilePublisher] WSDL published to: file:/E:/j2ee/jboss-4.2.0.GA/server/default/data/wsdl/TestWS.jar/HelloService46098.wsdl
14:48:41,781 INFO [ServiceEndpointManager] WebService started: http://localhost:8080/HelloService/Hello






Nous pouvons vérifier la présence de votre service gràce à JBoss WS
http://localhost:8080/jbossws/services :






Registered Service Endpoints

















ServiceEndpointID jboss.ws:context=HelloService,endpoint=Hello
ServiceEndpointAddress http://localhost:8080/HelloService/Hello?wsdl


































StartTime StopTime
Fri Jun 01 14:48:41 CEST 2007

RequestCount ResponseCount FaultCount
0 0 0
MinProcessingTime MaxProcessingTime AvgProcessingTime
0 0 0






Conclusion



C'est une bonne illustration de l'apport des annotations: nous économisons ici l'écriture d'un fichier WSDL, voire d'un fichier de déploiement WSDD. Il n'est nullement besoin non plus d'empaqueter sous forme de war, donc pas non plus de web.xml!



Trop facile...



A noter néanmoins qu'il semble que le format de l'URL du service soit figée à http://hostname/

Service/

, nous avons essayé de paramétrer un peu dans tous les sens les annotations sans résultat, si quelqu'un à les moyens d'enrichir le billet à ce sujet, n'hésitez pas...