Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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

General Configuration

localtab-live
Localtabgroup
Expand
titleV4.0

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".

Localtab live
activetrue
Expand
titleV4.1+

A few simple settings are controlled with conf/authn/authn.properties. The others, including the realms to use, are controlled with conf/authn/spnego-authn-config.xml.

The idp.authn.SPNEGO.enforceRun property controls the opt-in/enforcing mode (defaults to opt-in, false).

...

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

Expand
titleWhether client's IP address contains a certain address prefix
Code Block
languagejs
// 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;
Expand
titleWhether user agent's identifier string contains certain text
Code Block
languagejs
// 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;
Expand
titleWhether a cookie has a certain value
Code Block
languagejs
// This example assumes that the deployer uses the cookie "_idp_krb_enabled"
// to control the availability of SPNEGO. Any other cookie name could be
// used. (It has no relation with the cookie used for controlling "auto login".)
// If such a cookie is used, the view `views/user-prefs.vm` should be extended
// to allow the user to manage this setting. (By default, this view only
// supports to manage the auto-login setting.)

// Default return value.
var activate = false;

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

// Return the result.
activate;

If a cookie based activation method is used, the view template `views/user-prefs.js` should be extended to allow the users to manage this option. Just add another option block:

...

Full implementation, including logging
Expand
Code Block
languagexml
<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_krb_enabled" 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_krb_enabled" is set.
                    var cookies = request.getCookies();
                    if (cookies != null) {
                        for (var i = 0; i < cookies.length; i++) {
                            var cookie = cookies[i];
                            if (cookie.getName() == "_idp_krb_enabled" && cookie.getValue != null && cookie.getValue() == "true") {
                                logger.debug("Activating SPNEGO for client having cookie '_idp_krb_enabled' 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
Expand
Code Block
languagejs
//
// 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
Expand
Code Block
languagejs
//
// 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"));

Troubleshooting

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

...

Note

While this approach remains supported, it relies on a feature likely to be removed at some pointin a future version. Using theĀ MFA flow is a better the preferred approach, but no explicit example of this exists.

...

Velocity markup to add auto-login checkbox to views/login.vm
Expand
Code Block
            #foreach ($extFlow in $extendedAuthenticationFlows)
              #if ($authenticationContext.isAcceptable($extFlow) and $extFlow.apply(profileRequestContext))
                #if ($extFlow.getId() == 'authn/SPNEGO')
                <div class="form-element-wrapper">
                  <div class="form-element-wrapper">
                    <input type="checkbox" name="_shib_idp_SPNEGO_enable_autologin" value="true"> #springMessageText("idp.login.spnego.enable_autologin", "Enable auto-login")
                  </div>
                  <button class="form-element form-button" type="submit" name="_eventId_$extFlow.getId()">
                    #springMessageText("idp.login.$extFlow.getId().replace('authn/','')", $extFlow.getId().replace('authn/',''))
                  </button>
                </div>
                #end
              #end
            #end

The sequence of behavior during the login process is normally as follows:

...

Extract the username and realm from the Kerberos Principal name
Expand
Code Block
languagexml
    <!-- The principal name resulting from the authentication. -->
    <AttributeDefinition id="principalName"
                         xsi:type="PrincipalName"
                         dependencyOnly="true">
    </AttributeDefinition>
 
    <!-- Extract the simple username from the Kerberos Principal name. -->
    <AttributeDefinition id="krbPrincipalname"
                         xsi:type="Mapped"
                         dependencyOnly="true">
        <InputAttributeDefinition ref="principalName" />
 
        <DefaultValue passThru="true"/>
        <ValueMap>
            <ReturnValue>$1</ReturnValue>
            <SourceValue>(.+)@EXAMPLE.ORG</SourceValue>
        </ValueMap>
    </AttributeDefinition>
 
    <!-- Map the Kerberos realm to a domain name. -->
    <AttributeDefinition id="krbDomain"
                         xsi:type="Mapped"
                         dependencyOnly="true">
        <InputAttributeDefinition ref="principalName" />

        <DefaultValue passThru="true"/>
        <ValueMap>
            <ReturnValue>domain_a.com</ReturnValue>
            <SourceValue>(.+)@DOMAIN_A.COM</SourceValue>
        </ValueMap>
        <ValueMap>
            <ReturnValue>domain_b.com</ReturnValue>
            <SourceValue>(.+)@DOMAIN_B.COM</SourceValue>
        </ValueMap>
    </AttributeDefinition>

Then, the user directory lookup filter template needs to be adapted to use the attributes defined above:

...

JSP script to check whether SPNEGO works
Expand
Code Block
languagexml
<%@ page trimDirectiveWhitespaces="true" %>
<%@ page import="org.apache.commons.codec.binary.Base64" %>
<%@ page import="java.util.Arrays" %>
<%@ page contentType="application/json; charset=UTF-8" %>
<%
String status = "";
final String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader == null || !authorizationHeader.startsWith("Negotiate ")) {
    status = "SPNEGONotAvailable";
    response.addHeader("WWW-Authenticate", "Negotiate");
    response.setStatus(401);
} else {
    // Test for NTLM mechanism request.
    final byte[] headerNTLM = {(byte) 0x4E, (byte) 0x54, (byte) 0x4C, (byte) 0x4D, (byte) 0x53, (byte) 0x53, (byte) 0x50};
    final byte[] gssapiData = Base64.decodeBase64(authorizationHeader.substring(10).getBytes());
    if (Arrays.equals(headerNTLM, Arrays.copyOfRange(gssapiData, 0, 7))) {
        // GSS-API data represents an NTLM mechanism request.
        status = "NTLMUnsupported";
    } else {
        // Assume GSS-API data represents an SPNEGO mechanism request.
        status = "SPNEGOAvailable";
    }
    response.setStatus(200);
}
%>
{
    "status": "<%= status %>"
}

The login page can send an AJAX query to the URL path /idp/spnego-test.jsp and then evaluate the result:

AJAX example to detect SPENGO using JSP above
Expand
Code Block
languagejs
    ## SPNEGO detection, button fixup
    <script>
        $( document ).ready(function() {
            // query server and test if browser is ready to support SPNEGO
            $.ajax({
                url: "/idp/spnego-test.jsp",
                type: "GET",
                dataType: "json",
            })
            .done(function( json ) {
                var status = json["status"];
                if (status == "SPNEGOAvailable" ) {
                    // display the SPNEGO functionality to the user
                    $('button[name="_eventId_authn/SPNEGO"]').text('Login with SPNEGO');
                    $('button[name="_eventId_authn/SPNEGO"]').css("display", "inline");
                }
            });
        });
    </script>

Note: This example is not part of the main IdP distribution and is provided without warranty.

Reference

Localtabgroupexpand
Localtab live
titleBeans (V4.0)

The beans defined in conf/authn/spnego-authn-config.xml follow:

Bean ID / Type

Default

Description

shibboleth.authn.SPNEGO.externalAuthnPath

String

/Auth/SPNEGO

Servlet-relative path to the SPNEGO external authentication implementation

shibboleth.authn.SPNEGO.externalAuthnPathStrategy

Function<ProfileRequestContext,String>

Optional function that returns the redirection expression to use for the protected resource

shibboleth.authn.SPNEGO.EnforceRun

Boolean

false

Whether to always try to run SPNEGO, independent of the user's auto-login setting (i.e., try to run for all users)

shibboleth.authn.SPNEGO.Krb5.RefreshConfig

Boolean

false

Whether to reload the underlying Kerberos configuration (generally in /etc/krb5.conf) on every login attempt

shibboleth.authn.SPNEGO.Krb5.Realms

Collection<KerberosRealmSettings>

List of service principal names and credentials. At least one is required.

shibboleth.KerberosRealmSettings

KerberosRealmSettings

Parent bean used to define beans of this type

shibboleth.authn.SPNEGO.matchExpression

Pattern

Regular expression to match username against

shibboleth.authn.SPNEGO.ClassifiedMessageMap

Map<String,List<String>>

(see file)

A map between defined error/warning conditions and events and implementation-specific message fragments to map to them

localtab-live
Expand
titleBeans (V4.1+)

The beans defined in conf/authn/spnego-authn-config.xml follow:

Bean ID / Type

Default

Description

shibboleth.authn.SPNEGO.externalAuthnPathStrategy

Function<ProfileRequestContext,String>

Optional function that returns the redirection expression to use for the protected resource

shibboleth.authn.SPNEGO.Krb5.Realms

Collection<KerberosRealmSettings>

List of service principal names and credentials. At least one is required.

shibboleth.KerberosRealmSettings

KerberosRealmSettings

Parent bean used to define beans of this type

shibboleth.authn.SPNEGO.ClassifiedMessageMap

Map<String,List<String>>

(see file)

A map between defined error/warning conditions and events and implementation-specific message fragments to map to them

Localtab live
activetrue
Expand
titleProperties (V4.1+)

The flow-specific properties usable via authn/authn.properties are:

Name

Default

Description

idp.authn.SPNEGO.externalAuthnPath

/Authn/SPNEGO

Servlet-relative path to the SPNEGO external authentication implementation

idp.authn.SPNEGO.enforceRun

false

Whether to always try to run SPNEGO, independent of the user's auto-login setting (i.e., try to run for all users)

idp.authn.SPNEGO.refreshKrbConfig

false

Whether to reload the underlying Kerberos configuration (generally in /etc/krb5.conf) on every login attempt

idp.authn.SPNEGO.matchExpression

Regular expression to match username against

The general properties configuring this flow via authn/authn.properties are:

Name

Default

Description

idp.authn.SPNEGO.order

1000

Flow priority relative to other enabled login flows (lower is "higher" in priority)

idp.authn.SPNEGO.nonBrowserSupported

false

Whether the flow should handle non-browser request profiles (e.g., ECP)

idp.authn.SPNEGO.passiveAuthenticationSupported

false

Whether the flow allows for passive authentication

idp.authn.SPNEGO.forcedAuthenticationSupported

false

Whether the flow supports forced authentication

idp.authn.SPNEGO.proxyRestrictionsEnforced

%{idp.authn.enforceProxyRestrictions:true}

Whether the flow enforces upstream IdP-imposed restrictions on proxying

idp.authn.SPNEGO.proxyScopingEnforced

false

Whether the flow considers itself to be proxying, and therefore enforces SP-signaled restrictions on proxying

idp.authn.SPNEGO.discoveryRequired

false

Whether to invoke IdP-discovery prior to running flow

idp.authn.SPNEGO.lifetime

%{idp.authn.defaultLifetime:PT1H}

Lifetime of results produced by this flow

idp.authn.SPNEGO.inactivityTimeout

%{idp.authn.defaultTimeout:PT30M}

Inactivity timeout of results produced by this flow

idp.authn.SPNEGO.reuseCondition

shibboleth.Conditions.TRUE

Bean ID of Predicate<ProfileRequestContext> controlling result reuse for SSO

idp.authn.SPNEGO.activationCondition

shibboleth.Conditions.TRUE

Bean ID of Predicate<ProfileRequestContext> determining whether flow is usable for request

idp.authn.SPNEGO.subjectDecorator

Bean ID of BiConsumer<ProfileRequestContext,Subject> for subject customization

idp.authn.SPNEGO.supportedPrincipals

(see below)

Comma-delimited list of protocol-specific Principal strings associated with flow

idp.authn.SPNEGO.addDefaultPrincipals

true

Whether to auto-attach the preceding set of Principal objects to each Subject produced by this flow

As a non-password based flow, the supportedPrincipals property defaults to the following XML:

Code Block
languagexml
<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>

In property form, this is expressed as (note the trailing commas):

Code Block
idp.authn.SPNEGO.supportedPrincipals = \
    saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos, \
    saml1/urn:ietf:rfc:1510
localtab-live
Expand
titleFlow Descriptor XML (V4.1+)

To replace the internally defined flow descriptor bean, the following XML is required:

Code Block
languagexml
<util:list id="shibboleth.AvailableAuthenticationFlows">
 
    <bean p:id="authn/SPNEGO" parent="shibboleth.AuthenticationFlow"
            p:order="%{idp.authn.SPNEGO.order:1000}"
            p:nonBrowserSupported="%{idp.authn.SPNEGO.nonBrowserSupported:false}"
            p:passiveAuthenticationSupported="%{idp.authn.SPNEGO.passiveAuthenticationSupported:false}"
            p:forcedAuthenticationSupported="%{idp.authn.SPNEGO.forcedAuthenticationSupported:false}"
            p:proxyRestrictionsEnforced="%{idp.authn.SPNEGO.proxyRestrictionsEnforced:%{idp.authn.enforceProxyRestrictions:true}}"
            p:proxyScopingEnforced="%{idp.authn.SPNEGO.proxyScopingEnforced:false}"
            p:discoveryRequired="%{idp.authn.SPNEGO.discoveryRequired:false}"
            p:lifetime="%{idp.authn.SPNEGO.lifetime:%{idp.authn.defaultLifetime:PT1H}}"
            p:inactivityTimeout="%{idp.authn.SPNEGO.inactivityTimeout:%{idp.authn.defaultTimeout:PT30M}}"
            p:reuseCondition-ref="#{'%{idp.authn.SPNEGO.reuseCondition:shibboleth.Conditions.TRUE}'.trim()}"
            p:activationCondition-ref="#{'%{idp.authn.SPNEGO.activationCondition:shibboleth.Conditions.TRUE}'.trim()}"
            p:subjectDecorator-ref="#{getObject('%{idp.authn.SPNEGO.subjectDecorator:}'.trim())}">
        <property name="supportedPrincipalsByString">
            <bean parent="shibboleth.CommaDelimStringArray"
                c:_0="#{'%{idp.authn.SPNEGO.supportedPrincipals:}'.trim()}" />
        </property>
    </bean>
 
</util:list>

In older versions and upgraded systems, this list is defined in conf/authn/general-authn.xml. In V4.1+, no default version of the list is provided and it may simply be placed in conf/global.xml if needed.

...