SSO Configuration

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

Before starting the SSO configuration you should have AD properly 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 it, it is necessary to configure some things, including client browsers. 

In our case the whole system is integrated by the following components:

  • Active Directory on Windows Server 2008 and Windows Server 2012
  • Tomcat server on a Linux machine
  • Client 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 and exclusively for this purpose because if this user is used to log in to another computer, it will not be able to be used to log on to the application server. Once the user is created, modify its properties to prevent the password from being changed and to prevent the account from expiring.
  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@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@EXAMPLE.COM: The user created previously.
      3. web.keytab: The file to which 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 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 the 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 them from the previous AD integration step). It is recommended 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 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's always better to delete the user.

Linux Machine

The first thing to do is to include the application server in the domain if it was not done previously. To do so, 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/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 the 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 supports.

Preserve almost all 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 in this file is that if it is required to filter access not only by authentication but also by roles, you need 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 which is the location of the LDAP server.
  • servicePrincipal, which 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 this, it's recommended to follow the steps of LDAP configuration parameters.

Client browsers:

To allow automatic credential 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 > Internet Options > Security tab.
    3. In Local intranet section, make sure your server is trusted, e.g. by adding it to a list (tomcat.example.com in our example).
    4. 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, e.g. by adding it to a list (tomcat.example.com in our example).
    4. 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 can be found 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 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 

Almost all problems we have found are caused by a badly created web.keytab 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.