Skip to main content

How to CAS enable JasperServer in 5 Minutes

At work we recently decided to integrate our reports server, JasperServer 4.5, with our single sign on server, CAS.

Here is how to CAS enable JasperServer in 5 Minutes:
List of Assumptions:
  1. CAS is already installed, configured and in production.
  2. JasperServer is already installed.
  3. CAS Apache WebServer client mod_auth_cas is installed and configured.
List of Steps:
  1. Add CAS configuration to applicationContext-security.xml
  2. Add LDAP configuration to applicationContext-security.xml
  3. Modify authenticationManager bean in applicationContext-security.xml
  4. Modify filterChainProxy bean settings in applicationConfiguration-security-web.xml
  5. Modify mod_auth_cas configuration
Adding CAS configuration to applicationContext-security.xml
Since mod_auth_cas client is configured at the web server level, we will be modifying the application security context of JasperServer to work with pre-authentication. Mod_auth_cas is a supported client of CAS and more information can be found at https://wiki.jasig.org/display/CASC/mod_auth_cas.
Here is the CAS configuration we used in our applicatonContext-security.xml file. 

    <bean id="casFilter"
        class="org.springframework.security.ui.preauth.header.RequestHeaderPreAuthenticatedProcessi
ngFilter">
        <property name="principalRequestHeader" value="CAS-User" />
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="continueFilterChainOnUnsuccessfulAuthentication" value="true" />
    </bean>

    <bean id="preAuthProvider"
        class="org.springframework.security.providers.preauth.PreAuthenticatedAuthenticationProvide
r">
        <property name="preAuthenticatedUserDetailsService" ref="userDetailsServiceWrapper" />

    </bean>
    <bean id="userDetailsServiceWrapper"
        class="org.springframework.security.userdetails.UserDetailsByNameServiceWrapper">
        <property name="userDetailsService">
            <bean
                class="org.springframework.security.userdetails.ldap.LdapUserDetailsService">
                <constructor-arg index="0">
                    <ref local="userSearch" />
                </constructor-arg>
                <constructor-arg index="1">
                    <ref local="defaultLdapPop" />
                </constructor-arg>
            </bean>
        </property>
    </bean>


The above CAS configuration is basically using Spring Security constructs to create a filter that checks to see if mod_auth_cas has provided the request header. Once CAS request header is found in the request, the above configuration is making sure that user is populated through the UserDetailsServiceWrapper which is in our case using LDAPUserDetails Server. The next step has the LDAP configuration section of the applicationContext-security.xml file.

Adding LDAP configuration to applicationContext-security.xml
JasperServer's applicationContext-security.xml already ships with a commented pre-configured LDAP configuration. We made few changes to it in order for it to support the above CAS configuration.

Here is the LDAP configuration we used in our applicationContext-security.xml file.
<bean id="ldapContextSource"
        class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
        <constructor-arg
            value="ldap://localhost:389/DC=company,DC=com" />
        <property name="userDn" value="CN=ldap,CN=Users,DC=company,DC=com" />
        <property name="password" value="test" />
        <property name="baseEnvironmentProperties">
            <map>
                <entry key="java.naming.referral">
                    <value>follow</value>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="userSearch"
        class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
        <constructor-arg index="0">
            <value>CN=Users</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>(sAMAccountName={0})</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref local="ldapContextSource" />
        </constructor-arg>
        <property name="searchSubtree">
            <value>true</value>
        </property>
    </bean>


    <bean id="defaultLdapPop"
        class="org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator">
        <constructor-arg index="0">
            <ref local="ldapContextSource" />
        </constructor-arg>
        <constructor-arg index="1">
            <value>CN=Users</value>
        </constructor-arg>
        <property name="convertToUpperCase">
            <value>true</value>
        </property>
        <property name="rolePrefix">
            <value>ROLE_</value>
        </property>
        <property name="groupRoleAttribute">
            <value>CN</value>
        </property>
        <property name="groupSearchFilter">
            <value>member={0}</value>
        </property>
        <property name="searchSubtree">
            <value>true</value>
        </property>
    </bean>


    <bean id="ldapAuthenticationProvider"
        class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
        <constructor-arg>
            <bean
                class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
                <constructor-arg>
                    <ref local="ldapContextSource" />
                </constructor-arg>
                <property name="userSearch" ref="userSearch" />
            </bean>
        </constructor-arg>
        <constructor-arg>
            <ref local="defaultLdapPop" />
        </constructor-arg>
    </bean>


