/
SPNEGOAuthnConfiguration

The Shibboleth IdP V3 software has reached its End of Life and is no longer supported. This documentation is available for historical purposes only. See the IDP4 wiki space for current documentation on the supported version.

SPNEGOAuthnConfiguration

Current File(s): conf/authn/spnego-authn-config.xml, views/spnego-unavailable.vm, views/user-prefs.vm

Format: Native Spring

Overview

The authn/SPNEGO login flow supports SPNEGO-based Kerberos authentication, complying with RFC 4559, "SPNEGO-based Kerberos and NTLM HTTP Authentication" (http://tools.ietf.org/html/rfc4559). (It only supports Kerberos, but not the NTLM protocol.)

This mechanism allows the IdP to authenticate users by verifying a Kerberos service ticket sent by the client. Most current web browsers, including Internet Explorer, Firefox, Chrome, Opera, Safari and Konqueror, support SPNEGO/Kerberos based authentication. SPNEGO/Kerberos is most-often used in Microsoft Windows environments, and typically assumes the client machine is joined to a domain so that Kerberos credentials are obtained automatically. It can be tested, and given more technically-skilled users, used, without a domain-joined machine. It also works with MIT or Heimdal Kerberos, not just AD.

This login flow differs from the password-based Kerberos authentication provided by the authn/Password login flow. Where the authn/Password login flow relies on the password submitted to the IdP, the authn/SPNEGO login flow consumes a Kerberos ticket provided by the client, and the IdP never sees the password.

By default, this flow is configured without support for advanced authentication controls like passive or forced authentication, since this is generally not possible with SPNEGO authentication.

The SPNEGO login flow can be used via "opt-in" mode or "enforced" mode. In "opt-in" mode, users need to enable login via SPNEGO using an auto-login checkbox or button (see below). In "enforced" mode, SPNEGO is always tried (though possibly skipped in some cases based on an activation condition), independent of the auto-login option set by the users. By default, "opt-in" mode is used. The "enforced" mode is recommended only if you can ensure that Kerberos works in most situations for which any attached activation condition applies.

To use the authn/SPNEGO login flow, you need to install and configure Kerberos on your IdP server first. This includes the creation of a service principal in the Kerberos realm for your service, and usually includes obtaining a keytab file for that principal. A service password may also be used. See Kerberos Infrastructure for more information.

General configuration of Kerberos is outside the scope of the IdP, and not described in detail here, but no native Kerberos libraries beyond Oracle's Java implementation are required or used.

Requirements

Kerberos Infrastructure

To use the authn/SPNEGO login flow, it is necessary to have the Kerberos environment configured and working properly.

Some interesting tutorials that may help are:

http://www.grolmsnet.de/kerbtut/

HTTP-Based Cross-Platform Authentication by Using the Negotiate Protocol.

Before you start, please check that:

  • The service-principal (usually "HTTP/principal@your_realm.com") was configured at the KDC.
  • The keytab file holding the key of the service-principal was generated (using a keytab is recommended).
  • On the IdP server: Check if it is possible to get the service tickets from the KDC with the command kinit.

Browser Configuration

The clients' browsers need to be configured to support SPNEGO. See Single sign-on Browser configuration for details.

General Configuration

Use conf/authn/spnego-authn-config.xml to configure this flow.

To enable "opt-in" mode, set the value of the bean shibboleth.authn.SPNEGO.EnforceRun to "FALSE". This is the default. To enable "enforced" mode, set the value to "TRUE".

You also need to configure the Kerberos service principal(s) you want to use in the shibboleth.authn.SPNEGO.Krb5.Realms bean. A usual configuration involves a single realm and service principal. If your environment contains multiple realms, you may need to configure more than one service principal. They will be tried in sequence when attempting to accept a ticket from the client.

Each value of the realms list bean must be a bean inherited from shibboleth.KerberosRealmSettings and identifies the service principal and keytab file or password to use. A keytab is recommended, but is a bit more work to obtain securely from your Kerberos administrator. Service principals used with SPNEGO MUST be of the form "HTTP/hostname", where "hostname" is the FQDN of the IdP service. This is required because the client will request a service ticket for that principal.

Example Realm Configuration
<util:list id="shibboleth.authn.SPNEGO.Krb5.Realms">

    <bean parent="shibboleth.KerberosRealmSettings"
        p:servicePrincipal="HTTP/idp.example.org@DOMAIN_A.COM"
        p:keytab="%{idp.home}/credentials/http_domainA.keytab" />

    <bean parent="shibboleth.KerberosRealmSettings"
        p:servicePrincipal="HTTP/idp.example.org@DOMAIN_B.COM"
        p:password="ihavennokeytab" />

</util:list>

The shibboleth.authn.SPNEGO.ClassifiedMessageMap bean is a map of error messages to classified error conditions. The default mapping supplied allows a couple of errors raised by the external SPNEGO implementation to be passed through so that meaningful error messages specific to SPNEGO can be shown (e.g., on the login form of the Password login flow in a fall-through scenario). Usually, you won't need to change this mapping, but if you want other errors to cause the IdP to "fall through" to other login flows, you would need to map them to the "ReselectFlow" key.

Finally, note that a common deployment model is to enable the SPNEGO and the Password login flows together, requiring that both be enabled, usually via the conf/idp.properties file:

idp.authn.flows=SPNEGO|Password

Kerberos System Configuration

The actual Kerberos configuration is managed in a krb5.conf or krb5.ini file that can be placed in a number of different locations. On non-Windows servers, using a system-wide configuration in /etc/krb5.conf is generally advised. It's possible to have Java-specific configurations and/or provide the path to a configuration using the system property java.security.krb5.conf, as discussed here. If you want to set the system property java.security.krb5.conf, you should set it in the configuration of your Servlet container.

Configuration of an Activation Condition

There are known cases where the SPNEGO login process might break and stop in the browser, without returning to the IdP. As the IdP can't recover from this situation, the user can't continue the login process.

The following case is known:

  • Internet Explorer and Edge: If these browsers get a SPNEGO login request, but Kerberos is not available, they will fallback to the (deprecated) NTLM mechanism and show a login box to the user. No response will be sent to the IdP (unless the user actually enters credentials), and the user might get confused. (This case actually affects all Internet Explorer and Edge users who are not logged into a Windows Domain. Other browsers, like Firefox, Chrome, Safari, are not affected.)

The IdP provides a flexible mechanism to help avoid some of these situations, the use of an activation condition attached to the login flow's descriptor bean.

The SPNEGO protocol itself doesn't provide any way for the IdP to reliably decide whether the browser supports a Kerberos login or not. You need to specify the conditions under which a Kerberos login will be available in your environment, typically based on the client's IP address or some text appearing in the user agent's identifier string. It's possible to implement such a condition in Java, but using a script is usually simpler.

Note: If the activation condition disables the SPNEGO login flow, SPNEGO won't be available during the current login conversation (not even as "extended login flow" on the Password login page). Make sure that your activation condition disabled SPNEGO only if you are sure that the current client doesn't support SPNEGO at all.


An activation condition is configured in the file conf/authn/general-authn.xml as follows (this isn't the only way, but it serves as an example):

  • Add a new bean anywhere in the file (but outside the <util:list id="shibboleth.AvailableAuthenticationFlows"> bean) with the ID shibboleth.SPNEGO.ActivationCondition with parent bean shibboleth.Conditions.Scripted.
  • By setting the customObject property of this bean to the bean named shibboleth.HttpServletRequest, the current HTTP request object is available in the variable custom. This allows accessing various properties of the user agent, e.g., the current IP address of the client or the user agent's identifier string. Examples are included below.
  • Specify the condition using JavaScript inside the <value> element (between <![CDATA[ and ]]> lines). The last expression in your code should evaluate to true or false. (The result needs to be convertable to a Boolean object.) The example below shows an activation condition where the JavaScript code always evaluates to true, i.e. SPNEGO is always activated:

    Trivial Example
    <bean id="shibboleth.SPNEGO.ActivationCondition" parent="shibboleth.Conditions.Scripted" factory-method="inlineScript"
    		p:customObject-ref="shibboleth.HttpServletRequest">
        <constructor-arg>
            <value>
                <![CDATA[
                    // Always run the SPNEGO authentication flow.
                    true;
                ]]>
            </value>
        </constructor-arg>
    </bean>
    
  • Add the activation condition to the flow descriptor bean with ID authn/SPNEGO (already in the file), by adding the attribute p:activationCondition-ref with a value of shibboleth.SPNEGO.ActivationCondition:

    Adding the Activation Condition
    <bean id="authn/SPNEGO" parent="shibboleth.AuthenticationFlow" 
    		p:nonBrowserSupported="false" 
    		p:activationCondition-ref="shibboleth.SPNEGO.ActivationCondition">
    	<property name="supportedPrincipals">
    		<list>
    			<bean parent="shibboleth.SAML2AuthnContextClassRef"
    				c:classRef="urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos" />
    			<bean parent="shibboleth.SAML1AuthenticationMethod"
    				c:method="urn:ietf:rfc:1510" />
    		</list>
    	</property>
    </bean>

Example Conditions in JavaScript:

The following examples assume that the condition property customObject is set to the bean shibboleth.HttpServletRequest.

Whether client's IP address contains a certain address prefix
// Default return value.
var activate = false;

// Check the client's IP address.
if (custom.remoteAddr.startsWith("192.168.42.")) {
    activate = true;
}

// Return the result.
activate;
Whether user agent's identifier string contains certain text
// Default return value.
var activate = false;

// Check the user agent's identifier string.
var identifier = custom.getHeader("User-Agent");
if (identifier != null && identifier.match(/Kerberos/)) {
    activate = true;
}

// Return the result.
activate;
Whether a cookie has a certain value
 // Default return value.
var activate = false;

// Check whether the cookie "_idp_spnego_autologin" is set.
var cookies = custom.getCookies();
for (var i = 0; i < cookies.length; i++) {
    var cookie = cookies[i];
    if (cookie.getName() == "_idp_spnego_autologin" && cookie.getValue != null && cookie.getValue() == "true") {
        activate = true;
    }
}

// Return the result.
activate;


The following example shows a full-featured script including logging. To see the log lines in the IdP's log file, you need to configure a logger with name "shibboleth.SPNEGO.ActivationCondition" and log level "DEBUG" in your log configuration file conf/logback.xml.

Full implementation, including logging
<bean id="shibboleth.SPNEGO.ActivationCondition" parent="shibboleth.Conditions.Scripted" factory-method="inlineScript"
		p:customObject-ref="shibboleth.HttpServletRequest">
    <constructor-arg>
        <value>
            <![CDATA[
                // This script activates SPNEGO if the client is part
                // of the network 192.168.42.0/24, the user agent's
                // identifier string contains the term "Kerberos",
                // or the cookie "_idp_spnego_autologin" is set to "true".

                // Create logger object. (Syntax for Java 1.8/Nashorn.)
				var logger = Java.type("org.slf4j.LoggerFactory").getLogger("shibboleth.SPNEGO.ActivationCondition");
				// For Java 1.7 do this instead:
				// importPackage(Packages.org.slf4j);
				// logger = LoggerFactory.getLogger("shibboleth.SPNEGO.ActivationCondition");

                // Default return value.
                var activate = false;

                // Make HTTPServletRequest object known as "request".
                var request = custom;

                // Check the client's IP address.
                if (request.remoteAddr.startsWith("192.168.42.")) {
                    logger.debug("Activating SPNEGO for client in network 192.168.42.0/24.");
                    activate = true;
                }

                if (!activate) {
                    // Check the user agent's identifier string.
                    var identifier = request.getHeader("User-Agent");
                    if (identifier != null && identifier.match(/Kerberos/)) {
                        logger.debug("Activating SPNEGO for client with term 'Kerberos' in user agent's identifier string.");
                        activate = true;
                    }
                }

                if (!activate) {
                    // Check whether the cookie "_idp_spnego_autologin" is set.
                    var cookies = request.getCookies();
                    for (var i = 0; i < cookies.length; i++) {
                        var cookie = cookies[i];
                        if (cookie.getName() == "_idp_spnego_autologin" && cookie.getValue != null && cookie.getValue() == "true") {
                            logger.debug("Activating SPNEGO for client having cookie '_idp_spnego_autologin' set to 'true'.");
                            activate = true;
                        }
                    }
                }

                // Return the result.
                activate;
            ]]>
        </value>
    </constructor-arg>
</bean>


The following code examples might be helpful for you to implement your script.

Useful / Help - Scripts
//
// Test the IP Range
//

var ipToLong = function(ip) {
    var components = ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
    if (components) {
        var iplong = 0;
        var power  = 1;
        for (var i = 4; i >= 1; i -= 1) {
            iplong += power * parseInt(components[i]);
            power *= 256;
        }
        return iplong;
    } else {
        return -1;
    }
};

ipOkFrom = ipToLong("10.208.0.0");
ipOkTo = ipToLong("10.239.255.255");
remIp = ipToLong("10.208.0.1");
if (remIp >= ipOkFrom && remIp <= ipOkTo) {
  activate = true;
} else {
  activate = false;
}


The following boilerplate code might be helpful for you to test your scripts outside of the IdP. (The script creates a mock custom object that can be used to test various functionality.)

Boilerplate code to test a script
//
// Use this boilerplate code if you want to test your script standalone (i.e. outside of the IdP).
// For testing, you can use a service like https://jsfiddle.net for example.
//

my_cookie1 = new Object();
my_cookie1.getName = function() { return "my_cookie_name"};
my_cookie1.getValue = function() { return "my_cookie_value"};

function Custom() {
    // Define the client's IP address
    this.remoteAddr = "127.0.10.10";
    
    // Define the client's user agent identifier string
    this.getHeader = function(header_str) {
      my_header = {"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0",} 
      return my_header[header_str];
    }

    // Define a cookie to be returned
    this.getCookies = function() {
      return [my_cookie1];
    }
}

var custom = new Custom();

// Examples
alert("my IP = " + custom.remoteAddr);
alert("my cookie = " + custom.getCookies()[0].getValue());
alert("my browser = " + custom.getHeader("User-Agent"));


For more on the Java 7/8 differences, see IdPJava1.8 in the V2 documentation. Fo a reference on JavaScript, see Mozilla's JavaScript reference.

Troubleshooting

While implementing your activation condition, you may encounter the following problems:

  • The IdP doesn't start because of a syntax error in the JavaScript code. You won't find any error messages in the IdP's log file (i.e. logs/idp-process.log). In this case, consult the log files of your Java container. There, you should find a javax.script.ScriptException exception telling you more about the problem.
  • The script produces a runtime error while it is executed. In this case, you will find an error message in the IdP's log file (i.e. logs/idp-process.log) telling more about the error. The log line will look similar to this:
    2015-11-13 11:44:13,408 - ERROR [net.shibboleth.idp.profile.logic.ScriptedPredicate:119] - Anonymous Scripted Predicate : Error while executing Predicate script
    <Exception>
  • The script runs successfully, but evaluates to false. This means that the authn/SPNEGO flow won't be available.
    If you added logging to your script and adapted your logging configuration accordingly, as described above, you will find appropriate log messages in the IdP's log file (i.e. logs/idp-process.log).

Auto Login Management

Users can enable or disable the auto-login cookie by visiting a URL at the path "/idp/profile/user/prefs". You can customize the page displayed by modifying the view template in views/user-prefs.vm.

An alternative is to include the authn/SPNEGO login flow as an "extended" login flow within the authn/Password login flow and allow the users to enable auto-login on the Password login page. This is described below.

Extending the Password Login Flow

If you use the SPNEGO flow in "opt-in" mode, you may want to allow users to login with SPNEGO as an alternative to a Password login. You can make the SPNEGO login option available on the Password login page, and optionally allow users to enable auto-login with SPNEGO at the same time.

Note that SPNEGO as an alternative login method to the Password login is available only if the relying party doesn't request a specific authentication context class that is incompatible with the use of SPNEGO (which would typically be a sign of a poor SP configuration as well). If SPNEGO is not compatible with the requested authentication context class, SPNEGO won't be available on the Password login page. This is checked for you by the default login form template as part of the extended flow feature.

The Extended Flow feature of the Password flow is not specific to the use of SPNEGO and is described more generally in the PasswordAuthnConfiguration / Extended Flow Calling topic. SPNEGO-specific examples are included below.

First, you need to configure SPNEGO as an extended login flow in conf/authn/password-authn-config.xml. Add "SPNEGO" to the bean called shibboleth.authn.Password.ExtendedFlows, and add the auto-login signaling parameter to the bean called shibboleth.authn.Password.ExtendedFlowParameters:

Enabling Extended Flow in conf/authn/password-authn-config.xml
<bean id="shibboleth.authn.Password.ExtendedFlows" class="java.lang.String" c:_0="SPNEGO" />

<util:list id="shibboleth.authn.Password.ExtendedFlowParameters">
	<value>_shib_idp_SPNEGO_enable_autologin</value>
</util:list>


Then, you (usually) will want to adapt the view template view/login.vm. The default view template creates a login button for each extended flow, but doesn't include on option to enable "auto-login". You may adapt the default view by replacing the existing "#foreach" loop by the following code, or you may want to replace it more fully to provide a more appropriate presentation model for the use of SPNEGO in your environment.

Velocity markup to add auto-login checkbox to views/login.vm
            #foreach ($extFlow in $extendedAuthenticationFlows)
              #if ($authenticationContext.isAcceptable($extFlow) a