SSO Configuration

Before starting the SSO configuration you should have well configured for AD.

Take a look at LDAP configuration.

The aim of this configuration is to allow users to login into OpenKM with their Windows credentials. To do it, it is necesary to configure some stuff, including client's browsers. 

In our case the whole system is integrated by:

  • Active Directory in Windows 2008 and Windows 2012 server
  • Tomcat server in a Linux machine
  • Client on Windows 7 (Browsers Internet Explorer and Firefox)

The following guides have been used as a start point:

Active Directory

It is supposed that there is an Active Directory server installed and running (LDAP implementation) where it is going to be configured all the user accounts that will be used later.

The steps to follow are:

  1. Create a Computer object inside Active Directory that will correspond to the Linux server
    • Once done, modify its properties and activate the following option:
      • Trusted for delegation: This step is important because, if not enabled, negotiation between clients and servers won't be possible.
  2. The server must authenticate against Active Directory  before validating the tickets sent by the client. This authentication is performed using a file (keytab) that map the service (application server) with a domain user. Create a domain user. This user must be used only and exclusively for this purpose because if this user is used to log into another computer, it won't be able to be used to log on the application server. Once created the user, its properties are modified to not allow the modification of the password and the non expiration of the account.
  3. Generate keytab file. This file is an encoded copy of the private key of an user in Kerberos. It is used by the application web as an authentication proxy of the credentials sent by the user. The following command is executed:

    C:\>  ktpass -princ HTTP/tomcat.example.com@EXAMPLE.COM -mapuser user@EXAMAPLE.COM -crypto all -kvno 0 -ptype KRB5_NT_PRINCIPAL -pass * -out web.keytab

    It is important to highlight :
    • 1. tomcat.example.com@EXAMPLE.COM: Full name of the Tomcat server and its domain.
      2. user@EXAMAPLE.COM: The user created previously.
      3. web.keytab: The file where the private key that will be used by the Tomcat server is exported.
  4. Register new SPN services. A Service Principal Name (SPN) needs to be setup with HTTP and a server name: in example: tomcat.example.org where Tomcat is running. This is used with domain user and its keytab is then used as a service credential:

    C:\> setspn -A HTTP/tomcat.example.org@EXAMPLE.ORG user

    C:\> setspn -A HTTP/tomcat.example.org user

    Where tomcat.example.org is the machine where tomcat servlet container is running and user is the user previously created. 

    Usually the previous command "ktpass" should create the service HTTP/tomcat.example.org, when it's created will be raised an error indicating is yet present.

  5. Verify services has been correctly created and linked to the user:

    C:\> setspn -l user

  6. Finally create the groups that will be assigned to the users and which the corresponding roles will be obtainined from ( You should have it from previous AD integration step ). It is recomended to create:
    • An Organization Unit (OU) with name OpenKM
    • Inside the previous Organization Unit , create the groups that will be used as roles
      • ROLE_USER
      • ROLE_ADMIN

If you want to create the spn services again, the best way is deleting the user and create again in th domain. Also there's available the command:

$ setspn -D openkmsso

But we do not suggest it. It's always better deleting the user.

Linux Machine

The first thing to do is to include the application server inside the domain, if it was not done previously. To do it follow the guide:
http://cerowarnings.blogspot.com.es/2011/11/how-to-linux-en-dominio-windows.html

One thing to consider is to add the following configuration to the file /etc/host due to previously known problems:

# ldap in /etc/hosts
192.168.1.110 example.com
192.168.1.110 Schema.Configuration.example.com
192.168.1.110 Configuration.example.com
192.168.1.110 DomainDnsZones.example.com
192.168.1.110 ForestDnsZones.example.com

Kerberos configuration file

Install kerberos client:

$ apt-get krb5-user krb5-config

Before starting the application Kerberos must be configured properly to validate tickets correctly. In Linux systems this file is located at "/etc/krb5.conf".

In this file Domain, KDC server and allowed algorithms must be specified. The most importans algorithms are arcfour-hmac-md5 and rc4-hmac which Microsoft Active Directory offers.

Preservate almost krb5.con default configuration and make only the suggested changed shown below.