The above configuration is handling our connection to AD, user search, role population of users and authentication with AD. LDAP configuration beans userSearch and defaultLdapPop are used within the CAS integration configuration.

At this point our changes will not yet be used. We need to make sure that the authentication providers we created, preAuthProvider and LdapAuthenticationProvider are activated. In order to achieve this, we had to modify the authenticationManager bean configuration in applicationContext-security.xml.

Here is the snippet of code that shows how to add these two authentication providers to the chain.
<bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager">
        <property name="providers">
            <list>
                <!-- not on by default <ref local="ldapAuthenticationProvider"/>  -->
        <ref local="preAuthProvider" />
                <ref local="ldapAuthenticationProvider" />
                <ref bean="${bean.daoAuthenticationProvider}"/>
                <ref bean="anonymousAuthenticationProvider"/>
                <!--ref local="jaasAuthenticationProvider"/-->
            </list>
        </property>
    </bean>

We decided to keep the other authenticators on the list as well just in case.

Modify filterChainProxy bean settings in applicationConfiguration-security-web.xml

With this change, you can login to JasperServer with AD directory user credentials. We needed to implement one more configuration change in order to ensure that CAS authentication would take place and that was to modify the filterProxyChain configuration in applicationContext-security-web.xml file include casFilter as part of the filter chain.

