Showing posts with label sso. Show all posts
Showing posts with label sso. Show all posts

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! ;)

Wednesday, July 24, 2013

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

I wrote in previous post that passing username and password via parameter in Pentaho to show report viewer is a bad thing. Why? It's obvious that technique is not secure because anybody can read clearly username and password and can use it to get any information from your Pentaho BI Server. So, how if I want to integrate Pentaho to my application but I don't want to do double login: one login for my application and another login for Pentaho? We can use a technique called Single Sign On so that ANY application can be accessed only from one time login. I'm using this technique for developing +MaxLogistix: a low cost cloud warehouse management system.

I have read these 2 articles as my reference:
  1. http://tecnologia.2020mobile.es/single-sign-on-sso-on-pentaho-community-edition-using-cas/
  2. http://blog.datamensional.com/2011/07/pentaho-sso-setup-using-cas-and-ldap/
Those articles are good and very informative but... I found that these articles are not complete enough because there is some lost informations in one of them that given in another. So that I followed instructions in those articles and tried by myself which part is working and which part is not. The result is finally I can login to Pentaho using CAS.

So... is the problem solved? Sadly... not yet! There is one problem has not been solved by those articles. The problem is I can login to Pentaho using CAS and when I logged out from Pentaho, CAS status is also logged out, but.... Pentaho status still logged in eventhough I logged out from CAS. So the new problem I encountered is also SSO, not Single Sign ON but Single Sign OUT!

Well then I spent about a month trying to figure out what's wrong with my configuration. I read Pentaho forum, wiki, and any articles that explains about SSO. Even I was trying to replace spring-security library with the new one, but it didn't work because new spring-security library needs new version of spring library. And when I was trying to replace spring library in Pentaho with the new one, I just knew that I must refactor and recompile ALL Pentaho module with that new library. For me it's very frustating and I almost gave up.

And suddenly I found the solution in these 2 articles provided by wiki.jasig.org itself (jasig.org is an organization that manage CAS development)
  1. https://wiki.jasig.org/display/CASC/Using+the+CAS+Client+3.1+with+Spring+Security
  2. https://wiki.jasig.org/display/CASC/Configuring+Single+Sign+Out
The conclusion I got from those articles is... CAS Single Sign Out Filter must be in the first order to make Single Sign Out work perfectly. I will share a tutorial how to use CAS to implement SSO in Pentaho.