SPNEGO

New CAS documentation site

CAS documentation has moved over to apereo.github.io/cas, starting with CAS version 4.x. The wiki will no longer be maintained. For the most recent version of the documentation, please refer to the aforementioned link.

CAS Server's Java Version

SPNEGO support is tightly couple to the version of the JVM used by CAS. Full compatibility for SPNEGO requires JDK 1.6u19 and greater. For information about the particulars of using an older version of Java, view Version 21 of this page. 

SPNEGO Basics

Before getting started it will help to understand the steps involved in SPNEGO authentication.  There are three actors involved, the client, the CAS server, and the ActiveDirectory DomainController/KDC. 

Assumptions:

  • Client is logged in to a windows domain
  • Client is Windows XP pro SP2 or greater running IE 6 or IE 7
  • CAS is running on a UNIX server configured for kerberos against the AD server in the windows domain.

Spnego Steps:

  1. Client sends CAS:               HTTP GET to CAS  for cas protected page
  2. CAS responds:                    HTTP 401 - Access Denied WWW-Authenticate: Negotiate
  3. Client sends ticket request:  Kerberos(KRB_TGS_REQ) Requesting  ticket for HTTP/<Fully qualified domain name of CAS>@KERBEROS REALM
  4. Kerberos KDC responds:      Kerberos(KRB_TGS_REP) Granting ticket for HTTP/<Fully qualified domain name of CAS>@KERBEROS REALM
  5. Client sends CAS:               HTTP GET Authorization: Negotiate w/SPNEGO Token
  6. CAS responds:                    HTTP 200 - OK WWW-Authenticate w/SPNEGO response + requested page.

This only happens for the first request, when there is no CAS ticket associated with the users session.  Once CAS grants a ticket, this will not happen again until the CAS ticket expires

Including the Handler

In the pom.xml file for your CAS Maven2 WAR Overlay, add the following dependency:

<dependency>
     <groupId>org.jasig.cas</groupId>
     <artifactId>cas-server-support-spnego</artifactId>
     <version>${cas.version}</version>
</dependency>

Core Classes

JCIFSSpnegoAuthenticationHandler

This is the implementation of an AuthenticationHandler for SPNEGO supports. This Handler support both NTLM and Kerberos. NTLM is disabled by default. This class supports the following properties:

  • principalWithDomainName - boolean to enable or disable domain name in the returned netid
  • NTLMallowed - allows to authenticate using SPNEGO/NTLM token

JCIFSConfig

This class is the configuration helper for JCIFS and the Spring framework. This class supports the following properties:

  • jcifsServicePrincipal - set the Service Principal Name
  • jcifsServicePassword - set the password for the principal name
  • kerberosDebug - boolean to enable or disable the debug mode on Kerberos
  • kerberosRealm - set the Realm
  • kerberosKdc - set the KDC address
  • loginConf - path to the login.conf

SpnegoNegociateCredentialsAction

First action of a SPNEGO flow : negociation. The server checks if the negociation string is in the request header:

  • If found do nothing and return success()
  • else add a WWW-Authenticate response header and a 401 response status, then return success()

SpnegoCredentialsAction

Second action of a SPNEGO flow : decode the gssapi-data and build a new SpnegoCredentials.
Once AbstractNonInteractiveCredentialsAction has executed the authentication procedure, this action check whether a principal is present in Credentials and add correspondings response headers.

Configuration

Set up the Active Directory

A service account should be created. This account is called a Service Principal Name account (SPN account).

Create the User

  1. Start the Active Directory User and Computers from the Administration Tools menu.
  2. Right click on the Users Repository and select New > User
  3. Enter user information (by example myspnaccount for user login), press Next.
  4. Enter the password and select Password never expires and click on Next and then on Finish.

Now that the user account has been created and updated, we need to create a service principal setting for the created user account. This is automatically handled by exporting a keytab file for the created account.

Create the Keytab File

The Keytab file enables a trust link between the CAS server and the Key Distribution Center (KDC). This file contains a cryptographic key. The ktpass tool, which comes from the Windows Resource Kit, is used to generate this file. Be sure that you are running the command on your server where your Active Directory is installed and you are logged in as an administrator.

In a console, enter the command:

ktpass.exe /out myspnaccount.keytab /princ HTTP/your.server.name.here@YOUR.REALM.HERE /pass * /mapuser
myspnaccount@YOUR.REALM.HERE /ptype KRB5_NT_PRINCIPAL /crypto RC4-HMAC-NT

