SSO Configuration

This section is deprecated. Any authentication configuration which is not managed by LDAP or a database will be implemented as a custom external authentication.

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

Take a look at LDAP configuration.

The aim of this configuration is to allow users to log in to OpenKM with their Windows credentials. To do this, it is necessary to configure some things, including client browsers. 

In our case the whole system is integrated as follows:

  • Active Directory on Windows Server 2008 and Windows Server 2012
  • Tomcat server on a Linux machine
  • Clients on Windows 7 (browsers: Internet Explorer and Firefox)

The following guides have been used as a starting point:

Active Directory

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

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 maps the service (application server) to a domain user. Create a domain user. This user must be used only 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 to the application server. Once the user is created, its properties should be modified to disallow password changes and to prevent account expiration.
  3. Generate keytab file. This file is an encoded copy of the private key of a user in Kerberos. It is used by the web application as an authentication proxy for the credentials sent by the user. The following command is executed:

    C:\>  ktpass -princ HTTP/tomcat.example.com@EXAMPLE.COM -mapuser user@EXAMPLE.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@EXAMPLE.COM: The user created previously.
      3. web.keytab: The file where the private key used by the Tomcat server is exported.
  4. Register new SPN services. A Service Principal Name (SPN) needs to be set up with HTTP and a server name: for example: tomcat.example.org where Tomcat is running. This is used with a 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 the 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; if it's already created an error will be raised indicating it is already present.

  5. Verify services have 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 from which the corresponding roles will be obtained (you should have these from the previous AD integration step). It is recommended to create:
    • An Organizational Unit (OU) named OpenKM
    • Inside the previous Organizational 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 to delete the user and create it again in the domain. Also the following command is available:

$ setspn -D openkmsso

But we do not recommend it. It is always better to delete 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 adding the following configuration to the file /etc/hosts 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 the Domain, KDC server, and allowed algorithms must be specified. The most important algorithms are arcfour-hmac-md5 and rc4-hmac which Microsoft Active Directory offers.

Preserve most of the krb5.conf default configuration and make only the suggested changes 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 may not be exactly as shown below (some additional attributes).

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 not only authentication but also roles, it is necessary to replace the attribute IS_AUTHENTICATED_FULLY  with an authorized role list separated by commas (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 previously created on the server: HTTP/tomcat.example.org@EXAMPLE.COM
  • keyTabLocation:  that is the location in the file system of the Linux machine of the keytab file created on the Active Directory server. (It is possible to use classpath:, but it is preferable to use file:)

Configure LDAP in OpenKM

Once the previous configurations are done, the Tomcat server can be started up. To allow user names and roles to be available inside OpenKM,  it is necessary to configure LDAP.  To do it, it is recommended 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. In the address field, type about:config.
    3. In the filter/search field, type negotiate.
    4. Parameter network.negotiate-auth.trusted-uris may be set to the default https://, which may not work for you. Replace this with your server address (tomcat.example.com in our example).
  • Internet Explorer:
    1. Open Internet Explorer.
    2. Click Tools > Internet Options > Security tab.
    3. In Local intranet section, make sure your server is trusted, i.e., add it to the list (tomcat.example.com in our example).
    4. In advanced options: ensure the "Enable Integrated Windows Authentication" option 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, i.e., add it to the list (tomcat.example.com in our example).
    4. In advanced options: ensure the "Enable Integrated Windows Authentication" option is enabled.
    5. Add your server address (tomcat.example.com in our example)

Troubleshooting

SSO configuration is quite complex. Below you will find some clues and tools that 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 is at Log configuration.

Check Kerberos authentication from the Linux command line

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

$ kinit user@EXAMPLE.ORG

If all is correct, you will be prompted for the password and the login from Linux will succeed. If it goes wrong, there is something wrong in your Kerberos Linux client configuration (krb5.conf).

Listing keys 

Most problems we have found are caused by a bad web.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 the "ktpass" command before executing the "setspn" commands.