In this document we show how to use PicketLink STS to validate SAML assertions and authenticate EJB clients.
Required software: JDK 6, PicketLink version 1.0.3 or superior. (Feature available starting 1.0.3.CR2)
Process Overview
The following picture illustrates the process of using SAML assertions to authenticate clients of EJB applications:
The client must first obtain the SAML assertion from PicketLink STS by sending a WS-Trust request to the token service. This process usually involves authentication of the client. After obtaining the SAML assertion from the STS, the client includes the assertion in the security context of the EJB request before invoking an operation on the bean. Upon receiving the invocation, the EJB container extracts the assertion and validates it by sending a WS-Trust validate message to the STS. If the assertion is considered valid by the STS (and the proof of possession token has been verified if needed), the client is authenticated.
On JBoss, the SAML assertion validation process is handled by the SAML2STSLoginModule. It reads properties from a configurable file (specified by the configFile option) and establishes communication with the STS based on these properties. We will see how a configuration file looks like later on. If the assertion is valid, a Principal is created using the assertion subject name and if the assertion contains roles, these roles are also extracted and associated with the caller's Subject.
EJB3 Integration Example
In this section we present a sample EJB3 application that authenticates clients by validating their SAML assertions with PicketLink STS. The deployments for both the EJB3 application and the STS can be found attached in this document.
EJB3 Sample App
Our EJB3 application consists of a simple stateless session bean. The session interface can be seen bellow:
/* * JBoss, Home of Professional Open Source. * Copyright 2010, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.test.security.ejb3; import java.security.Principal; /** * <p> * This is the remote interface of session beans used in the EJB3 security tests. * </p> * * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a> */ public interface SimpleSession { /** * <p> * This is a method available for regular users and administrators. Implementations must annotate either the class or * this method with {@code @RolesAllowed({"RegularUser", "Administrator"})} to enforce that only these roles should * be granted access to this method. * </p> * * @return the caller's {@code Principal}. */ public Principal invokeRegularMethod(); /** * <p> * This is a method available for administrators only. Implementations must annotate either the class or this method * with {@code @RolesAllowed({"Administrator"})} to enforce that only administrators should be granted access to * this method. * </p> * * @return the caller's {@code Principal}. */ public Principal invokeAdministrativeMethod(); /** * <p> * This is a method available for all authenticated users, regardless or role. Implementations must annotate this * method with {@code @PermitAll} to specify that all security roles should be granted access. * </p> * * @return the caller's {@code Principal}. */ public Principal invokeUnprotectedMethod(); /** * <p> * This is a method that is unavailable for everybody. Implementations must annotate this method with * {@code @DenyAll} to specify that access should be restricted for everybody. * </p> * * @return the caller's {@code Principal}. */ public Principal invokeUnavailableMethod(); }
And this is the implementation class:
/* * JBoss, Home of Professional Open Source. * Copyright 2010, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.test.security.ejb3; import java.security.Principal; import javax.annotation.Resource; import javax.annotation.security.DenyAll; import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; import javax.ejb.Remote; import javax.ejb.SessionContext; import javax.ejb.Stateless; /** * <p> * Stateless session bean implementation used in the EJB3 security tests. * </p> * * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a> */ @Stateless @Remote(SimpleSession.class) @RolesAllowed({"RegularUser", "Administrator"}) public class SimpleStatelessSessionBean implements SimpleSession { @Resource private SessionContext context; /* * (non-Javadoc) * * @see org.jboss.test.security.ejb3.SimpleSession#invokeRegularMethod() */ public Principal invokeRegularMethod() { // this method allows the same roles as the class. return this.context.getCallerPrincipal(); } /* * (non-Javadoc) * * @see org.jboss.test.security.ejb3.SimpleSession#invokerAdministrativeMethod() */ @RolesAllowed({"Administrator"}) public Principal invokeAdministrativeMethod() { // this method overrides the roles defined by the class to grant access to admnistrators only. return this.context.getCallerPrincipal(); } /* * (non-Javadoc) * * @see org.jboss.test.security.ejb3.SimpleSession#invokeUnprotectedMethod() */ @PermitAll public Principal invokeUnprotectedMethod() { // this method overrides the roles defined by the class to grant access to all roles. return this.context.getCallerPrincipal(); } /* * (non-Javadoc) * * @see org.jboss.test.security.ejb3.SimpleSession#invokeUnavailableMethod() */ @DenyAll public Principal invokeUnavailableMethod() { // this method should never be called - it overrides the class roles to deny access to all roles. return this.context.getCallerPrincipal(); } }
The session defines four methods: invokeRegularMethod (available to both Administrators and RegularUsers), invokeAdministrativeMethod (available to Administrators only), invokeUnprotectedMethod (available to all authenticated clients), and invokeUnavailableMethod (annotated with @DenyAll and thus unavailable to all roles).
Besides the sample session classes, our ejb3-sampleapp.jar contains the application policy definition for the EJBs:
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns="urn:jboss:bean-deployer:2.0"> <!-- ejb3 test application-policy definition --> <application-policy xmlns="urn:jboss:security-beans:1.0" name="ejb3-sampleapp"> <authentication> <login-module code="org.picketlink.identity.federation.bindings.jboss.auth.SAML2STSLoginModule" flag="required"> <module-option name="password-stacking">useFirstPass</module-option> <module-option name="configFile">sts-config.properties</module-option> </login-module> <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required"> <module-option name="password-stacking">useFirstPass</module-option> <module-option name="usersProperties">ejb3-sampleapp-users.properties</module-option> <module-option name="rolesProperties">ejb3-sampleapp-roles.properties</module-option> </login-module> </authentication> </application-policy> </deployment>
The policy defines two login modules: SAML2STSLoginModule and UsersRolesLoginModule. The first will be responsible for validating the assertion with the STS in order to authenticate the client, while the second will be responsible for retrieving the client's roles from a properties file. In order to validate the SAML assertions, SAML2STSLoginModule needs information about the STS, like its endpoint URL, service name, port name, etc. This information is supplied by the sts-config.properties file:
serviceName=PicketLinkSTS portName=PicketLinkSTSPort endpointAddress=http://localhost:8080/picketlink-sts-1.0.0/PicketLinkSTS username=JBoss password=JBoss
The last two properties specify the username and password that will be used to authenticate the JBoss server to the STS when the WS-Trust validate message is dispatched. In other words, SAML2STSLoginModule needs to authenticate to the STS when validating the SAML assertions and these properties specify the username and password that will be used for that.
In our sample applications we will have three users (UserA, UserB, UserC), each with different roles. The ejb3-sampleapp-roles.properties file specifies the roles that have been assigned to each user:
UserA=RegularUser,Administrator UserB=RegularUser UserC=Guest
As we can see, UserA is both a RegularUser and Administrator, so he should be able to call all methods except for invokeUnavailableMethod. UserB is a RegularUser, so he should be able call invokeRegularMethod and invokeUnprotectedMethod methods. UserC is a Guest and should be able to invoke only the unprotected method of our sample EJB.
For the sake of completeness, here we can see the jboss.xml file of our ejb3-sampleapp.jar:
<?xml version="1.0"?> <!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 5.0//EN" "http://www.jboss.org/j2ee/dtd/jboss_5_0.dtd"> <jboss> <security-domain>java:/jaas/ejb3-sampleapp</security-domain> </jboss>
All the configuration files can be found in the ejb3-sampleapp.jar that has been attached to this document.
PicketLink STS
Our PicketLink STS application is a tweaked version of the picketink-sts.war file that is available in the PicketLink project downloads page. More specifically, we created a new security domain for the STS in jboss-web.xml, included an application policy for the new domain that uses the UsersRolesLoginModule to authenticate STS clients, included the users and roles properties files, and changed the required role in web.xml to STSClient.
This is the content of the STS web.xml:
<?xml version="1.0"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <servlet> <servlet-name>PicketLinkSTS</servlet-name> <servlet-class>org.picketlink.identity.federation.core.wstrust.PicketLinkSTS</servlet-class> </servlet> <servlet-mapping> <servlet-name>PicketLinkSTS</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <web-resource-name>TokenService</web-resource-name> <url-pattern>/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>STSClient</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>PicketLinkSTSRealm</realm-name> </login-config> <security-role> <role-name>STSClient</role-name> </security-role> </web-app>
STS callers must all have the STSClient role in order to send a WS-Trust request to the STS.
The STS security domain is specified by the jboss-web.xml file:
<?xml version="1.0" encoding="UTF-8"?> <jboss-web> <security-domain>java:/jaas/sts-domain</security-domain> </jboss-web>
The application policy for the sts-domain is defined in the sts-jboss-beans.xml file:
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns="urn:jboss:bean-deployer:2.0"> <!-- ejb3 test application-policy definition --> <application-policy xmlns="urn:jboss:security-beans:1.0" name="sts-domain"> <authentication> <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required"> <module-option name="usersProperties">sts-users.properties</module-option> <module-option name="rolesProperties">sts-roles.properties</module-option> </login-module> </authentication> </application-policy> </deployment>
The sts-users.properties specify the username/passwords of the STS callers:
JBoss=JBoss UserA=PassA UserB=PassB UserC=PassC
The sts-roles.properties specify the roles of the STS callers:
JBoss=STSClient UserA=STSClient UserB=STSClient UserC=STSClient
Notice that the JBoss user represents the JBoss server during the SAML validation process. All other users are the clients of the EJB3 sample application - they send a message to the STS to acquire a SAML assertion before calling the methods on the EJB3 application.
Client Application
The SAMLEJB3IntegrationTest shows what happens when each of the users (UserA, UserB, and UserC) acquire a SAML assertion from PicketLinkSTS and invoke all methods on the sample EJB3. Let's take a look at the code:
/* * JBoss, Home of Professional Open Source Copyright 2009, Red Hat Middleware * LLC, and individual contributors by the @authors tag. See the copyright.txt * in the distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This software is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this software; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF * site: http://www.fsf.org. */ package test; import java.security.Principal; import java.util.Hashtable; import javax.ejb.EJBAccessException; import javax.naming.Context; import javax.naming.InitialContext; import javax.rmi.PortableRemoteObject; import org.jboss.security.client.SecurityClient; import org.jboss.security.client.SecurityClientFactory; import org.jboss.test.security.ejb3.SimpleSession; import org.picketlink.identity.federation.api.wstrust.WSTrustClient; import org.picketlink.identity.federation.api.wstrust.WSTrustClient.SecurityInfo; import org.picketlink.identity.federation.core.wstrust.SamlCredential; import org.picketlink.identity.federation.core.wstrust.WSTrustException; import org.picketlink.identity.federation.core.wstrust.plugins.saml.SAMLUtil; import org.w3c.dom.Element; /** * <p> * This class tests the usage of SAML assertions to authenticate clients of EJB3 applications on JBoss. This is * accomplished by having the client first obtain a SAML assertion from the PicketLink STS service and then use * the assertion as the credential when calling the protected EJB3. * </p> * <p> * The protected EJB3 application used in this test has configured the {@code SAML2STSLoginModule}. This login * module sends the SAML assertion to the STS for validation in order to authenticate the caller. A second login * module, {@code UsersRolesLoginModule}, has been used to provide the client's roles. * </p> * * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a> */ public class SAMLEJB3IntegrationTest { private Hashtable<String, Object> env; public static void main(String[] args) throws Exception { SAMLEJB3IntegrationTest test = new SAMLEJB3IntegrationTest(); test.testSAMLEJB3Integration("UserA", "PassA"); test.testSAMLEJB3Integration("UserB", "PassB"); test.testSAMLEJB3Integration("UserC", "PassC"); } public SAMLEJB3IntegrationTest() { // initialize the JNDI env that will be used to lookup the test EJB. this.env = new Hashtable<String, Object>(); this.env.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); this.env.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces"); this.env.put("java.naming.provider.url", "localhost:1099"); } public void testSAMLEJB3Integration(String username, String password) throws Exception { // create a WSTrustClient instance. WSTrustClient client = new WSTrustClient("PicketLinkSTS", "PicketLinkSTSPort", "http://localhost:8080/picketlink-sts-1.0.0/PicketLinkSTS", new SecurityInfo(username, password)); // issue a SAML assertion using the client API. Element assertion = null; try { System.out.println("\nInvoking token service to get SAML assertion for " + username); assertion = client.issueToken(SAMLUtil.SAML2_TOKEN_TYPE); System.out.println("SAML assertion for " + username + " successfully obtained!"); } catch (WSTrustException wse) { System.out.println("Unable to issue assertion: " + wse.getMessage()); wse.printStackTrace(); System.exit(1); } // use the SecurityClient API to set the assertion in the client security context. SecurityClient securityClient = SecurityClientFactory.getSecurityClient(); securityClient.setSimple(username, new SamlCredential(assertion)); securityClient.login(); // invoke the EJB3 bean - the assertion will be propagated with the security context. System.out.println(username + " invoking secure EJB3 session bean"); Context context = new InitialContext(env); Object object = context.lookup("SimpleStatelessSessionBean/remote"); SimpleSession session = (SimpleSession) PortableRemoteObject.narrow(object, SimpleSession.class); // invoke method that requires the Administrator role. try { Principal principal = session.invokeAdministrativeMethod(); System.out.println(principal.getName() + " successfully called administrative method!"); } catch (EJBAccessException eae) { System.out.println(username + " is not authorized to call administrative method!"); } // invoke method that requires the RegularUser role. try { Principal principal = session.invokeRegularMethod(); System.out.println(principal.getName() + " successfully called regular method!"); } catch (EJBAccessException eae) { System.out.println(username + " is not authorized to call regular method!"); } // invoke method that allows all roles. try { Principal principal = session.invokeUnprotectedMethod(); System.out.println(principal.getName() + " successfully called unprotected method!"); } catch (EJBAccessException eae) { // this should never happen as long as the user has successfully authenticated. System.out.println(username + " is not authorized to call unprotected method!"); } // invoke method that denies access to all roles. try { Principal principal = session.invokeUnavailableMethod(); // this should never happen because the method should deny access to all roles. System.out.println(principal.getName() + " successfully called unavailable method!"); } catch (EJBAccessException eae) { System.out.println(username + " is not authorized to call unavailable method!"); } } }
As we can see, the assertion is first obtained using the WSTrustClient API. Once the assertion has been acquired, we use the SecurityClient API to push it to the client-side security context. Then we attempt to call all methods on the sample EJB3 session and print the results of these calls.
Deploying and Running the EJB3 Sample Application on JBoss AS5
In order to get the sample application running you must first install the PicketLink jar files on JBoss. This is accomplished by copying picketlink-fed-1.0.3.jar and picketlink-bindings-jboss-1.0.3.jar (both attached in this document) files to the JBOSS_HOME/server/partition/lib folder. After installing the required PicketLink libs you must copy the ejb3-sampleapp.jar and picketlink-sts-1.0.0.war to JBOSS_HOME/server/partition/deploy.
After copying the required PicketLink jars and deploying the sample application and the STS war, start your JBoss partition. If everything is ok, you should see something like the following in the log:
21:02:10,099 INFO [SessionSpecContainer] Starting jboss.j2ee:jar=ejb3-sampleapp.jar,name=SimpleStatelessSessionBean,service=EJB3 21:02:10,108 INFO [EJBContainer] STARTED EJB: org.jboss.test.security.ejb3.SimpleStatelessSessionBean ejbName: SimpleStatelessSessionBean 21:02:10,152 INFO [JndiSessionRegistrarBase] Binding the following Entries in Global JNDI: SimpleStatelessSessionBean/remote - EJB3.x Default Remote Business Interface SimpleStatelessSessionBean/remote-org.jboss.test.security.ejb3.SimpleSession - EJB3.x Remote Business Interface 21:02:10,306 INFO [TomcatDeployment] deploy, ctxPath=/ 21:02:11,375 INFO [WSDLFilePublisher] WSDL published to: file:/opt/workspace-jboss/jbossas-trunk/build/target/jboss-6.0.0-SNAPSHOT/server/default/data/wsdl/picketlink-sts-1.0.0.war/PicketLinkSTS.wsdl 21:02:11,482 INFO [DefaultEndpointRegistry] register: jboss.ws:context=picketlink-sts-1.0.0,endpoint=PicketLinkSTS 21:02:11,543 INFO [TomcatDeployment] deploy, ctxPath=/picketlink-sts-1.0.0
In order to compile the sample client application, you need to have ejb3-sampleapp.jar, picketlink-fed-1.0.3.jar (both attached in this document), and jbossall-client.jar (found in JBOSS_HOME/client) in your classpath. If using an IDE like Eclipse, all jars referenced by jbossall-client.jar will be automatically included in the classpath. If not, you may need to add these jars manually.
In order to run the client, all you have to do is specify the aforementioned classpath:
java -cp CLASSPATH test.SAMLEJB3IntegrationTest
If everything has been configured and deployed properly, you should see the following output:
Invoking token service to get SAML assertion for UserA SAML assertion for UserA successfully obtained! UserA invoking secure EJB3 session bean UserA successfully called administrative method! UserA successfully called regular method! UserA successfully called unprotected method! UserA is not authorized to call unavailable method! Invoking token service to get SAML assertion for UserB SAML assertion for UserB successfully obtained! UserB invoking secure EJB3 session bean UserB is not authorized to call administrative method! UserB successfully called regular method! UserB successfully called unprotected method! UserB is not authorized to call unavailable method! Invoking token service to get SAML assertion for UserC SAML assertion for UserC successfully obtained! UserC invoking secure EJB3 session bean UserC is not authorized to call administrative method! UserC is not authorized to call regular method! UserC successfully called unprotected method! UserC is not authorized to call unavailable method!
As we can see, each user had access to the expected methods. Authentication was performed by the SAML2STSLoginModule, which validated the supplied assertion with PicketLink STS, and the roles were provided by the UsersRolesLoginModule.
EJB2 Integration Example
In this section we present the EJB2 version of the sample application (ejb2-sampleapp.jar which can be found attached to this document). The sample session bean performs the same operations as in the EJB3 example, but let's take a look at the classes anyway.
The remote and home interfaces look as follows:
/* * JBoss, Home of Professional Open Source. * Copyright 2010, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.test.security.ejb2; import java.rmi.RemoteException; import java.security.Principal; import javax.ejb.EJBObject; /** * <p> * This is the remote interface of the session bean used in the EJB2 SAML security test. * </p> * * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a> */ public interface SimpleEJB2Session extends EJBObject { /** * <p> * This is a method available for regular users and administrators. The deployment descriptor must enforce that * only users in RegularUser or Administrator roles are granted access to this method. * </p> * * @return the caller's {@code Principal}. */ public Principal invokeRegularMethod() throws RemoteException; /** * <p> * This is a method available for administrators only. The deployment descriptor must enforce that only users in the * Administrator role are granted access to this method. * </p> * * @return the caller's {@code Principal}. */ public Principal invokeAdministrativeMethod() throws RemoteException; /** * <p> * This is a method available for all authenticated users, regardless or role. The deployment descriptor must * contain an {@code unchecked} element for this method. * </p> * * @return the caller's {@code Principal}. */ public Principal invokeUnprotectedMethod() throws RemoteException; /** * <p> * This is a method that is unavailable for all roles. The deployment descriptor must add this method to the * {@code exclude-list} element. * </p> * * @return the caller's {@code Principal}. */ public Principal invokeUnavailableMethod() throws RemoteException; }
/* * JBoss, Home of Professional Open Source. * Copyright 2010, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.test.security.ejb2; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; /** * <p> * This is the home interface of the session bean used in the EJB2 SAML security test. * </p> * * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a> */ public interface SimpleEJB2SessionHome extends EJBHome { /** * <p> * Creates and returns a reference to the {@code SimpleEJB2Session} interface. * </p> * * @return a reference to the {@code SimpleEJB2Session} remote interface. */ public SimpleEJB2Session create() throws CreateException, RemoteException; }
And here we can see the implementation class:
/* * JBoss, Home of Professional Open Source. * Copyright 2010, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.test.security.ejb2; import java.rmi.RemoteException; import java.security.Principal; import javax.ejb.CreateException; import javax.ejb.EJBException; import javax.ejb.SessionBean; import javax.ejb.SessionContext; public class SimpleEJB2SessionBean implements SessionBean { private SessionContext context; /** * <p> * {@code ejbCreate} method required by the EJB2 specification. * </p> * * @throws CreateException if an error occurs while creating the session bean. */ public void ejbCreate() throws CreateException { } /* * (non-Javadoc) * * @see javax.ejb.SessionBean#ejbActivate() */ public void ejbActivate() { } /* * (non-Javadoc) * * @see javax.ejb.SessionBean#ejbPassivate() */ public void ejbPassivate() { } /* * (non-Javadoc) * * @see javax.ejb.SessionBean#ejbRemove() */ public void ejbRemove() { } /* * (non-Javadoc) * * @see javax.ejb.SessionBean#setSessionContext(javax.ejb.SessionContext context) */ public void setSessionContext(SessionContext context) { this.context = context; } /* * (non-Javadoc) * * @see org.jboss.test.security.ejb2.SimpleEJB2Session#invokeRegularMethod() */ public Principal invokeRegularMethod() { // this method can be invoked by RegularUser and Administrator roles. return this.context.getCallerPrincipal(); } /* * (non-Javadoc) * * @see org.jboss.test.security.ejb2.SimpleEJB2Session#invokerAdministrativeMethod() */ public Principal invokeAdministrativeMethod() { // this method can be invoked by the Administrator role only. return this.context.getCallerPrincipal(); } /* * (non-Javadoc) * * @see org.jboss.test.security.ejb2.SimpleEJB2Session#invokeUnprotectedMethod() */ public Principal invokeUnprotectedMethod() { // this method can be invoked by any role. return this.context.getCallerPrincipal(); } /* * (non-Javadoc) * * @see org.jboss.test.security.ejb2.SimpleEJB2Session#invokeUnavailableMethod() */ public Principal invokeUnavailableMethod() { // this method cannot be invoked by any role. throw new EJBException("Excluded method - no access should be allowed"); } }
The application policy definition (ejb2-sampleapp-jboss-beans.xml), the properties files used by the UsersRolesLoginModule, the STS configuration file, and the META-INF/jboss.xml file are all very similar to the ones found in the EJB3 example. For this reason we are not going to show them here.
Now, the authorization rules must be defined in the META-INF/ejb-jar.xml deployment descriptor:
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd"> <ejb-jar> <display-name>EBJ2 SAML Tests</display-name> <enterprise-beans> <session> <description>A secured stateless session bean</description> <ejb-name>SimpleEJB2Session</ejb-name> <home>org.jboss.test.security.ejb2.SimpleEJB2SessionHome</home> <remote>org.jboss.test.security.ejb2.SimpleEJB2Session</remote> <ejb-class>org.jboss.test.security.ejb2.SimpleEJB2SessionBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> <assembly-descriptor> <security-role> <description>The role required to invoke administrative methods</description> <role-name>Administrator</role-name> </security-role> <security-role> <description>The role required to invoke regular methods</description> <role-name>RegularUser</role-name> </security-role> <!-- specify unchecked methods (methods available to all roles) --> <method-permission> <unchecked/> <method> <ejb-name>SimpleEJB2Session</ejb-name> <method-name>invokeUnprotectedMethod</method-name> </method> <method> <ejb-name>SimpleEJB2Session</ejb-name> <method-intf>Home</method-intf> <method-name>create</method-name> </method> </method-permission> <!-- specify methods the Administrator role can access --> <method-permission> <role-name>Administrator</role-name> <method> <ejb-name>SimpleEJB2Session</ejb-name> <method-intf>Remote</method-intf> <method-name>*</method-name> </method> </method-permission> <!-- specify methods the RegularUser role can access --> <method-permission> <role-name>RegularUser</role-name> <method> <ejb-name>SimpleEJB2Session</ejb-name> <method-intf>Remote</method-intf> <method-name>invokeRegularMethod</method-name> </method> </method-permission> <!-- specify excluded methods (those not available to any role) --> <exclude-list> <description>A method that no one can access in this deployment</description> <method> <ejb-name>SimpleEJB2Session</ejb-name> <method-name>invokeUnavailableMethod</method-name> </method> </exclude-list> </assembly-descriptor> </ejb-jar>
As we can see, the invokeUnprotectedMethod is available to all roles. The Administrator role can call all methods on the bean except for invokeUnavailableMethod, which is in the exclude-list section. The RegularUser role is allowed to call only the invokeRegularMethod method besides the unprotected method.
Client Application
The client application for the EJB2 example is also very similar to the one used to test the EJB3 SAML integration. The main differences are the lookup code and the way we use to establish the client-side security context.
/* * JBoss, Home of Professional Open Source Copyright 2010, Red Hat Middleware * LLC, and individual contributors by the @authors tag. See the copyright.txt * in the distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This software is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this software; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF * site: http://www.fsf.org. */ package test; import java.rmi.AccessException; import java.security.Principal; import java.util.Hashtable; import javax.naming.Context; import javax.naming.InitialContext; import javax.rmi.PortableRemoteObject; import org.jboss.test.security.ejb2.SimpleEJB2Session; import org.jboss.test.security.ejb2.SimpleEJB2SessionHome; import org.picketlink.identity.federation.api.wstrust.WSTrustClient; import org.picketlink.identity.federation.api.wstrust.WSTrustClient.SecurityInfo; import org.picketlink.identity.federation.core.wstrust.SamlCredential; import org.picketlink.identity.federation.core.wstrust.WSTrustException; import org.picketlink.identity.federation.core.wstrust.plugins.saml.SAMLUtil; import org.w3c.dom.Element; /** * <p> * This class tests the usage of SAML assertions to authenticate clients of EJB2 applications on JBoss. This is * accomplished by having the client first obtain a SAML assertion from the PicketLink STS service and then use * the assertion as the credential when calling the protected EJB2. * </p> * <p> * The protected EJB3 application used in this test has configured the {@code SAML2STSLoginModule}. This login * module sends the SAML assertion to the STS for validation in order to authenticate the caller. A second login * module, {@code UsersRolesLoginModule}, has been used to provide the client's roles. * </p> * * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a> */ public class SAMLEJB2IntegrationTest { private Hashtable<String, Object> env; public static void main(String[] args) throws Exception { SAMLEJB2IntegrationTest test = new SAMLEJB2IntegrationTest(); test.testSAMLEJB2Integration("UserA", "PassA"); test.testSAMLEJB2Integration("UserB", "PassB"); test.testSAMLEJB2Integration("UserC", "PassC"); } public SAMLEJB2IntegrationTest() { // initialize the JNDI env that will be used to lookup the test EJB. this.env = new Hashtable<String, Object>(); this.env.put("java.naming.factory.initial", "org.jboss.security.jndi.JndiLoginInitialContextFactory"); this.env.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces"); this.env.put("java.naming.provider.url", "localhost:1099"); } public void testSAMLEJB2Integration(String username, String password) throws Exception { // create a WSTrustClient instance. WSTrustClient client = new WSTrustClient("PicketLinkSTS", "PicketLinkSTSPort", "http://localhost:8080/picketlink-sts-1.0.0/PicketLinkSTS", new SecurityInfo(username, password)); // issue a SAML assertion using the client API. Element assertion = null; try { System.out.println("\nInvoking token service to get SAML assertion for " + username); assertion = client.issueToken(SAMLUtil.SAML2_TOKEN_TYPE); System.out.println("SAML assertion for " + username + " successfully obtained!"); } catch (WSTrustException wse) { System.out.println("Unable to issue assertion: " + wse.getMessage()); wse.printStackTrace(); System.exit(1); } // invoke the remote EJB using the assertion as the credential. this.env.put("java.naming.security.principal", username); this.env.put("java.naming.security.credentials", new SamlCredential(assertion)); System.out.println("Invoking secure EJB2 session bean with " + username + " SAML assertion"); Context context = new InitialContext(env); Object object = context.lookup("SimpleEJB2Session/home"); SimpleEJB2SessionHome home = (SimpleEJB2SessionHome) PortableRemoteObject. narrow(object, SimpleEJB2SessionHome.class); SimpleEJB2Session session = home.create(); // invoke method that requires the Administrator role. try { Principal principal = session.invokeAdministrativeMethod(); System.out.println("User " + principal.getName() + " successfully called administrative method!"); } catch (AccessException ae) { System.out.println("User " + username + " is not authorized to call administrative method!"); } // invoke method that requires the RegularUser role. try { Principal principal = session.invokeRegularMethod(); System.out.println("User " + principal.getName() + " successfully called regular method!"); } catch (AccessException ae) { System.out.println("User " + username + " is not authorized to call regular method!"); } // invoke method that allows all roles. try { Principal principal = session.invokeUnprotectedMethod(); System.out.println("User " + principal.getName() + " successfully called unprotected method!"); } catch (AccessException ae) { // this should never happen as long as the user has successfully authenticated. System.out.println("User " + username + " is not authorized to call unprotected method!"); } // invoke method that denies access to all roles. try { Principal principal = session.invokeUnavailableMethod(); // this should never happen because the method should deny access to all roles. System.out.println("User " + principal.getName() + " successfully called unavailable method!"); } catch (AccessException ae) { System.out.println("User " + username + " is not authorized to call unavailable method!"); } } }
In this case we are using the JndiLoginInitialContextFactory to set the SAML assertion in the security context just to show an alternative to the SecurityClient API. The JndiLoginInitialContextFactory gets the principal and credentials from the InitialContext properties and pushes them to the security context.
NOTE: The JndiLoginInitialContextFactory approach doesn't work for EJB3 beans on JBoss AS 5.1.0.GA. An issue (JBAS-7010) has been flagged and a fix is available for JBoss 5 EAP and JBoss AS 6. So if you are using JBoss AS 5.1.0.GA make sure to use the SecurityClient API to invoke EJB3 beans using SAML.
Deploying and Running the EJB2 Sample Application on JBoss AS5
If the PicketLink libs haven't been installed yet, you need to do this before deploying the sample application and the STS. This is accomplished by copying picketlink-fed-1.0.3.jar and picketlink-bindings-jboss-1.0.3.jar (both attached to this document) files to the JBOSS_HOME/server/partition/lib folder. After installing the required PicketLink libs you must copy the ejb2-sampleapp.jar and picketlink-sts-1.0.0.war to JBOSS_HOME/server/partition/deploy.
In order to compile the EJB 2 sample client application, you need to have ejb2-sampleapp.jar, picketlink-fed-1.0.3.jar (both found in this document), and jbossall-client.jar (found in JBOSS_HOME/client) in your classpath. If using an IDE like Eclipse, all jars referenced by jbossall-client.jar will be automatically included in the classpath. If not, you may need to add these jar manually.
In order to run the client, just specify the aforementioned classpath:
java -cp CLASSPATH test.SAMLEJB2IntegrationTest
If everything has been configured and deployed properly, you should see the following output (similar to the output produced by the EJB3 client application we've shown before):
Invoking token service to get SAML assertion for UserA SAML assertion for UserA successfully obtained! Invoking secure EJB2 session bean with UserA SAML assertion User UserA successfully called administrative method! User UserA successfully called regular method! User UserA successfully called unprotected method! User UserA is not authorized to call unavailable method! Invoking token service to get SAML assertion for UserB SAML assertion for UserB successfully obtained! Invoking secure EJB2 session bean with UserB SAML assertion User UserB is not authorized to call administrative method! User UserB successfully called regular method! User UserB successfully called unprotected method! User UserB is not authorized to call unavailable method! Invoking token service to get SAML assertion for UserC SAML assertion for UserC successfully obtained! Invoking secure EJB2 session bean with UserC SAML assertion User UserC is not authorized to call administrative method! User UserC is not authorized to call regular method! User UserC successfully called unprotected method! User UserC is not authorized to call unavailable method!
Comments