This command will generate the myspnaccount.keytab file which has to be copied on the CAS server in order to test Kerberos from a bash using the MIT Kerberos V. Additionally when the properties of the spn account are viewed in Active Directory Users and Computers, a new delegation tab is displayed.

The syntax can be confusing. Here's an example assuming the samAccountName of the service account is "cassp", the fully qualified domain name of the CAS server is "cas.gasper.local", and a domain name of "gasper.local":

ktpass.exe /out cassp.keytab /princ HTTP/cas.gasper.local@GASPER.LOCAL /pass * /mapuser cassp@GASPER.LOCAL /ptype KRB5_NT_PRINCIPAL /crypto RC4-HMAC-NT


Test the SPN account

First configure MIT Kerberos V on the server. The file is <literal>/etc/krb5.conf</literal>. Here is an example:

[logging]
 default = FILE:/var/log/krb5libs.log
 kdc = FILE:/var/log/krb5kdc.log
 admin_server = FILE:/var/log/kadmind.log

[libdefaults]
 ticket_lifetime = 24000
 default_realm = YOUR.REALM.HERE
 default_keytab_name = /home/cas/kerberos/myspnaccount.keytab
 dns_lookup_realm = false
 dns_lookup_kdc = false
 default_tkt_enctypes = rc4-hmac
 default_tgs_enctypes = rc4-hmac
[realms]
 YOUR.REALM.HERE = {
  kdc = your.kdc.your.realm.here:88
 }

[domain_realm]
 .your.realm.here = YOUR.REALM.HERE
 your.realm.here = YOUR.REALM.HERE

Then verify that your are able to read the keytab file:

klist -k

Then verify that your are able to read the keytab file :

kinit a_user_in_the_realm@YOUR.REALM.HERE
klist

Another test is to try the command:

kinit -V HTTP/your.server.name.here@YOUR.REALM.HERE -k -t /home/cas/kerberos/myspnaccount.keytab

You should not be prompted for a password. Here's the command using the example domain:

kinit -V HTTP/cas.gasper.local@GASPER.LOCAL -k -t /home/user/kerberos/cas.keytab

Set up Browser

Internet Explorer (min 5.01)

  1. In Internet Explorer, click Internet Options on the Tools menu.
  2. Click on the Advanced tab, click to select the Enable Integrated Windows Authentication (requires restart) check box in the Security section, and then click OK.
  3. Click on the Security tab, click to select Local Intranet then click on Sites, then click on Advanced.
  4. Enter https://your.server.name.here_ and validate by clicking on _Add and OK.
  5. Restart Internet Explorer.

Firefox (min 0.9)

  1. In Firefox, enter about:config as url and click on Go.
  2. On the line network.negotiate-auth.trusted-uris, right click on Modify and enter _https://your.server.name.here_

Kerberos authentication does not work from a browser hosted on the CAS SSO server. See this CAS Users thread.

Set Up CAS

Set up the login webflow

The CAS 3 Login Webflow needs to be modified. This webflow is located in /WEB-INF/login-webflow.xml. There are 2 new action states which are placed before the state viewLoginForm.

<action-state id="startAuthenticate">
  <evaluate expression="negociateSpnego" />
  <transition on="success" to="spnego" />
</action-state>

<action-state id="spnego">
  <evaluate expression="spnego" />
  <transition on="success" to="sendTicketGrantingTicket" />
  <transition on="error" to="viewLoginForm" />
</action-state>

And 2 existing transitions need to be update:

  • In the decision-state gatewayRequestCheck, replace reference to viewLoginForm by startAuthenticate
  • In the decision-state renewRequestCheck, replace reference to viewLoginForm by startAuthenticate

diff against version 3.5.2:

diff --git a/webapps/cas/WEB-INF/login-webflow.xml b/webapps/cas/WEB-INF/login-webflow.xml
index 30063e3..151c634 100644
--- a/webapps/cas/WEB-INF/login-webflow.xml
+++ b/webapps/cas/WEB-INF/login-webflow.xml
@@ -78,7 +78,7 @@
                <evaluate expression="passwordPolicyAction" />
                <transition on="showWarning" to="passwordServiceCheck" />
                <transition on="success" to="sendTicketGrantingTicket" />
-               <transition on="error" to="viewLoginForm" />
+               <transition on="error" to="startAuthenticate" />
        </action-state>
 
        <action-state id="passwordServiceCheck">