[libdefaults]
default_realm = EXAMPLE.COM
#these are mandatory for kerberos.
permitted_enctypes = aes128-cts rc4-hmac
default_tgs_enctypes = aes128-cts rc4-hmac
default_tkt_enctypes = aes128-cts rc4-hmac
[realms] EXAMPLE.COM = {  kdc = SERVER.example.com  admin_server = SERVER.example.com   default_domain = EXAMPLE.COM }
[domain_realm] example.com = EXAMPLE.COM .example.com = EXAMPLE.COM

Configuration files

The appContext.xml file might be modified and the security:http entry do not be exactly as is shown below ( some addional atrributtes ).

To change the authentication type to use it is necessary to modify the following two files:

  • $CATALINA_HOME/webapps/OpenKM/WEB-INF/appContext.xml:
    Change the following line:
<security:http access-decision-manager-ref="accessDecisionManager" access-denied-page="/unauthorized.jsp">

 to

<security:http access-decision-manager-ref="accessDecisionManager" access-denied-page="/unauthorized.jsp" entry-point-ref="spnegoEntryPoint" authentication-manager-ref="authenticationManagerSpnego">

Under this line add:

<security:custom-filter ref="spnegoAuthenticationProcessingFilter" position="BASIC_AUTH_FILTER" />

A detail to take into account inside this file, is that if it is required to filter the access not only but the authentication but the roles too, it is needed to replace the attribute IS_AUTHENTICATED_FULLY  to an authorize role list separated by comas (ROLE_USER, ROLE_ADMIN)

  • $CATALINA_HOME/openkm.xml

    Comment the line:
 <security:authentication-manager alias="authenticationManager">
<security:authentication-provider>
<security:password-encoder hash="md5"/>
<security:jdbc-user-service
data-source-ref="dataSource"
users-by-username-query="select usr_id, usr_password, 1 from OKM_USER where usr_id=? and usr_active='T'"
authorities-by-username-query="select ur_user, ur_role from OKM_USER_ROLE where ur_user=?"/>
</security:authentication-provider>
</security:authentication-manager>

 And add the following:

<beans:bean id="spnegoEntryPoint" class="org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint">
<beans:constructor-arg value="/login" />
</beans:bean>

<beans:bean id="kerberosAuthenticationProvider" class="org.springframework.security.kerberos.authentication.KerberosAuthenticationProvider">
<beans:property name="userDetailsService" ref="ldapUserDetailsService" />
<beans:property name="kerberosClient">
<beans:bean class="org.springframework.security.kerberos.authentication.sun.SunJaasKerberosClient">
<beans:property name="debug" value="true"/>
</beans:bean>
</beans:property>
</beans:bean>

<!--beans:bean id="kerberosServiceAuthenticationProvider" class="org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider"-->
<beans:bean id="kerberosServiceAuthenticationProvider" class="com.openkm.spring.OKMKerberosServiceAuthenticationProvider">
<beans:property name="ticketValidator">
<beans:bean class="org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator">
<beans:property name="servicePrincipal" value="HTTP/tomcat.example.com@EXAMPLE.COM" />
<beans:property name="keyTabLocation" value="file:/path/to/web.keytab" />
<beans:property name="debug" value="true" />
</beans:bean>
</beans:property>
<beans:property name="userDetailsService" ref="ldapUserDetailsService" />
</beans:bean>

<!-- Ticket security manager -->
<security:authentication-manager id="authenticationManagerSpnego">
<security:authentication-provider ref="kerberosAuthenticationProvider" />
<security:authentication-provider ref="kerberosServiceAuthenticationProvider" />
</security:authentication-manager>

<beans:bean id="spnegoAuthenticationProcessingFilter" class="org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter">
<beans:property name="authenticationManager" ref="authenticationManagerSpnego" />
</beans:bean>

<beans:bean id="authorizationContextSource" class="org.springframework.security.kerberos.client.KerberosLdapContextSource">
<beans:constructor-arg value="ldap://server.example.com:389/dc=example,dc=com" />
<beans:property name="loginConfig" ref="LoginConfig"/>
</beans:bean>

