...
The clients' browsers need to be configured to support SPNEGO. See Single sign-on Browser configuration for details.
General Configuration
Localtabgroup |
localtab-live Expand |
---|
|
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 |
---|
|
active | true |
---|
Expand |
---|
|
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 |
---|
title | Whether client's IP address contains a certain address prefix |
---|
|
Code Block |
---|
| // 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 |
---|
title | Whether user agent's identifier string contains certain text |
---|
|
Code Block |
---|
| // 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 |
---|
title | Whether a cookie has a certain value |
---|
|
Code Block |
---|
| // 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 |
---|
| <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 |
---|
| //
// 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 |
---|
| //
// 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 |
---|
| <!-- 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 |
---|
| <%@ 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 |
---|
| ## 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 |
---|
|
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 |
---|
|
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 |
---|
|
active | true |
---|
Expand |
---|
|
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 |
---|
| <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 |
---|
title | Flow Descriptor XML (V4.1+) |
---|
|
To replace the internally defined flow descriptor bean, the following XML is required: Code Block |
---|
| <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. |
...