Thursday, July 25, 2013

Pentaho Single Sign On (SSO) using CAS (tutorial)

In previous story I share about my journey in +MaxLogistix project to implement SSO in Pentaho using CAS. In this article I share completely, step by step how to implement Single Sign On/Out (SSO) using CAS. In this tutorial I use Windows, D:\pentaho\biserver-ce\tomcat\ as my tomcat folder, and D:\pentaho\biserver-ce\pentaho-solutions\ as my pentaho solutions folder.
  1. Download CAS Server. Choose the latest version (3.5.2) and extract cas-server-webapp-3.5.2.war from modules folder. Rename it to cas.war and copy that file to D:\pentaho\biserver-ce\tomcat\webapps\. When Tomcat is running, it will extract that file to new folder named cas.

    Also extract cas-server-support-jdbc-3.5.2.jar from modules folder and copy that file to D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF\lib\.

    And copy commons-dbcp-1.4.jar and commons-pool-1.5.7.jar from D:\pentaho\biserver-ce\tomcat\webapps\pentaho\WEB-INF\lib\ to D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF\lib\





  2. Download CAS Client. Choose latest version (cas-client-core-3.2.1.jar) and copy that file to D:\pentaho\biserver-ce\tomcat\webapps\pentaho\WEB-INF\lib\

  3. Download Spring Security CAS Client version 2.0.5. Place that file in D:\pentaho\biserver-ce\tomcat\webapps\pentaho\WEB-INF\lib\

  4. Create security certificate.
  5. Run as administrator this command in command prompt (cmd).

     "%JAVA_HOME%"\bin\keytool -delete -alias tomcat -keypass changeit  
     "%JAVA_HOME%"\bin\keytool -genkey -alias tomcat -keypass changeit -keyalg RSA  
     "%JAVA_HOME%"\bin\keytool -export -alias tomcat -keypass changeit -file server.crt  
     "%JAVA_HOME%"\bin\keytool -import –alias tomcat -file server.crt -keypass changeit -keystore "%JAVA_HOME%"\jre\lib\security\cacerts  
    

  6. Edit server.xml in D:\pentaho\biserver-ce\tomcat\conf\

  7. Add this part

     <Connector URIEncoding="UTF-8" port="8443"  
       protocol="org.apache.coyote.http11.Http11Protocol" SSLEnabled="true"  
       maxThreads="150" scheme="https" secure="true"keystoreFile="D:/Fabian/.keystore"  
       keyAlias="tomcat" keystorePass="changeit"  
       truststoreFile="C:/Program Files/Java/jdk1.7.0_10/jre/lib/security/cacerts" clientAuth="false"  
       sslProtocol="TLS" />  
    

    Before this part

     <!-- Define an AJP 1.3 Connector on port 8009 -->  
     <Connector URIEncoding="UTF-8" port="8009" protocol="AJP/1.3" redirectPort="8443" />  
    

    Change yellow highlighted part according to user profile and JDK path.

  8. Edit cas.properties in D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF

  9. Change this part

     server.name=http://localhost:8080  
    

    With this part

     server.name=https://localhost:8443  
    

  10. Edit cas.servlet.xml in D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF

  11. Comment this part

     <webflow:flow-execution-listeners>   
       <webflow:listener ref="terminateWebSessionListener" />   
     </webflow:flow-execution-listeners>  
    

    Become this

     <!--  
     <webflow:flow-execution-listeners>   
       <webflow:listener ref="terminateWebSessionListener" />   
     </webflow:flow-execution-listeners>  
     -->  
    

  12. Edit deployerConfigContext.xml in D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF

  13. Replace or comment this part

     <bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />  
    

    With this part

     <bean class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler">  
       <property name="tableUsers"><value>Users</value></property>  
       <property name="fieldUser"><value>user_name</value></property>  
       <property name="fieldPassword"><value>pwdhash</value></property>  
       <property name="dataSource" ref="dataSource"/>  
       <property name="passwordEncoder">  
         <bean class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder" p:characterEncoding="UTF-16LE" >  
           <constructor-arg index="0" value="SHA1" />  
         </bean>  
       </property>  
     </bean>  
    

    Highlighted part is database table setting used for authentication. For this tutorial I use Users table with users_name as user id field and pwdhash as password field. Password encoded by SHA1 method. Encoding UTF-16LE used by Microsoft SQL Server (yes, it's not UTF-16 like in other database engine).

    Still in the same file, add this part

     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">  
       <property name="driverClassName">  
         <value>net.sourceforge.jtds.jdbc.Driver</value>  
       </property>  
       <property name="url">  
         <value>jdbc:jtds:sqlserver://localhost:1433/MainDB</value>  
       </property>  
       <property name="username"><value>authentication</value></property>  
       <property name="password"><value>test123</value></property>  
     </bean>  
    

    Before this part (last line)

     </beans>  
    

    It is database configuration. I use SQL Server with jtds driver and authentication and test123 as database username and password and MainDB as database name. Copy jtds-1.3.1.jar from D:\pentaho\biserver-ce\tomcat\lib\ to D:\pentaho\biserver-ce\tomcat\webapps\cas\WEB-INF\lib\

  14. Create new file named applicationContext-spring-security-cas.xml in D:\pentaho\biserver-ce\pentaho-solutions\system\ and copy paste this code to that file.

  15.  <?xml version="1.0" encoding="UTF-8"?>  
     <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springsource.org/dtd/spring-beans.dtd">  
     <beans default-autowire="no" default-dependency-check="none" default-lazy-init="false">  
          <bean id="filterChainProxy" class="org.springframework.security.util.FilterChainProxy" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="filterInvocationDefinitionSource">  
                    <value>  
                         <![CDATA[CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON  
                         PATTERN_TYPE_APACHE_ANT  
                         /**=securityContextHolderAwareRequestFilter,httpSessionContextIntegrationFilter,logoutFilter,casProcessingFilter,basicProcessingFilter,requestParameterProcessingFilter,anonymousProcessingFilter,pentahoSecurityStartupFilter,exceptionTranslationFilter,filterInvocationInterceptor,casSingleSignOutFilter]]>  
                    </value>  
               </property>  
          </bean>  
          <bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="service" value="http://localhost:8080/pentaho/j_spring_cas_security_check"/>  
               <property name="sendRenew" value="false"/>  
          </bean>  
          <bean id="casProcessingFilter" class="org.springframework.security.ui.cas.CasProcessingFilter" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="authenticationManager">  
                    <ref bean="authenticationManager"/>  
               </property>  
               <property name="authenticationFailureUrl" value="/public/casFailed"/>  
               <property name="defaultTargetUrl" value="/"/>  
               <property name="filterProcessesUrl" value="/j_spring_cas_security_check"/>  
          </bean>  
          <bean id="casSingleSignOutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter" />  
          <bean id="casSingleSignOutHttpSessionListener" class="org.jasig.cas.client.session.SingleSignOutHttpSessionListener" />  
          <bean id="exceptionTranslationFilter" class="org.springframework.security.ui.ExceptionTranslationFilter" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="authenticationEntryPoint">  
                    <ref local="casProcessingFilterEntryPoint"/>  
               </property>  
               <property name="accessDeniedHandler">  
                    <bean class="org.springframework.security.ui.AccessDeniedHandlerImpl" />  
               </property>  
          </bean>  
          <bean id="casProcessingFilterEntryPoint" class="org.springframework.security.ui.cas.CasProcessingFilterEntryPoint" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="loginUrl" value="https://localhost:8443/cas/login"/>  
               <property name="serviceProperties">  
                    <ref local="serviceProperties"/>  
               </property>  
          </bean>  
          <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager" autowire="default" dependency-check="default" lazy-init="default">  
               <property name="providers">  
                    <list>  
                         <ref bean="anonymousAuthenticationProvider"/>  
                         <ref bean="casAuthenticationProvider"/>  
                    </list>  
               </property>  
          </bean>  
          <bean id="casAuthenticationProvider" class="org.springframework.security.providers.cas.CasAuthenticationProvider">  
               <property name="userDetailsService">  
                    <ref bean="userDetailsService"/>  
               </property>  
               <property name="serviceProperties">  
                    <ref local="serviceProperties"/>  
               </property>  
               <property name="ticketValidator">  
                    <ref local="ticketValidator"/>  
               </property>  
               <property name="key" value="my_password_for_this_auth_provider_only"/>  
          </bean>  
          <bean id="ticketValidator" class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator" autowire="default" dependency-check="default" lazy-init="default">  
               <constructor-arg index="0" value="https://localhost:8443/cas"/>  
          </bean>  
          <bean id="logoutFilter" class="org.springframework.security.ui.logout.LogoutFilter" autowire="default" dependency-check="default" lazy-init="default">  
               <constructor-arg value="https://localhost:8443/cas/logout"/>  
               <constructor-arg>  
                    <list>  
                         <bean class="org.pentaho.platform.web.http.security.PentahoLogoutHandler"/>  
                         <bean class="org.springframework.security.ui.logout.SecurityContextLogoutHandler"/>  
                    </list>  
               </constructor-arg>  
               <property name="filterProcessesUrl" value="/Logout"/>  
          </bean>  
          <bean id="loggerListener" class="org.springframework.security.event.authentication.LoggerListener" />  
          <bean id="basicProcessingFilter" class="org.springframework.security.ui.basicauth.BasicProcessingFilter">  
               <property name="authenticationManager">  
                    <ref local="authenticationManager" />  
               </property>  
               <property name="authenticationEntryPoint">  
                    <ref local="basicProcessingFilterEntryPoint" />  
               </property>  
          </bean>  
          <bean id="basicProcessingFilterEntryPoint" class="org.springframework.security.ui.basicauth.BasicProcessingFilterEntryPoint">  
               <property name="realmName" value="Pentaho Realm" />  
          </bean>  
          <bean id="requestParameterProcessingFilter" class="org.pentaho.platform.web.http.security.RequestParameterAuthenticationFilter">  
               <property name="authenticationManager">  
                    <ref local="authenticationManager" />  
               </property>  
               <property name="authenticationEntryPoint">  
                    <ref local="requestParameterProcessingFilterEntryPoint" />  
               </property>  
          </bean>  
          <bean id="requestParameterProcessingFilterEntryPoint" class="org.pentaho.platform.web.http.security.RequestParameterFilterEntryPoint" />  
          <bean id="pentahoSecurityStartupFilter" class="org.pentaho.platform.web.http.security.SecurityStartupFilter">  
               <property name="injectAnonymous" value="true" />  
          </bean>  
          <bean id="anonymousProcessingFilter" class="org.springframework.security.providers.anonymous.AnonymousProcessingFilter">  
               <property name="key" value="foobar" />  
               <property name="userAttribute" value="anonymousUser,Anonymous" />  
          </bean>  
          <bean id="anonymousAuthenticationProvider" class="org.springframework.security.providers.anonymous.AnonymousAuthenticationProvider">  
               <property name="key" value="foobar" />  
          </bean>  
          <bean id="httpSessionContextIntegrationFilter" class="org.springframework.security.context.HttpSessionContextIntegrationFilter" />  
          <bean id="securityContextHolderAwareRequestFilter" class="org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter" />  
          <bean id="httpSessionReuseDetectionFilter" class="org.pentaho.platform.web.http.security.HttpSessionReuseDetectionFilter">  
               <property name="filterProcessesUrl" value="/j_spring_security_check" />  
               <property name="sessionReuseDetectedUrl" value="/Login?login_error=2" />  
          </bean>  
          <bean id="httpRequestAccessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">  
               <property name="allowIfAllAbstainDecisions" value="false" />  
               <property name="decisionVoters">  
                    <list>  
                         <ref bean="roleVoter" />  
                    </list>  
               </property>  
          </bean>  
          <bean id="filterInvocationInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">  
               <property name="authenticationManager">  
                    <ref local="authenticationManager" />  
               </property>  
               <property name="accessDecisionManager">  
                    <ref local="httpRequestAccessDecisionManager" />  
               </property>  
               <property name="objectDefinitionSource">  
                    <value>  
                         <![CDATA[  
                         CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON  
                         \A/docs/.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/mantlelogin/.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/mantle/mantleloginservice/*\Z=Anonymous,Authenticated,ea_admin  
                         \A/mantle/.*\Z=Authenticated,ea_admin  
                         \A/welcome/.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/public/.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/login.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/ping/alive.gif.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/j_spring_security_check.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/getimage.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/getresource.*\Z=Anonymous,Authenticated,ea_admin  
                         \A/admin.*\Z=Admin,uidai_admin  
                         \A/auditreport.*\Z=Admin,uidai_admin  
                         \A/auditreportlist.*\Z=Admin,uidai_admin  
                         \A/versioncontrol.*\Z=Admin,uidai_admin  
                         \A/propertieseditor.*\Z=Admin,uidai_admin  
                         \A/propertiespanel.*\Z=Admin,uidai_admin  
                         \A/subscriptionadmin.*\Z=Admin,uidai_admin  
                         \A/resetrepository.*\Z=Admin,uidai_admin  
                         \A/viewaction.*solution.admin.*\Z=Admin,uidai_admin  
                         \A/scheduleradmin.*\Z=Admin,uidai_admin  
                         \A/publish.*\Z=Admin,uidai_admin  
                         \A/logout.*\Z=Anonymous  
                         \A/solutionrepositoryservice.*component=delete.*solution=system.*\Z=Nobody  
                         \A/solutionrepositoryservice.*solution=system.*component=delete.*\Z=Nobody  
                         .*system.*pentaho.xml.*=Nobody  
                         .*system.*applicationcontext.*.xml.*=Nobody  
                         .*system.*pentahoobjects.spring.xml.*=Nobody  
                         .*system.*pentahosystemconfig.xml.*=Nobody  
                         .*system.*adminplugins.xml.*=Nobody  
                         .*system.*plugin.properties.*=Nobody  
                         .*system.*publisher_config.xml.*=Nobody  
                         .*system.*sessionstartupactions.xml.*=Nobody  
                         .*system.*systemlisteners.xml.*=Nobody  
                         .*system.*hibernate.*=Nobody  
                         .*system.*birt/.*=Nobody  
                         .*system.*dialects/.*=Nobody  
                         .*system.*google/.*=Nobody  
                         .*system.*jasperreports/.*=Nobody  
                         .*system.*jfree/.*=Nobody  
                         .*system.*kettle/.*=Nobody  
                         .*system.*logs/.*=Nobody  
                         .*system.*metadata/.*=Nobody  
                         .*system.*mondrian/.*=Nobody  
                         .*system.*olap/.*=Nobody  
                         .*system.*quartz/.*=Nobody  
                         .*system.*simple-jndi/.*=Nobody  
                         .*system.*smtp-email/.*=Nobody  
                         .*system.*ui/.*=Nobody  
                         .*system.*analysistemplate.tpl.*=Nobody  
                         .*system.*\.\./.*=Nobody  
                         \A/.*\Z=Authenticated,ea_admin  
                         ]]>  
                    </value>  
               </property>  
          </bean>  
     </beans>  
    

  16. Edit pentaho-spring-beans.xml in D:\pentaho\biserver-ce\pentaho-solutions\system\

  17. Change this part

     <import resource="applicationContext-spring-security.xml" />  
    

    Become this part

     <import resource="applicationContext-spring-security-cas.xml" />  
    

  18. Edit web.xml in D:\pentaho\biserver-ce\tomcat\webapps\pentaho\WEB-INF

  19. Add this part

     <filter>  
       <filter-name>CAS Single Sign Out Filter</filter-name>  
       <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>  
     </filter>  
    

    Before this part

     <filter> <!-- This must be the first filter listed in the web.xml -->  
       <filter-name>Set Character Encoding Filter</filter-name>  
       <filter-class>org.pentaho.platform.web.http.filters.PentahoAwareCharacterEncodingFilter</filter-class>  
       <init-param>  
         <param-name>ignore</param-name>  
         <param-value>yes</param-value>  
       </init-param>  
     </filter>  
    

    Add this part

     <filter-mapping>  
       <filter-name>CAS Single Sign Out Filter</filter-name>  
       <url-pattern>/*</url-pattern>  
     </filter-mapping>  
    

    Before this part

     <filter-mapping>  
       <filter-name>Set Character Encoding Filter</filter-name>  
       <url-pattern>/*</url-pattern>  
     </filter-mapping>  
    

    Add this part

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

    After this part

     <listener>  
       <listener-class>org.pentaho.platform.web.http.session.PentahoCacheSessionListener</listener-class>  
     </listener>  
    

  20. Restart tomcat and open http://localhost:8080/pentaho, we will be redirected to https://localhost:8443/cas/login?service=http%3A%2F%2Flocalhost%3A8080%2Fpentaho%2Fj_spring_cas_security_check. Now when you log in/out from CAS, you will be logged in/out from all application you integrated with CAS.

  21. Let's have a drink and party! ;)

No comments:

Post a Comment