JasperReports Server Authentication with NTLM

Table of Contents 

Configuring JasperReports Server SSO Authentication with NTLM/Kerberos

Customers in a Windows environment using Active Directory for authentication may configure JasperReports Server to automatically authenticate using the NTLM protocol without seeing the login screen. This is done using the WAFFLE 1.4 library. A modified version of this library is attached, along with support libraries and a custom authentication filter for handling SSO. The following steps outline how to configure this authentication. This document assumes you're using the bundled Apache Tomcat web server. For non-bundled Tomcat or other web servers, please adjust the paths in the steps as needed.

IMPORTANT:  This is community-contributed content and is not supported by TIBCO Analytics.  It is provided as-is with no express or implied warranties of any kind.  The source code is included in the Waffle-ntlm.jar file and may be modified to meet your business requirements.  By doing so, however, the end consumer assumes all liabilities and responsibilities for modifying the code.

IMPORTANT:  This configuration has been tested and verified on JasperReports Server through version 6.0.0 using the bundled Windows installer.  If you're planning to use JasperReports Server 6.0.1 or newer, there's a new version of this page that uses Waffle 1.7 available here.

Prerequisites

  • Windows Server 2003 or later (Tested with Windows Server 2008 R2) with the Active Directory Domain Services and DNS Server roles installed

  • JasperReports Server 5.2 through 6.0 running on Apache Tomcat as a Windows Service logged on as an Active Directory Domain user

