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:
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.
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.
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.
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.
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
Here is how to CAS enable JasperServer in 5 Minutes:
List of Assumptions:
- CAS is already installed, configured and in production.
- JasperServer is already installed.
- CAS Apache WebServer client mod_auth_cas is installed and configured.
- Add CAS configuration to applicationContext-security.xml
- Add LDAP configuration to applicationContext-security.xml
- Modify authenticationManager bean in applicationContext-security.xml
- Modify filterChainProxy bean settings in applicationConfiguration-security-web.xml
- 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
- 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.
- In userSearch bean definition, we are using sAMAccountName to match user's username. You may need to change this for your environment.
- In defaultLdapPop bean definition, we set up the role prefix to be ROLE_. You may need to change this for your environment.
Comments
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.
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
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?