@@ -113,9 +113,20 @@
        
        <action-state id="generateLoginTicket">
         <evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" />
-               <transition on="generated" to="viewLoginForm" />
+               <transition on="generated" to="startAuthenticate" />
        </action-state>
     
+       <action-state id="startAuthenticate">
+         <evaluate expression="negociateSpnego" />
+         <transition on="success" to="spnego" />
+       </action-state>
+ 
+       <action-state id="spnego">
+         <evaluate expression="spnego" />
+         <transition on="success" to="sendTicketGrantingTicket" />
+         <transition on="error" to="viewLoginForm" />
+       </action-state>
+
        <view-state id="viewLoginForm" view="casLoginView" model="credentials">
         <binder>
             <binding property="username" />
@@ -215,8 +226,8 @@
        <global-transitions>
         <!-- CAS-1023 This one is simple - redirects to a login page (same as renew) when 'ssoEnabled' flag is unchecked
              instead of showing an intermediate unauthorized view with a link to login page -->
-        <transition to="viewLoginForm" on-exception="org.jasig.cas.services.UnauthorizedSsoServiceException"/>
+        <transition to="startAuthenticate" on-exception="org.jasig.cas.services.UnauthorizedSsoServiceException"/>
         <transition to="viewServiceErrorView" on-exception="org.springframework.webflow.execution.repository.NoSuchFlowExecutionException" />
                <transition to="viewServiceErrorView" on-exception="org.jasig.cas.services.UnauthorizedServiceException" />
        </global-transitions>
-</flow>
\ No newline at end of file
+</flow>

/WEB-INF/cas-servlet.xml

Two beans are needed for the login flow. Those beans are:

<bean id="negociateSpnego" class="org.jasig.cas.support.spnego.web.flow.SpnegoNegociateCredentialsAction" />

<bean id="spnego" class="org.jasig.cas.support.spnego.web.flow.SpnegoCredentialsAction">
	<property name="centralAuthenticationService" ref="centralAuthenticationService"/>
</bean>

diff against cas version 3.5.2:

diff --git a/webapps/cas/WEB-INF/cas-servlet.xml b/webapps/cas/WEB-INF/cas-servlet.xml
index 38c0061..2358c73 100644
--- a/webapps/cas/WEB-INF/cas-servlet.xml
+++ b/webapps/cas/WEB-INF/cas-servlet.xml
@@ -28,6 +28,12 @@
 
   <import resource="spring-configuration/propertyFileConfigurer.xml"/>
 
+  <bean id="negociateSpnego" class="org.jasig.cas.support.spnego.web.flow.SpnegoNegociateCredentialsAction" />
+ 
+  <bean id="spnego" class="org.jasig.cas.support.spnego.web.flow.SpnegoCredentialsAction">
+    <property name="centralAuthenticationService" ref="centralAuthenticationService"/>
+  </bean>
+
   <!-- Theme Resolver -->
   <bean id="themeResolver" class="org.jasig.cas.services.web.ServiceThemeResolver"
         p:defaultThemeName="${cas.themeResolver.defaultThemeName}"
@@ -273,4 +279,4 @@
 
   <bean id="credentialsValidator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"
         p:messageInterpolator-ref="messageInterpolator"/>
-</beans>
\ No newline at end of file
+</beans>

/WEB-INF/deployerConfigContext.xml

In the bean authenticationManager, add:
*_ org.jasig.cas.adaptors.spnego.authentication.principal.SpnegoCredentialsToPrincipalResolver_ as credentialsToPrincipalResolvers
*_ org.jasig.cas.adaptors.spnego.authentication.handler.support.JCIFSSpnegoAuthenticationHandler_ as authenticationHandlers

<bean id="authenticationManager" class="org.jasig.cas.authentication.AuthenticationManagerImpl">
  <property name="credentialsToPrincipalResolvers">
    <list>
      <!--  ... the others credentialsToPrincipalResolvers ... -->
      <bean class="org.jasig.cas.support.spnego.authentication.principal.SpnegoCredentialsToPrincipalResolver" />
    </list>
  </property>
  <property name="authenticationHandlers">
    <list>
      <bean class="org.jasig.cas.support.spnego.authentication.handler.support.JCIFSSpnegoAuthenticationHandler">
	<property name="authentication">
	  <bean class="jcifs.spnego.Authentication" />
	</property>
	<property name="principalWithDomainName" value="false" />
	<property name="NTLMallowed" value="true"/>
      </bean>
      <!--  ... the others authenticationHandlers... -->
    </list>
  </property>