<beans:bean id="LoginConfig" class="org.springframework.security.kerberos.client.config.SunJaasKrb5LoginConfig" init-method="afterPropertiesSet">
<beans:property name="servicePrincipal" value="HTTP/tomcat.example.com@EXAMPLE.COM"/>
<beans:property name="keyTabLocation" value="file:/path/to/web.keytab" />
<beans:property name="useTicketCache" value="false" />
<beans:property name="isInitiator" value="true" />
<beans:property name="debug" value="true" />
</beans:bean>

<beans:bean id="ldapUserDetailsService" class="org.springframework.security.ldap.userdetails.LdapUserDetailsService">
<beans:constructor-arg ref="FilterBasedLdapUserSearch" />
<beans:constructor-arg ref="defaultLdapAuthoritiesPopulator" />
<beans:property name="UserDetailsMapper" ref="UserDetailsMapper" />
</beans:bean>

<beans:bean id="FilterBasedLdapUserSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<beans:constructor-arg value="" />
<!--beans:constructor-arg value="dc=example,dc=com" /-->
<beans:constructor-arg value="(| (userPrincipalName={0}) (sAMAccountName={0}))" />
<beans:constructor-arg ref="authorizationContextSource" />
</beans:bean>

<beans:bean id="defaultLdapAuthoritiesPopulator" class="com.openkm.spring.OKMLdapAuthoritiesPopulator"/>
<beans:bean id="UserDetailsMapper" class="org.springframework.security.ldap.userdetails.LdapUserDetailsMapper"/>

<beans:bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<beans:constructor-arg>
<beans:bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
<beans:constructor-arg ref="authorizationContextSource"/>
<beans:property name="userSearch" ref="FilterBasedLdapUserSearch"/>
</beans:bean>
</beans:constructor-arg>
<beans:constructor-arg ref="defaultLdapAuthoritiesPopulator" />
</beans:bean>

<!-- Alternative Ldap security manager -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="ldapAuthProvider" />
</security:authentication-manager>

 The important configuration data to take into account is:

  • ldap://server.example.com:389/dc=example,dc=com" that is the location of the ldap server.
  • servicePrincipal that is the service previusly created at the server: HTTP/tomcat.example.org@EXAMPLE.COM
  • keyTabLocation:  that is the location inside the file system of the linux machine of the keytab file created in the Active directory server. ( It is possible to use classpath: but is preferable use file:)

Configure LDAP in OpenKM

Once done the previously configurations, the Tomcat server can be start up. To allow user name and role to be available inside OpenKM,  it is necessary to configurate LDAP.  To do it, it's recomended to follow the steps of LDAP configuration parameters.

Client browsers:

To allow the automatic credentials negotiation, it is also necessary to configure the client web browsers. This is detailed at: 

http://docs.spring.io/spring-security-kerberos/docs/1.0.0.RC2/reference/htmlsingle/#browserspnegoconfig

  • Firefox
    1. Open Firefox.
    2. At address field, type about:config.
    3. In filter/search, type negotiate.
    4. Parameter network.negotiate-auth.trusted-uris may be set to default https:// which doesn’t work for you. Replace this with your server address (tomcat.example.com in our example).
  • Internet Explorer:
    1. Open Internet Explorer.
    2. Click Tools > Intenet Options > Security tab.
    3. In Local intranet section make sure your server is trusted by i.e. adding it into a list (tomcat.example.com in our example)..
    4. In advanced options: check the option "Enable Integrated Windows Authentication" is enabled
    5. Add your server address (tomcat.example.com in our example)
  • Chrome:
    1. Open Chrome.
    2. Click Configuration> Show Advanced Configuration > Change proxy configuration > Security tab.
    3. In Local intranet section make sure your server is trusted by i.e. adding it into a list (tomcat.example.com in our example)..
    4. In advanced options: check the option "Enable Integrated Windows Authentication" is enabled.
    5. Add your server address (tomcat.example.com in our example)

Throubleshootings

SSO configuration is quite complex. Below you will find some clues and tools what can be useful while trying to configure it.

Enable log for SSO authentication

Modify the log configuration file and add the line:

<logger name="org.springframework.security" level="DEBUG" />

More information about log configuration at Log configuration.

Check kerberos authentication from linux command line