Here is the code snippet in our applicationContext-security-web.xml file.
<bean id="filterChainProxy" class="org.springframework.security.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /xmla=httpSessionContextIntegrationFilter,${bean.loggingFilter},casFilter,${bean.basicProcessingFilter},JIAuthenticationSynchronizer,anonymousProcessingFilter,basicAuthExceptionTranslationFilter,filterInvocationInterceptor
                /services/**=httpSessionContextIntegrationFilter,${bean.loggingFilter},casFilter,${bean.portletAuthenticationProcessingFilter},${bean.basicProcessingFilter},${bean.passwordExpirationProcessingFilter},JIAuthenticationSynchronizer,anonymousProcessingFilter,wsBasicAuthExceptionTranslationFilter,filterInvocationInterceptor
                /rest/login=httpSessionContextIntegrationFilter,${bean.loggingFilter},casFilter,restLoginAuthenticationFilter,JIAuthenticationSynchronizer,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
                /rest/**=httpSessionContextIntegrationFilter,${bean.loggingFilter},casFilter,${bean.portletAuthenticationProcessingFilter},${bean.basicProcessingFilter},${bean.passwordExpirationProcessingFilter},JIAuthenticationSynchronizer,anonymousProcessingFilter,wsBasicAuthExceptionTranslationFilter,filterInvocationInterceptor
                /**=httpSessionContextIntegrationFilter,${bean.loggingFilter},casFilter,${bean.userPreferencesFilter},${bean.authenticationProcessingFilter},${bean.userPreferencesFilter},${bean.basicProcessingFilter},requestParameterAuthenticationFilter,JIAuthenticationSynchronizer,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor,switchUserProcessingFilter,iPadSupportFilter
            </value>
        </property>
    </bean>


Once you made these changes to the configuration files, you will need to restart your application server.

Modify mod_auth_cas configuration
Last thing we needed to accomplish was to ensure that access to JasperServer is intercepted by mod_auth_cas. To achieve this we needed to modify our mod_auth_cas configuration and include a directive for JasperServer.

Here is the code snippet we used in our mod_auth_cas.conf (may be different in your installation and configuration) file.
<Location ~ "/jasperserver*">
 AuthType CAS
 AuthName "CAS"
 require valid-user
 CasAuthNHeader Cas-User
</Location>


Once the above change is made to the mod_auth_cas.conf file, you will need to restart/reload your web server.

When you vist yourdomain.com/jasperserver you will be redirected to your CAS server's login page.

Notes

  1. In casFilter bean definition, we have set up the principalRequestHeader value to be "Cas-User". This value is also set up in mod_auth_cas configuration. See above. 
  2. In userSearch bean definition, we are using sAMAccountName to match user's username. You may need to change this for your environment.
  3. In defaultLdapPop bean definition, we set up the role prefix to be ROLE_. You may need to change this for your environment.

Comments

Andy said…
Great post, however after enabling CAS on Jasper Server I ran into an issue connecting to the repository with iReport. Since iReport has no idea what to do with CAS, my solution requires that the JS service calls remain unprotected by CAS. This means that mod_auth_cas should be set to protect all URLs that do not begin with /jasperserver/service. Jasper Server will also need to be modified to not require the CAS-User header.

I took advantage of the regular expression matching features of Apache's tag to un-protect the web service URLs. Instead of '' as Ismail wrote, I used ''. This prevents the user (and iReport) from being redirected to the CAS login page.

Now on Jasper Server, Ismail added the property continueFilterChainOnUnsuccessfullAuthentication in applicationContext-security.xml to allow for authentication to fail-over into Active Directory. However, this only works when the authentication is unsuccessful. In this case, we're not sending the CAS-User header and Jasper Server would fail with the error "org.springframework.security.ui.preauth.PreAuthenticatedCredentialsNotFoundException: CAS-User header not found in request".

To overcome this, I set Jasper Server to un-CASify for all URLs /jasperserver/service*. In applicationContext-security-web.xml, I removed the casFilter for /services/** in "filterInvocationDefinitionSource": /services/**=httpSessionContextIntegrationFilter,${bean.loggingFilter},${bean.portletAuthenticationProcessingFilter}.....

After this I restarted Jasper Server and BOOM, iReport is able to connect to the repository.
Alaska said…
The cas authentication is done successfully and I am able to redirect to the home flow, /jasperserver-pro/flow.html?_flowId=homeFlow. But all of the other links throws a null pointer exception. My guess is there is some property that I am missing to set on one of the security xml files. I have included pat of the stack trace below. any ideas where I should look into? Thanks

java.lang.NullPointerException at com.jaspersoft.jasperserver.multipleTenancy.security.MTAcegiSecurityContextProvider.getContextUser(MTAcegiSecurityContextProvider.java:55) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:318) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke
ismail said…
Once the user principal is provided as part of the request header, You need to ensure that JasperServer uses the header value to create a user security context. In the post, I handle it through LDAP. If you look at eh configuration in the xml files in the post, you will see that head value is passed as a search input. Once the user is found properties and roles are loaded and context is created. Based on the partial exception stack trace above, there seems to be a problem with that process. If you have not already increase your logging that should help.
Adi's World said…
I have CAS configured on tomcat6 and jasperserver 5.0. Cas authentication is working fine with regular web based login, but its creating trouble while connecting through iReport.

I could not find mod_auth_cas.conf file in either my CAS tomcat server or Jasper server machine.

Removing /services/** from applicationContext-security-web.xml makes the server unstable and gives "Error 302 (Moved temporarily)". Any ideas how to resolve this issue?
Anonymous said…
Basically, this is the best article on Internet on this subject
NoBadDays said…
Where is the "applicationConfiguration-security-web.xml" file located ?, I can't seem to find it in Clean Jasper ?
ismail said…
I just downloaded it. It seems to be under jasperserver/jasperserver-war/shared-config/ folder. Hope this helps.
ismail said…
mod_auth_cas.conf is the configuration for mod_auth_cas module for Apache Web Server. This article assumes you have mod_auth_cas configured for your web server. Here is the link to the components site:https://wiki.jasig.org/display/CASC/mod_auth_cas
Yogesh said…
A very helpful article. I have been able to configure Jasper to use CAS for my web browser. I want to configure the Jasper Mobile for Iphone to use CAS authentication. Any pointers will be very helpful.