Skip navigation.
Home

JASIG CAS + Seam 3 (Guvnor) + Tomcat

CAS+Seam+DroolsI recently had to integrate the JBoss Rules Engine Management UI (Drools Guvnor) with CAS authentication, deployed on Tomcat. There were some articles about CAS integration with Seam 2 but nothing with Seam 3. Here's how I did it...

Steps

  1. Make the Seam 3 application use CAS for Authentication instead of whatever it is currently configured to do.
  2. Create an Seam Security Authenticator that can find the CAS authentication object and use that for Seam authentication.
  3. Configure the application to use the custom authenticator.

Seam 3 Application --> CAS

The best way I found to do this is by configuration of the web application (via web.xml) to use CAS specific filters. There are probably other ways to do this (like at the container level) but the key take away is that the CAS authenticated user object (Assertion) needs to be present on ThreadLocal.

The Assertion going to ThreadLocal is accomplished by the org.jasig.cas.client.util.AssertionThreadLocalFilter below...


<filter>
    <filter-name>CAS Authentication Filter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    <init-param>
        <param-name>casServerLoginUrl</param-name>
        <param-value>https://localhost:5543/cas/login</param-value>
    </init-param>
    <init-param>
        <param-name>serverName</param-name>
        <param-value>https://localhost:5543</param-value>
    </init-param>
</filter>
<filter>
    <filter-name>CAS Validation Filter</filter-name>
    <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
    <init-param>
        <param-name>casServerUrlPrefix</param-name>
        <param-value>https://localhost:5543/cas</param-value>
    </init-param>
    <init-param>
        <param-name>serverName</param-name>
        <param-value>https://localhost:5543</param-value>
    </init-param>
</filter>
<filter>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter>
    <filter-name>CAS Assertion Thread Local Filter</filter-name>
    <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter>
   <filter-name>CAS Single Sign Out Filter</filter-name>
   <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>CAS Single Sign Out Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Authentication Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>CAS Assertion Thread Local Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
    <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>

As an extra, I found it a little restrictive to put the CAS URLs inside of the web.xml. Instead I removed them from the web.xml altogether and instead put them into JNDI.

With that my web.xml looks like this (it's the same as above minus the elements):


    <filter>
        <filter-name>CAS Authentication Filter</filter-name>
        <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    </filter>
    <!-- CAS URIs defined in context.xml -->
    <filter>
        <filter-name>CAS Validation Filter</filter-name>
        <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
    </filter>

Each application server has a way to include application specific JNDI settings. Everything but the 'value' attribute is required for the CAS filters to pull in the URL at runtime. In Tomcat, you define a context.xml file that looks like so:


<Context>
    <Resource name="BeanManager"
              auth="Container"
              type="javax.enterprise.inject.spi.BeanManager"
              factory="org.jboss.weld.resources.ManagerObjectFactory"/>

    <!-- This is the location of the roots server name and port only -->
    <Environment description="" name="cas/serverName" override="false"
                 type="java.lang.String" value="https://localhost:5543"/>

    <Environment description="" name="cas/AuthenticationFilter/casServerLoginUrl" override="false"
                 type="java.lang.String" value="https://localhost:5543/cas/login"/>

    <Environment description="" name="cas/Cas20ProxyReceivingTicketValidationFilter/casServerUrlPrefix" override="false"
                 type="java.lang.String" value="https://localhost:5543/cas"/>

</Context>

For Guvnor, this is what it looks like. The context.xml file is found inside the META-INF folder of the WAR and can be overridden outside of the deployed application if need be. For us, we check in the file using developer settings and override in production outside of the application.

Seam Security Authenticator

The CASAuthenticator (Drools) that I wrote is a good example of the fundamental things you need to do inside the custom Seam Authenticator. I'm not an expert on Seam Security in general but for Guvnor, the Authenticator had to do two things:

  1. Update the Credentials object (@Inject into the class) with the user name.
  2. Return the correct Authentication status.

The only other trick in the class is where the user name comes from. In this case it comes from CAS via ThreadLocal in this section of the code:


Assertion casLoggedInAssertion = AssertionHolder.getAssertion();
if (casLoggedInAssertion != null) {
  String userName = casLoggedInAssertion.getPrincipal().getName();

The AssertionHolder will only have the logged in CAS Assertion if the filter has been established.

Guvnor Configuration for Custom CASAuthenticator

To make Guvnor use the CASAuthenticator, all you need to do is put an entry into the Seam/CDI configuration file... beans.xml like so:


  <security:IdentityImpl>
    <s:modifies/>

    <!-- No real authentication: demo authentication for demo purposes -->
    <!--<security:authenticatorClass>org.drools.guvnor.server.security.DemoAuthenticator</security:authenticatorClass>-->

    <!-- JAAS based authentication   -->
    <!--<security:authenticatorName>jaasAuthenticator</security:authenticatorName>-->

    <!-- IDM based authentication (supports LDAP, see Seam 3 and PicketLink IDM documentation) -->
    <!--<security:authenticatorClass>org.jboss.seam.security.management.IdmAuthenticator</security:authenticatorClass>-->

     <security:authenticatorClass>org.drools.guvnor.server.security.CASAuthenticator</security:authenticatorClass>
  </security:IdentityImpl>

As an aside, the DemoAuthenticator was pretty useful in determining the minimum amount of work that was necessary to making the custom CASAuthenticator.

Conclusion

Really all of this makes a lot of sense.. make the application use CAS and then use the CAS authenticated user for Seam. The problem was that I am not a Seam Security expert (I do know CAS really well though) and the documentation was pretty sparse (to non-existent). Hopefully this fills the gap.