9 Replies Latest reply on Nov 30, 2007 7:15 PM by agnadello

    External Client and Seam Security

    agnadello

      Hi,

      I've configured Seam to use drools in my security setup according to Seam docs, chapter 13. Everything works fine...

      I also have a QuartzInitializerServlet starting up jobs (POJO's):

      public void execute(final JobExecutionContext theJobExecutionContext)
       throws JobExecutionException {
       this.LOGGER.info("Executing job with description: "
       + theJobExecutionContext.getJobDetail().getDescription());
      
       try {
       UsernamePasswordHandler handler = new UsernamePasswordHandler(
       "user", "Demo987!");
       this.LOGGER.info("Login attempt...");
       LoginContext lc = new LoginContext("client-login", handler);
       lc.login();
       this.LOGGER.info("Login successful!");
       // Any calls to secured resources now use the username/password
       // identity
       final EchoService service = (EchoService) new InitialContext()
       .lookup("sio/EchoServiceBean/local");
       final Echo echo = service.echo();
       this.LOGGER.info("Echo Message = '" + echo + "'");
       // Clear and restore the previous identity
       this.LOGGER.info("Logout attempt...");
       lc.logout();
       this.LOGGER.info("Logout successful!");
       } catch (Exception e) {
       e.printStackTrace();
       }
       }


      The EJB method 'echo' is annotated with the Seam @Restrict annotation like this:

      @Restrict("#{s:hasRole('admin')}")


      My question is if it's possible to make the external JAAS login (from the Quartz job) to propagate to the Seam security framework?

      The described implementation doesn't work and throws IllegalStateException with the message that there is no active session context.

      Anyone done this before?

      Seam 2.0.0.GA and JBoss AS 4.2.1.GA

      Cheers!

      Regards, Andreas

        • 1. Re: External Client and Seam Security
          shane.bryzak

          You could try creating a session context using Lifecycle.beginSession(), just make sure you call Lifecycle.endSession() when you are doneto clear up thread-locals.

          • 2. Re: External Client and Seam Security
            agnadello

            Thank you. One step further... no more IllegalStateException.

            Instead the Identity doesn't seem to be populated with any subject/principals.

            The

            @Restrict
            annotation don't kick in, neither the Drools rules.

            Do you know if it's possible to make use of the Seam security if the Session Beans and Entity Beans is accessed from an external client?

            Cheers!

            Regards, Andreas

            • 3. Re: External Client and Seam Security
              shane.bryzak

              Seam security should work, you can set a breakpoint in RootInterceptor.invoke() to confirm that the Seam interceptors are being invoked.

              Also, you will have to still authenticate using Identity.login() for the subject and principals to be populated.

              • 4. Re: External Client and Seam Security
                agnadello

                Hi again,

                Well, I did put a breakpoint at the RootInterceptor.invoke() method and it gets hit.

                I've also implemented the manual Identity.login(), even tested with Identity.authenticate().

                I've also verified that the @Restrict("#{s:hasRole('user')}") works if I access the method from my JSF after I'm logged in.

                Seems like I'm missing something here so I'll give you the whole enchillada!

                Here's my Quartz Pojo Job

                public class SampleJob implements Job {
                
                 private final Logger LOGGER = Logger.getLogger(this.getClass());
                
                 public void execute(final JobExecutionContext theJobExecutionContext)
                 throws JobExecutionException {
                 this.LOGGER.info("Executing job with description: "
                 + theJobExecutionContext.getJobDetail().getDescription());
                 LoginContext lc = null;
                 try {
                 // Begin Seam session
                 Lifecycle.beginSession(new java.util.HashMap<String, Object>());
                
                 // External client login
                 UsernamePasswordHandler handler = new UsernamePasswordHandler(
                 "user", "Demo987!");
                 lc = new LoginContext("client-login", handler);
                 lc.login();
                
                 // Lookup EJB
                 final TestSeamSecurityService service = (TestSeamSecurityService) new InitialContext()
                 .lookup("sio/TestSeamSecurityServiceBean/local");
                
                 // Any calls to secured resources now use the username/password
                 // identity
                 service.login("user", "Demo987!");
                
                 // Should fail because 'user' don't have the role 'admin'
                 service.secure();
                
                 // Clear and restore the previous identity
                 service.logout();
                 lc.logout();
                 } catch (Exception e) {
                 e.printStackTrace();
                 } finally {
                 // End Seam session
                 Lifecycle.endSession(new java.util.HashMap<String, Object>());
                 }
                 }
                }
                


                And here's my Seam component using the @Restrict annotation (local business interface omitted):
                @Stateful
                @SecurityDomain("sio")
                @Local(TestSeamSecurityService.class)
                @Name("echoService")
                public class TestSeamSecurityServiceBean implements TestSeamSecurityService {
                
                 @Logger
                 private transient Log log;
                
                 @Resource
                 private SessionContext sessionContext;
                
                 @RolesAllowed("user")
                 public void login(final String theUsername, final String thePassword)
                 throws LoginException {
                 this.log.info("Entered 'login' method...");
                 // Seam login
                 Identity.instance().setUsername(theUsername);
                 Identity.instance().setPassword(thePassword);
                 Identity.instance().login();
                 // Identity.instance().authenticate();
                 this.log.info("Exiting 'login' method.");
                 this.status();
                 }
                
                 public void logout() {
                 Identity.instance().logout();
                 }
                
                 @Restrict("#{s:hasRole('admin')}")
                 public void secure() {
                 this.log.info("Entered 'secure' method.");
                 this.status();
                 }
                
                 @Remove
                 @Destroy
                 public void destroy() {
                 }
                
                 private void status() {
                 this.log.info("-=STATUS=-");
                 this.log.info("Caller Principal = #0", this.sessionContext
                 .getCallerPrincipal());
                 this.log.info("Is Identity Security Enabled = #0", Identity
                 .isSecurityEnabled());
                 this.log.info("Identity Is Logged In = #0", Identity.instance()
                 .isLoggedIn());
                 this.log.info("Identity Has Role 'user' = #0", Identity.instance()
                 .hasRole("user"));
                 this.log.info("Identity Has Role 'admin' = #0", Identity.instance()
                 .hasRole("admin"));
                 }
                
                }
                


                As you can see I've implemented the "standard Java EE security" using @SecurityDomain annotation. The annotation @RolesAllowed works like a charm but not @Restrict.

                component.xml
                <?xml version="1.0" encoding="UTF-8"?>
                <components xmlns="http://jboss.com/products/seam/components"
                 xmlns:core="http://jboss.com/products/seam/core"
                 xmlns:persistence="http://jboss.com/products/seam/persistence"
                 xmlns:security="http://jboss.com/products/seam/security"
                 xmlns:drools="http://jboss.com/products/seam/drools"
                 xmlns:async="http://jboss.com/products/seam/async"
                 xmlns:web="http://jboss.com/products/seam/web"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:framework="http://jboss.com/products/seam/framework"
                 xsi:schemaLocation="http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.0.xsd
                 http://jboss.com/products/seam/persistence http://jboss.com/products/seam/persistence-2.0.xsd
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.0.xsd
                 http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.0.xsd
                 http://jboss.com/products/seam/async http://jboss.com/products/seam/async-2.0.xsd
                 http://jboss.com/products/seam/web http://jboss.com/products/seam/web-2.0.xsd
                 http://jboss.com/products/seam/framework http://jboss.com/products/seam/framework-2.0.xsd
                 http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.0.xsd">
                
                 <core:init jndi-pattern="sio/#{ejbName}/local" debug="true"
                 transaction-management-enabled="true" />
                
                 <persistence:managed-persistence-context name="entityManager"
                 auto-create="true"
                 persistence-unit-jndi-name="java:/OracleSioEntityManagerFactory" />
                
                 <core:manager conversation-timeout="600000"
                 concurrent-request-timeout="500" conversation-id-parameter="cid" />
                
                 <!-- Default system JAAS configuration -->
                 <security:identity
                 authenticate-method="#{authenticator.authenticate}"
                 jaas-config-name="sio" />
                
                 <drools:rule-base name="securityRules">
                 <drools:rule-files>
                 <value>/META-INF/security.drl</value>
                 </drools:rule-files>
                 </drools:rule-base>
                
                 <framework:entity-query name="roles" ejbql="select r from Role r" />
                
                 <event type="org.jboss.seam.notLoggedIn">
                 <action execute="#{redirect.captureCurrentView}" />
                 </event>
                
                 <event type="org.jboss.seam.postAuthenticate">
                 <action execute="#{redirect.returnToCapturedView}" />
                 </event>
                
                </components>
                


                The Drools rules in security.drl only concerns entities not part of this problem so I'm not including it and the orm.xml in this post. Does anyone feel the need to see just tell me :-)

                web.xml
                <?xml version="1.0" encoding="UTF-8"?>
                
                <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
                
                 <!-- Initializes Quartz in the application server -->
                
                 <servlet>
                 <servlet-name>QuartzInitializer</servlet-name>
                 <display-name>Quartz Initializer Servlet</display-name>
                 <servlet-class>
                 org.quartz.ee.servlet.QuartzInitializerServlet
                 </servlet-class>
                 <load-on-startup>2</load-on-startup>
                 <init-param>
                 <param-name>shutdown-on-unload</param-name>
                 <param-value>true</param-value>
                 </init-param>
                 <init-param>
                 <param-name>start-scheduler-on-load</param-name>
                 <param-value>true</param-value>
                 </init-param>
                 </servlet>
                
                 <!-- BEGIN: RichFaces -->
                 <context-param>
                 <param-name>org.richfaces.SKIN</param-name>
                 <param-value>DEFAULT</param-value>
                 </context-param>
                
                 <filter>
                 <display-name>RichFaces Filter</display-name>
                 <filter-name>richfaces</filter-name>
                 <filter-class>org.ajax4jsf.Filter</filter-class>
                 </filter>
                
                 <filter-mapping>
                 <filter-name>richfaces</filter-name>
                 <servlet-name>Faces Servlet</servlet-name>
                 <dispatcher>REQUEST</dispatcher>
                 <dispatcher>FORWARD</dispatcher>
                 <dispatcher>INCLUDE</dispatcher>
                 </filter-mapping>
                
                 <!-- END: RichFaces -->
                
                 <!-- Seam -->
                
                 <listener>
                 <listener-class>
                 org.jboss.seam.servlet.SeamListener
                 </listener-class>
                 </listener>
                
                 <servlet>
                 <servlet-name>Seam Resource Servlet</servlet-name>
                 <servlet-class>
                 org.jboss.seam.servlet.ResourceServlet
                 </servlet-class>
                 </servlet>
                
                 <servlet-mapping>
                 <servlet-name>Seam Resource Servlet</servlet-name>
                 <url-pattern>/seam/resource/*</url-pattern>
                 </servlet-mapping>
                
                 <filter>
                 <filter-name>Seam Filter</filter-name>
                 <filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
                 </filter>
                
                 <filter-mapping>
                 <filter-name>Seam Filter</filter-name>
                 <url-pattern>/*</url-pattern>
                 </filter-mapping>
                
                 <context-param>
                 <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
                 <param-value>client</param-value>
                 </context-param>
                
                 <context-param>
                 <param-name>facelets.DEVELOPMENT</param-name>
                 <param-value>true</param-value>
                 </context-param>
                
                 <context-param>
                 <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
                 <param-value>.xhtml</param-value>
                 </context-param>
                
                 <servlet>
                 <servlet-name>Faces Servlet</servlet-name>
                 <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
                 <load-on-startup>1</load-on-startup>
                 </servlet>
                
                 <!-- Faces Servlet Mapping -->
                
                 <servlet-mapping>
                 <servlet-name>Faces Servlet</servlet-name>
                 <url-pattern>*.seam</url-pattern>
                 </servlet-mapping>
                
                </web-app>
                


                ejb-jar.xml
                <?xml version="1.0" encoding="UTF-8"?>
                <ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
                 version="3.0">
                
                 <interceptors>
                 <interceptor>
                 <interceptor-class>
                 org.jboss.seam.ejb.SeamInterceptor
                 </interceptor-class>
                 </interceptor>
                 </interceptors>
                
                 <assembly-descriptor>
                 <interceptor-binding>
                 <ejb-name>*</ejb-name>
                 <interceptor-class>
                 org.jboss.seam.ejb.SeamInterceptor
                 </interceptor-class>
                 </interceptor-binding>
                 </assembly-descriptor>
                
                </ejb-jar>
                


                Here's my login-config.xml:
                <?xml version='1.0'?>
                <!DOCTYPE policy PUBLIC
                 "-//JBoss//DTD JBOSS Security Config 3.0//EN"
                 "http://www.jboss.org/j2ee/dtd/security_config.dtd">
                
                <policy>
                
                 <!-- Used by clients within the application server VM such as mbeans and servlets that access EJBs. -->
                 <application-policy name = "client-login">
                 <authentication>
                 <login-module code = "org.jboss.security.ClientLoginModule"
                 flag = "required">
                 <!-- Any existing security context will be restored on logout -->
                 <module-option name="restore-login-identity">true</module-option>
                 <module-option name="multi-threaded">true</module-option>
                 </login-module>
                 </authentication>
                 </application-policy>
                
                 <!-- Default JBoss stuff omitted -->
                
                 <!-- Security domain for SIO -->
                 <application-policy name = "sio">
                 <authentication>
                 <login-module code = "com.cybercomgroup.security.auth.jboss.SoxDatabaseServerLoginModule" flag = "required">
                 <module-option name = "dsJndiName">java:/OracleSio</module-option>
                 <module-option name = "rolesQuery">SELECT role_name, 'Roles' FROM principals JOIN roles ON principals.id = roles.id WHERE principals.username=?</module-option>
                 <module-option name = "principalsQuery">SELECT Password FROM principals WHERE username=?</module-option>
                 </login-module>
                 </authentication>
                 </application-policy>
                
                </policy>
                


                And here the log output from a Quartz job execution:
                13:41:00,025 INFO [SampleJob] Executing job with description: A sample job doing nothing.
                13:41:00,025 INFO [Contexts] starting up: org.jboss.seam.web.session
                13:41:00,025 INFO [Contexts] starting up: org.jboss.seam.security.identity
                13:41:00,756 INFO [RuleBase] parsing rules: /META-INF/security.drl
                13:41:05,703 INFO [SoxDatabaseServerLoginModule] started login attempt
                13:41:06,774 INFO [SoxDatabaseServerLoginModule] principal 'user' is authenticated and active
                13:41:06,774 INFO [SoxDatabaseServerLoginModule] days since last password change = 65
                13:41:06,804 INFO [SoxDatabaseServerLoginModule] authentication successful = true
                13:41:06,894 INFO [TestSeamSecurityServiceBean] Entered 'login' method...
                13:41:06,894 INFO [SoxDatabaseServerLoginModule] started login attempt
                13:41:06,945 INFO [SoxDatabaseServerLoginModule] principal 'user' is authenticated and active
                13:41:06,945 INFO [SoxDatabaseServerLoginModule] days since last password change = 65
                13:41:06,975 INFO [SoxDatabaseServerLoginModule] authentication successful = true
                13:41:08,246 INFO [TestSeamSecurityServiceBean] Exiting 'login' method.
                13:41:08,246 INFO [TestSeamSecurityServiceBean] -=STATUS=-
                13:41:08,246 INFO [TestSeamSecurityServiceBean] Caller Principal = user
                13:41:08,246 INFO [TestSeamSecurityServiceBean] Is Identity Security Enabled = true
                13:41:08,246 INFO [TestSeamSecurityServiceBean] Identity Is Logged In = true
                13:41:08,246 INFO [TestSeamSecurityServiceBean] Identity Has Role 'user' = true
                13:41:08,246 INFO [TestSeamSecurityServiceBean] Identity Has Role 'admin' = false
                
                13:41:08,276 INFO [TestSeamSecurityServiceBean] Entered 'secure' method.
                13:41:08,276 INFO [TestSeamSecurityServiceBean] -=STATUS=-
                13:41:08,276 INFO [TestSeamSecurityServiceBean] Caller Principal = user
                13:41:08,276 INFO [TestSeamSecurityServiceBean] Is Identity Security Enabled = true
                13:41:08,276 INFO [TestSeamSecurityServiceBean] Identity Is Logged In = false
                13:41:08,276 INFO [TestSeamSecurityServiceBean] Identity Has Role 'user' = false
                13:41:08,276 INFO [TestSeamSecurityServiceBean] Identity Has Role 'admin' = false
                


                As you can see the Identity is populated with the role 'user' when I perform a "manual" login. But between the calls to method 'login' and then method 'secure' the Identity seems to lose its principal information.

                And even worse - the @Restrict annotation doesn't kick in at all. Even though the Identity doesn't seem to get its principals between calls the @Restrict annotation should react to it and throw an exception because the Identity doesn't contain the role required to call 'secure'.

                Soon I'll have to implement this myself which sucks :-(

                Cheers!

                Regards, Andreas

                • 5. Re: External Client and Seam Security
                  shane.bryzak

                  I think I know what the problem is - SecurityInterceptor (which performs the security checks for Seam components) is a client interceptor, which means it is not part of the ejb interceptor stack. I'm not sure what to suggest as a workaround at this stage, though the first thing you can try is to extend SecurityInterceptor and change the interceptor type to server and check that the restrictions are processed.

                  @Interceptor(stateless = true, type=InterceptorType.SERVER,
                   around=AsynchronousInterceptor.class)
                  public class MySecurityInterceptor extends SecurityInterceptor


                  • 6. Re: External Client and Seam Security
                    agnadello

                    I give up!

                    Thanks a lot Shane for all your help.

                    I wasn't able to use the interceptor you suggested. I tried to add it to the default stack by Component.forName("...").addInterceptor(...) but ended up with ArrayIndexOutOfBounds etc.

                    My second try was to use a regular EJB3 interceptor which does the Seam login/logout and basically all the thing in SecurityInterceptor from Seam.

                    It's really a copy of your code:

                    public class ExternalClientSecurityInterceptor {
                    
                     @AroundInvoke
                     public Object aroundInvoke(final InvocationContext theInvocationContext)
                     throws Exception {
                    
                     try {
                     // Perform a Seam login
                     this.doSeamLogin();
                    
                     // Get the invoked method
                     final Method theInterfaceMethod = theInvocationContext.getMethod();
                    
                     // TODO: optimize this:
                     // Check if there's a Seam @Restrict annotation on invoked method
                     final Object theTarget = theInvocationContext.getTarget();
                     final Method theMethod = this.getComponent(theTarget)
                     .getBeanClass().getMethod(theInterfaceMethod.getName(),
                     theInterfaceMethod.getParameterTypes());
                     final Restrict theRestriction = this.getRestriction(theMethod,
                     theTarget);
                    
                     // Perform security check if a restriction is found
                     if (null != theRestriction && Identity.isSecurityEnabled()) {
                     final String theRestrictionExpression = !Strings
                     .isEmpty(theRestriction.value()) ? theRestriction
                     .value() : this.createDefaultExpr(theMethod, theTarget);
                     Identity.instance().checkRestriction(theRestrictionExpression);
                     }
                     return theInvocationContext.proceed();
                     } finally {
                    
                     // Always logout after invocation
                     this.doSeamLogout();
                     }
                     }
                    
                     private Component getComponent(final Object theTarget) {
                     // Get the Seam component name of the target class
                     final String theComponentName = Component.getComponentName(theTarget
                     .getClass());
                     // Return the component
                     return Component.forName(theComponentName);
                     }
                    
                     private void doSeamLogin() {
                     Identity.instance().setUsername("user");
                     Identity.instance().setPassword("Demo987!");
                     Identity.instance().login();
                     }
                    
                     private void doSeamLogout() {
                     Identity.instance().logout();
                     }
                    
                     private Restrict getRestriction(final Method theMethod,
                     final Object theTarget) {
                     if (theMethod.isAnnotationPresent(Restrict.class)) {
                     return theMethod.getAnnotation(Restrict.class);
                     } else if (this.getComponent(theTarget).getBeanClass()
                     .isAnnotationPresent(Restrict.class)) {
                     if (!this.getComponent(theTarget).isLifecycleMethod(theMethod)) {
                     return this.getComponent(theTarget).getBeanClass()
                     .getAnnotation(Restrict.class);
                     }
                     }
                     return null;
                     }
                    
                     /**
                     * Creates a default security expression for a specified method. The method
                     * must be a method of a Seam component.
                     *
                     * @param theMethod
                     * The method for which to create a default permission expression
                     * @return The generated security expression.
                     */
                     private String createDefaultExpr(final Method theMethod,
                     final Object theTarget) {
                     return String.format("#{s:hasPermission('%s','%s', null)}", this
                     .getComponent(theTarget).getName(), theMethod.getName());
                     }
                    }
                    


                    This enabled the recognition of the @Restrict("s:hasRole('user')") annotation on EJB methods.

                    Next problem - the Drools rules doesn't seem to work. Well, they work if I run from the JSF's but not from my Quartz POJO job.

                    I've tried to debug to see how and if my RuleBasedIdentity uses the rules but I got lost in the Drools code :-(
                    At least I can see that the RuleBasedIdentity is created and that my Drools rule file is read.

                    I'll guess I'll use default Java EE security and where I need more advanced security constraints I'll have to implement it myself... too bad.

                    Is there a possibility to file this feature to JIRA?

                    Kind regards, Andreas


                    • 7. Re: External Client and Seam Security
                      shane.bryzak

                      Please go ahead and file it in JIRA - even though this isn't a common scenario it should still work.

                      • 8. Re: External Client and Seam Security
                        agnadello

                        About it being a common scenario or not...

                        Given the following:

                        - I'd like to have one single EJB Entity bean model for my application.
                        - My EJB Entity beans is annotated with the @Restrict tag for use in Seam.
                        - My Entity beans is configured to use Seam Entity Security (orm.xml).
                        - I have a non-Seam client who uses the same Entity beans as Seam.

                        Since my non-Seam client isn't authenticated through Seam, the Seam EntityListener will always throw AuthorizationException on Identity.instance().checkPermission(...).

                        This was the initial problem which started the effort to login and use Seam security from non-Seam client.

                        The application have both JSF clients and Quartz POJO clients using the same Entity beans which is (IMHO) not a very uncommon scenario :-)

                        Anyways, I'll file it to JIRA.

                        Thanks a lot Shane for the help!

                        Cheers!

                        • 9. Re: External Client and Seam Security
                          agnadello