Execute the command ( it's case sensitive and uppercase is important ) :

$ kinit user@EXAMPLE.ORG

If all is right, you will be inquired for setting the password and the login from linux is make. If it's going wrong you have something wrong in your kerberos linux client configuration ( krb5.conf ).

Listing keys 

Almost problems we have found, comes with bad we.keytab creation file. If you get errors like:

rg.springframework.ldap.AuthenticationException: GSSAPI; nested exception is javax.naming.AuthenticationException: GSSAPI [Root exception is javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))]]
	at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:182)
	at org.springframework.ldap.core.support.AbstractContextSource.createContext(AbstractContextSource.java:285)
	at org.springframework.ldap.core.support.AbstractContextSource.doGetContext(AbstractContextSource.java:119)
	at org.springframework.ldap.core.support.AbstractContextSource.getReadOnlyContext(AbstractContextSource.java:138)
	at org.springframework.ldap.core.LdapTemplate.executeReadOnly(LdapTemplate.java:791)
	at org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForSingleEntry(SpringSecurityLdapTemplate.java:194)
	at org.springframework.security.ldap.search.FilterBasedLdapUserSearch.searchForUser(FilterBasedLdapUserSearch.java:116)
	at org.springframework.security.ldap.userdetails.LdapUserDetailsService.loadUserByUsername(LdapUserDetailsService.java:38)
	at com.openkm.spring.OKMKerberosServiceAuthenticationProvider.authenticate(OKMKerberosServiceAuthenticationProvider.java:64)
	at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
	at org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter.doFilter(SpnegoAuthenticationProcessingFilter.java:145)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:225)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:999)
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:565)
	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:309)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
	at java.lang.Thread.run(Thread.java:745)
Caused by: javax.naming.AuthenticationException: GSSAPI [Root exception is javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))]]
	at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:168)
	at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:235)
	at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2740)
	at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:316)
	at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:193)
	at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:211)
	at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:154)
	at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:84)
	at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684)
	at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:307)
	at javax.naming.InitialContext.init(InitialContext.java:242)
	at javax.naming.ldap.InitialLdapContext.<init>(InitialLdapContext.java:153)
	at org.springframework.ldap.core.support.LdapContextSource.getDirContextInstance(LdapContextSource.java:43)
	at org.springframework.security.kerberos.client.KerberosLdapContextSource.access$001(KerberosLdapContextSource.java:66)
	at org.springframework.security.kerberos.client.KerberosLdapContextSource$1.run(KerberosLdapContextSource.java:110)
	at org.springframework.security.kerberos.client.KerberosLdapContextSource$1.run(KerberosLdapContextSource.java:105)
	at java.security.AccessController.doPrivileged(Native Method)
	at javax.security.auth.Subject.doAs(Subject.java:356)
	at org.springframework.security.kerberos.client.KerberosLdapContextSource.getDirContextInstance(KerberosLdapContextSource.java:105)
	at org.springframework.ldap.core.support.AbstractContextSource.createContext(AbstractContextSource.java:273)
	... 39 more
Caused by: javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))]
	at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:212)
	at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:123)
	... 58 more
Caused by: GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))
	at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:710)
	at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:248)
	at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:179)
	at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:193)
	... 59 more
Caused by: KrbException: Server not found in Kerberos database (7)
	at sun.security.krb5.KrbTgsRep.<init>(KrbTgsRep.java:70)
	at sun.security.krb5.KrbTgsReq.getReply(KrbTgsReq.java:192)
	at sun.security.krb5.KrbTgsReq.sendAndGetCreds(KrbTgsReq.java:203)
	at sun.security.krb5.internal.CredentialsUtil.serviceCreds(CredentialsUtil.java:309)
	at sun.security.krb5.internal.CredentialsUtil.acquireServiceCreds(CredentialsUtil.java:115)
	at sun.security.krb5.Credentials.acquireServiceCreds(Credentials.java:456)
	at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:641)
	... 62 more
Caused by: KrbException: Identifier doesn't match expected value (906)
	at sun.security.krb5.internal.KDCRep.init(KDCRep.java:143)
	at sun.security.krb5.internal.TGSRep.init(TGSRep.java:66)
	at sun.security.krb5.internal.TGSRep.<init>(TGSRep.java:61)
	at sun.security.krb5.KrbTgsRep.<init>(KrbTgsRep.java:55)
	... 68 more

Probably your web.keytab  is wrong. Ensure you have executed "ktpass" command before executing the "setspn" ones.