</bean>

There is also the jcifsConfig bean which needs to be added.

<bean name="jcifsConfig" class="org.jasig.cas.support.spnego.authentication.handler.support.JCIFSConfig">
  <property name="jcifsServicePrincipal" value="HTTP/your.server.name.here@YOUR.REALM.HERE" />
  <property name="kerberosDebug" value="false" />
  <property name="kerberosRealm" value="YOUR.REALM.HERE" />
  <property name="kerberosKdc" value="THE.KDC.IP.HERE" />
  <property name="loginConf" value="/path/to/WEB-INF/login.conf" />
</bean>

diff against cas version 3.5.2:

diff --git a/webapps/cas/WEB-INF/deployerConfigContext.xml b/webapps/cas/WEB-INF/deployerConfigContext.xml
index 9e0b328..648ec2e 100644
--- a/webapps/cas/WEB-INF/deployerConfigContext.xml
+++ b/webapps/cas/WEB-INF/deployerConfigContext.xml
@@ -41,6 +41,14 @@
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
+
+       <bean name="jcifsConfig" class="org.jasig.cas.support.spnego.authentication.handler.support.JCIFSConfig">
+         <property name="jcifsServicePrincipal" value="HTTP/tomcat@REALM" />
+         <property name="kerberosDebug" value="true" />
+         <property name="kerberosRealm" value="REALM" />
+         <property name="kerberosKdc" value="infra.realm" />
+         <property name="loginConf" value="/usr/local/webapps/cas/WEB-INF/login.conf" />
+       </bean>
        <!--
                | This bean declares our AuthenticationManager.  The CentralAuthenticationService service bean
                | declared in applicationContext.xml picks up this AuthenticationManager by reference to its id, 
@@ -101,6 +109,7 @@
                                        +-->
                                <bean
                                        class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />
+                               <bean class="org.jasig.cas.support.spnego.authentication.principal.SpnegoCredentialsToPrincipalResolver" />
                        </list>
                </property>
 
@@ -124,9 +133,17 @@
                                        | where the username equals the password.  You will need to replace this with an AuthenticationHandler that implements your
                                        | local authentication strategy.  You might accomplish this by coding a new such handler and declaring
                                        | edu.someschool.its.cas.MySpecialHandler here, or you might use one of the handlers provided in the adaptors modules.
-                                       +-->
                                <bean 
                                        class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />
+                                       +-->
+                               <bean class="org.jasig.cas.support.spnego.authentication.handler.support.JCIFSSpnegoAuthenticationHandler">
+                                       <property name="authentication">
+                                               <bean class="jcifs.spnego.Authentication" />
+                                       </property>
+                                       <property name="principalWithDomainName" value="false" />
+                                       <property name="NTLMallowed" value="true"/>
+                               </bean>
+
                        </list>
                </property>
        </bean>

/WEB-INF/login.conf

Copy or create the file /path/to/WEB-INF/login.conf

jcifs.spnego.initiate {
   com.sun.security.auth.module.Krb5LoginModule required storeKey=true useKeyTab=true keyTab="/home/cas/kerberos/myspnaccount.keytab";
};
jcifs.spnego.accept {
   com.sun.security.auth.module.Krb5LoginModule required storeKey=true useKeyTab=true keyTab="/home/cas/kerberos/myspnaccount.keytab";
};

Changes for JBoss

JBoss has its own security manager so specifying the login.conf file above has no effect. This was solved by amending a section in the login-config.xml file in the /SERVER-ROOT/server/default/conf directory as follows:

<application-policy name="other">
  <authentication>
    <login-module code="com.sun.security.auth.module.Krb5LoginModule" flag="required">
      <module-option name="storeKey">true</module-option>
    </login-module>
  </authentication>
</application-policy>

This means that JBoss defaults to using the Kerberos login module when no others are specified. This can be extracted to a custom application policy and specified in a jboss-web.xml file so that it can be explicitly selected.

 

ClassNotFound jcifs/spnego/AuthenticationException

You need the jcifs and jcifs-ext jars in order to make spnego working. They can be downloaded from http://developer.jasig.org/repo/content/groups/m2-legacy/org/samba/jcifs/

If there any dependency problems, you can run 'mvn compile' in the cas source. After that, your maven repo (~/.m2 by defualt) will contain all the dependency jars, go cherrypick!