Configuration Steps

  1. Download the zip file that contains these jar files:

    unpack them and save them in the following locations:

    <jasperserver-home>/apache-tomcat/lib

    • Waffle-jna.jar
    • Waffle-jna-jasper.jar
    • Jna.jar
    • Platform.jar

    <jasperserver-home>/apache-tomcat/webapps/jasperserver-pro/WEB-INF/lib

    • Waffle-jna.jar
    • Waffle-jna-jasper.jar
    • Waffle-ntlm.jar
  2. Copy the following jar files from <jasperserver-home>/apache-tomcat/webapps/jasperserver-pro/WEB-INF/lib to <jasperserver-home>/apache-tomcat/lib:

    • commons-logging-1.0.4.jar
    • commons-logging-api-1.1.jar
  3. In a text editor, create this file with the following contents:

    <jasperserver-home>/apache-tomcat/webapps/jasperserver-pro/WEB-INF/applicationContext-waffle.xml

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:util="http://www.springframework.org/schema/util"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd">
     
        <!-- windows authentication provider -->
        <bean id="waffleWindowsAuthProvider"
              class="waffle.windows.auth.impl.WindowsAuthProviderImpl" />
     
        <!-- collection of security filters -->
        <bean id="negotiateSecurityFilterProvider"
              class="waffle.servlet.spi.NegotiateSecurityFilterProvider">
            <constructor-arg ref="waffleWindowsAuthProvider" />
        </bean>
     
        <bean id="basicSecurityFilterProvider"
              class="waffle.servlet.spi.BasicSecurityFilterProvider">
            <constructor-arg ref="waffleWindowsAuthProvider" />
        </bean>
     
        <bean id="waffleSecurityFilterProviderCollection"
              class="waffle.servlet.spi.SecurityFilterProviderCollection">
            <constructor-arg>
                <list>
                    <ref bean="negotiateSecurityFilterProvider" />
                    <ref bean="basicSecurityFilterProvider" />
                </list>
            </constructor-arg>
        </bean>
     
        <bean id="negotiateSecurityFilterEntryPoint"
              class="waffle.spring.NegotiateSecurityFilterEntryPoint">
            <property name="provider"
                      ref="waffleSecurityFilterProviderCollection" />
        </bean>
     
        <!-- spring security filter -->
        <bean id="waffleNegotiateSecurityFilter"
              class="com.jaspersoft.security.waffle.CustomAuthenticationFilter">
            <property name="provider"
                      ref="waffleSecurityFilterProviderCollection" />
            <property name="allowGuestLogin" value="false" />
            <property name="principalFormat" value="fqn" />
            <property name="roleFormat" value="both" />
            <property name="domain" value="*Windows Domain Name*" />
            <property name="userSearch" ref="userSearch" />
            <property name="impersonate" value="false" />
            <!-- property name="roleFilterString" value="JASPER" /-->
            <property name="userAuthorityService">
                <ref bean="${bean.internalUserAuthorityService}"/>
            </property>
            <property name="externalUserProcessor">
                <ref bean="defaultExternalUserProcessor"/>
            </property>
            <property name="multiTenancyConfiguration">
                <ref bean="multiTenancyConfiguration"/>
            </property>
     
            <property name="adminRoles">
                <map>
                    <entry>
                        <key>
                            <value>ROLE_DOMAIN_ADMINS</value>
                        </key>
                        <value>ROLE_ADMINISTRATOR</value>
                    </entry>
                </map>
            </property>
        </bean>
    </beans>

    Replace the *Windows Domain Name* value with the domain name of your Active Directory server. The domain name appears when an Active Directory user logs in to their computer, as shown:

    NOTE: Earlier versions of this document recommended using the fully qualified domain name, i.e. stevejs.com. This is incorrect. The domain name to use here is the one that would appear as a prefix of a user name when logging in to a new domain controller.

    You can also uncomment the roleFilterString property in the waffleNegotiateSecurityFilter bean to only create roles in JasperReports Server whose names match the filter string.

  4. (pre-5.1 release only) Edit the applicationContext-security.xml file:

    <jasperserver-home>/apache-tomcat/webapps/jasperserver-pro/WEB-INF/applicationContext-security.xml

    (Using 5.1+ SSO Framework) Copy the sample-applicationContext-externalAuth-LDAP.xml (community) or sample-applicationContext-externalAuth-LDAP-mt.xml (professional), hereafter referred to as sample-applicationContext-externalAuth-LDAP[-mt].xml for brevity, from the samples/externalAuth-sample-config folder to the WEB-INF folder, rename it to applicationContext-externalAuth-LDAP[-mt].xml by removing the "sample-" prefix from the filename, and edit it:

    <jasperserver-home>/apache-tomcat/webapps/jasperserver-pro/WEB-INF/applicationContext-externalAuth-LDAP[-mt].xml

    Change the ldapAuthenticationProvider, userSearch, and ldapContextSource bean definitions to:

    <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>
            <bean class="org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator">
                <constructor-arg index="0"><ref local="ldapContextSource"/></constructor-arg>
                <constructor-arg index="1"><value></value></constructor-arg>
                <property name="groupRoleAttribute" value="CN"/>
                <property name="groupSearchFilter" value="(&amp;(member={0})(objectClass=group))"/>
                <property name="searchSubtree" value="true"/>
            </bean>
        </constructor-arg>
    </bean>
     
    <bean id="userSearch"
          class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
        <constructor-arg index="0">
            <value></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="ldapContextSource" 
          class="com.jaspersoft.jasperserver.api.security.externalAuth.ldap.JSLdapContextSource">
        </constructor-arg value="ldap://<DOMAIN SERVER HOST>[:<PORT>]/<BASE DN>"/>
        <property name="userDn" value="<ADMINISTRATOR UPN>"/>
        <property name="password" value="<ADMINISTRATOR PASSWORD>"/>
        <property name="referral" value="follow"/>
    </bean>

    IMPORTANT: You will need to replace the <DOMAIN SERVER HOST>, <PORT>, <BASE DN>, <ADMINISTRATOR DN>, and <ADMINISTRATOR PASSWORD> placeholders in the above file with the actual values that correspond with your environment. For example, if your Active Directory Domain credentials are the following:

    • Server DNS name: dsp1.example.test
    • Domain Administrator User Principal Name: Administrator@example.test
    • Domain Administrator password: uhfe32

    Then your ldapContextSource bean definition would be:

    <bean id="ldapContextSource"
          class="com.jaspersoft.jasperserver.api.security.externalAuth.ldap.JSLdapContextSource">
        <constructor-arg value="ldap://dsp1.example.test:389/dc=example,dc=test"/>
        <property name="userDn" value="Administrator@example.test"/>
        <property name="password" value="uhfe32"/>
        <property name="referral" value="follow"/>
    </bean>
  5. (5.1+ SSO Framework Only) Edit the applicationContext-externalAuth-LDAP[-mt].xml, and comment out the proxyAuthenticationProcessingFilter bean:

    <jasperserver-home>/apache-tomcat/webapps/jasperserver-pro/WEB-INF/applicationContext-externalAuth-LDAP[-mt].xml

    <!--bean id="proxyAuthenticationProcessingFilter"
             class="com.jaspersoft.jasperserver.api.security.externalAuth.BaseAuthenticationProcessingFilter">
            <property name="authenticationManager">
                <ref local="ldapAuthenticationManager"/>
            </property>
            <property name="externalDataSynchronizer">
                <ref local="externalDataSynchronizer"/>
            </property>
     
            <property name="sessionRegistry">
                <ref bean="sessionRegistry"/>
            </property>
     
            <property name="internalAuthenticationFailureUrl" value="/login.html?error=1"/>
            <property name="defaultTargetUrl" value="/loginsuccess.html"/>
            <property name="invalidateSessionOnSuccessfulAuthentication" value="true"/>
            <property name="migrateInvalidatedSessionAttributes" value="true"/>
     
            <property name="authenticationDetailsSource">
                <bean class="org.springframework.security.ui.AuthenticationDetailsSourceImpl">
                    <property name="clazz">
                        <value>com.jaspersoft.jasperserver.multipleTenancy.MTWebAuthenticationDetails</value>
                    </property>
                </bean>
            </property>
        </bean-->
  6. Edit the applicationContext-security-web.xml file:

    <jasperserver-home>/apache-tomcat/webapps/jasperserver-pro/WEB-INF/applicationContext-security-web.xml

    Add the waffleNegotiateSecurityFilter bean to the wildcard (/**) filter chain immediately before the ${bean.basicProcessingFilter} bean (pre-5.5) or the delegatingBasicProcessingFilter bean (5.5):

    <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},${bean.basicProcessingFilter},JIAuthenticationSynchronizer,anonymousProcessingFilter,basicAuthExceptionTranslationFilter,filterInvocationInterceptor
                    /services/**=httpSessionContextIntegrationFilter,${bean.loggingFilter},${bean.portletAuthenticationProcessingFilter}, delegatingBasicProcessingFilter,${bean.passwordExpirationProcessingFilter},JIAuthenticationSynchronizer,anonymousProcessingFilter,wsBasicAuthExceptionTranslationFilter,filterInvocationInterceptor
                    /rest/login=httpSessionContextIntegrationFilter,${bean.loggingFilter}, encryptionFilter,delegatingAuthenticationRestProcessingFilter,JIAuthenticationSynchronizer,anonymousProcessingFilter,filterInvocationInterceptor
                    /rest/**=httpSessionContextIntegrationFilter,${bean.loggingFilter},${bean.portletAuthenticationProcessingFilter},delegatingBasicProcessingFilter,${bean.passwordExpirationProcessingFilter},JIAuthenticationSynchronizer,anonymousProcessingFilter,wsBasicAuthExceptionTranslationFilter,filterInvocationInterceptor
                    /rest_v2/**=httpSessionContextIntegrationFilter,encryptionFilter,textOnlyResponseWebAppSecurityFilter,jsCsrfGuardFilter,${bean.loggingFilter},${bean.userPreferencesFilter},${bean.authenticationProcessingFilter},${bean.userPreferencesFilter},delegatingBasicProcessingFilter,delegatingRequestParameterAuthenticationFilter,JIAuthenticationSynchronizer,anonymousProcessingFilter,restExceptionTranslationFilter,filterInvocationInterceptor
                    /**=httpSessionContextIntegrationFilter,encryptionFilter,multipartRequestWrapperFilter,webAppSecurityFilter,jsCsrfGuardFilter,${bean.loggingFilter},${bean.userPreferencesFilter},delegatingAuthenticationProcessingFilter,${bean.userPreferencesFilter},waffleNegotiateSecurityFilter,delegatingBasicProcessingFilter,delegatingRequestParameterAuthenticationFilter,JIAuthenticationSynchronizer,anonymousProcessingFilter,delegatingExceptionTranslationFilter,filterInvocationInterceptor,switchUserProcessingFilter,iPadSupportFilter
                </value>
            </property>
        </bean>

    IMPORTANT: The JIAuthenticationSynchronizer bean shown above was removed from the filter chains in the JasperReports Server 5.1 and 5.2 releases. For the NTLM integration to work, it will need to be re-added to the filter chain in the location shown above. Please also note that the delegatingRequestParameterAuthenticationFilter bean was called requestParameterAuthenticationFilter in the v5.0 and earlier releases.

    In the exceptionTranslationFilter bean, comment out the following line:

    <ref local="authenticationProcessingFilterEntryPoint"/>

    and add the following line immediately below it:

    <ref bean="negotiateSecurityFilterEntryPoint"/>
  7. On the Windows Server hosting the Active Directory controller, download the SetSPN.exe file from Microsoft (if necessary), open a command line window, and run the following command:

    SetSPN.exe -S http/<fully-qualified-tomcat-server-name> <AD-domain-name>\<tomcat-username>

    UPDATE: According to the Apache Tomcat documentation, the Tomcat server must either run on port 80 or you must configure IIs to forward requests to the Tomcat server. Also, the SPN entry cannot contain a port number. For more information on how to configure Tomcat with Waffle, see https://tomcat.apache.org/tomcat-7.0-doc/windows-auth-howto.html#Waffle. For more information on how to configure IIs to forward requests to Tomcat, see https://ashrafhossain.wordpress.com/2010/09/20/how-to-configure-iis-7-and-tomcat-redirection-on-windows-server-2008-64-bit/.

    Launch the Server Manager application if necessary, and set the Active Directory user that's running the Tomcat server to use Trusted Delegation using Kerberos:

  8. On the client machine, add the JasperReports Server website URL to the Intranet Zone in Internet Options:

    • Open Internet Explorer and click Tools > Internet Options.
    • Click the Security tab.
    • Click the Local Intranet zone and click the Sites button.
    • Click the Advanced button.
    • In the text box under "Add this website to the zone:", enter http://<fully-qualified-tomcat-server-name> and click the Add button.
    • Click the Close and OK buttons to exit.

    For more information on how to add websites to the Internet Explorer Intranet Zone using a Group Policy Object on the Windows Domain Controller, see https://blog.thesysadmins.co.uk/group-policy-internet-explorer-security-zones.html

  9. Restart Tomcat, open a browser window and navigate to the following URL:

    http://<fully-qualified-tomcat-server-name>/jasperserver-pro

NOTE: On Firefox browsers, Windows Integrated Security is not enabled by default. To enable, enter "about:config" on the URL, use the search term negotiate and set the following properties:

  • network.negotiate-auth.delegation-uris: <fully-qualified-tomcat-server-name>
  • network.negotiate-auth.trusted-uris: <fully-qualified-tomcat-server-name>
  • network.negotiate-auth.using-native-gssapi: true

It may be necessary to close and reopen the Firefox browser for the changes to take effect.

For more information on integrating Apache Tomcat with Windows Active Directory, see http://tomcat.apache.org/tomcat-7.0-doc/windows-auth-howto.html

For more information on setting SPNs and enabling trusted delegation, see https://geertbaeten.wordpress.com/2013/06/03/kerberos-authentication-and-delegation-serviceprincipalnames/

Feedback
randomness