Issue #2868 - Adding SPNEGO authentication support for Jetty Client.
Removed old deprecated SPNEGO implementation on server-side. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
c25b7196bd
commit
1ca4e12c3b
|
@ -19,11 +19,12 @@
|
|||
[[spnego-support]]
|
||||
=== SPNEGO Support
|
||||
|
||||
Simple and Protected GSSAPI Negotiation Mechanism (SPNEGO) is a way for users to be seamlessly authenticated when running on a Windows or Active Directory based network.
|
||||
Jetty supports this type of authentication and authorization through the JDK (which has been enabled since the later versions of Java 6 and 7).
|
||||
Also important to note is that this is an _incredibly_ fragile setup where everything needs to be configured just right for things to work, otherwise it can fail in fun and exciting, not to mention obscure, ways.
|
||||
Simple and Protected GSSAPI Negotiation Mechanism (SPNEGO) is a way for users
|
||||
to be seamlessly authenticated when running on systems that rely on Kerberos
|
||||
for authentication, such as Windows Active Directory based networks.
|
||||
|
||||
There is a substantial amount of configuration and testing required to enable this feature as well as knowledge and access to central systems on a Windows network such as the Active Domain Controller and the ability to create and maintain service users.
|
||||
Jetty supports this type of authentication and authorization through the JDK
|
||||
(which has been enabled since the later versions of Java 6 and 7).
|
||||
|
||||
==== Configuring Jetty and SPNEGO
|
||||
|
||||
|
@ -31,97 +32,62 @@ To run with SPNEGO enabled the following command line options are required:
|
|||
|
||||
[source,screen, subs="{sub-order}"]
|
||||
----
|
||||
-Djava.security.krb5.conf=/path/to/jetty/etc/krb5.ini \
|
||||
-Djava.security.auth.login.config=/path/to/jetty/etc/spnego.conf \
|
||||
-Djavax.security.auth.useSubjectCredsOnly=false
|
||||
-Djava.security.krb5.conf=/path/to/krb5.ini
|
||||
----
|
||||
|
||||
For debugging the SPNEGO authentication the following options are very helpful:
|
||||
For debugging the SPNEGO authentication the following options are helpful:
|
||||
|
||||
[source,screen, subs="{sub-order}"]
|
||||
----
|
||||
-Dorg.eclipse.jetty.LEVEL=debug \
|
||||
-Dsun.security.spnego.debug=all
|
||||
-Dorg.eclipse.jetty.LEVEL=debug
|
||||
-Dsun.security.spnego.debug=true
|
||||
-Dsun.security.jgss.debug=true
|
||||
-Dsun.security.krb5.debug=true
|
||||
----
|
||||
|
||||
SPNEGO Authentication must be enabled in the webapp in the following way.
|
||||
SPNEGO authentication must be enabled in the webapp in the following way.
|
||||
The name of the role will be different for your network.
|
||||
|
||||
[source, xml, subs="{sub-order}"]
|
||||
----
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Secure Area</web-resource-name>
|
||||
<url-pattern>/secure/me/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<!-- this is the domain that the user is a member of -->
|
||||
<role-name>MORTBAY.ORG</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
<login-config>
|
||||
<auth-method>SPNEGO</auth-method>
|
||||
<realm-name>Test Realm</realm-name>
|
||||
<!-- optionally to add custom error page -->
|
||||
<spnego-login-config>
|
||||
<spnego-error-page>/loginError.html?param=foo</spnego-error-page>
|
||||
</spnego-login-config>
|
||||
</login-config>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Secure Area</web-resource-name>
|
||||
<url-pattern>/secure/me/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<!-- this is the domain that the user is a member of -->
|
||||
<role-name>MORTBAY.ORG</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
<login-config>
|
||||
<auth-method>SPNEGO</auth-method>
|
||||
<realm-name>Test Realm</realm-name>
|
||||
<!-- optionally to add custom error page -->
|
||||
<spnego-login-config>
|
||||
<spnego-error-page>/loginError.html?param=foo</spnego-error-page>
|
||||
</spnego-login-config>
|
||||
</login-config>
|
||||
----
|
||||
|
||||
A corresponding `UserRealm` needs to be created either programmatically if embedded, via the `jetty.xml` or in a context file for the webapp.
|
||||
A corresponding `UserRealm` needs to be created either programmatically if
|
||||
embedded, via the `jetty.xml` or in a context file for the webapp.
|
||||
|
||||
This is what the configuration within a Jetty xml file would look like.
|
||||
This is what the configuration within a context XML file would look like:
|
||||
|
||||
[source, xml, subs="{sub-order}"]
|
||||
----
|
||||
|
||||
<Call name="addBean">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.security.SpnegoLoginService">
|
||||
<Set name="name">Test Realm</Set>
|
||||
<Set name="config"><Property name="jetty.home" default="."/>/etc/spnego.properties</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
<Get name="securityHandler">
|
||||
<Set name="loginService">
|
||||
<New class="org.eclipse.jetty.security.ConfigurableSpnegoLoginService">
|
||||
<Arg>Test Realm</Arg>
|
||||
<Arg><Ref refid="authorizationService" /></Arg>
|
||||
<Set name="keyTabPath"><Ref refid="keyTabPath" /></Set>
|
||||
</New>
|
||||
</Set>
|
||||
</Get>
|
||||
----
|
||||
|
||||
This is what the configuration within a context xml file would look like.
|
||||
|
||||
[source, xml, subs="{sub-order}"]
|
||||
----
|
||||
|
||||
<Get name="securityHandler">
|
||||
<Set name="loginService">
|
||||
<New class="org.eclipse.jetty.security.SpnegoLoginService">
|
||||
<Set name="name">Test Realm</Set>
|
||||
<Set name="config">
|
||||
<SystemProperty name="jetty.home" default="."/>/etc/spnego.properties
|
||||
</Set>
|
||||
</New>
|
||||
</Set>
|
||||
<Set name="checkWelcomeFiles">true</Set>
|
||||
</Get>
|
||||
|
||||
|
||||
----
|
||||
|
||||
There are a number of important configuration files with S3pnego that are required. The default values for these configuration files from this
|
||||
test example are found in the `/etc` folder of the Jetty distribution.
|
||||
|
||||
spnego.properties::
|
||||
configures the user realm with runtime properties
|
||||
krb5.ini::
|
||||
configures the underlying kerberos setup
|
||||
spnego.conf::
|
||||
configures the glue between gssapi and kerberos
|
||||
|
||||
It is important to note that the keytab file referenced in the `krb5.ini` and the `spnego.conf` files needs to contain the keytab for the `targetName` for the http server.
|
||||
To do this use a process similar to this:
|
||||
|
||||
On the Windows Active Domain Controller run:
|
||||
|
||||
[source, screen, subs="{sub-order}"]
|
||||
|
@ -129,15 +95,15 @@ On the Windows Active Domain Controller run:
|
|||
$ setspn -A HTTP/linux.mortbay.org ADUser
|
||||
----
|
||||
|
||||
To create the keytab file use the following process:
|
||||
To create the keyTab file use the following process:
|
||||
|
||||
[source, screen, subs="{sub-order}"]
|
||||
----
|
||||
$ ktpass -out c:\dir\krb5.keytab -princ HTTP/linux.mortbay.org@MORTBAY.ORG -mapUser ADUser -mapOp set -pass ADUserPWD -crypto RC4-HMAC-NT -pType KRB5_NT_PRINCIPAL
|
||||
----
|
||||
|
||||
This step will give you the keytab file which should then be copied to the machine running the http server and referenced from the configuration files.
|
||||
For our testing we put the keytab into the `/etc` directory of Jetty and referenced it from there.
|
||||
This step will give you the keyTab file which should then be copied to the
|
||||
machine running the http server and referenced from the configuration files.
|
||||
|
||||
==== Configuring Firefox
|
||||
|
||||
|
@ -161,7 +127,6 @@ The follows steps have been required to inform Internet Explorer that it should
|
|||
7. Tools -> Options -> Advanced -> Security -> Ok
|
||||
8. Close IE then reopen and browse to your SPNEGO protected resource
|
||||
|
||||
|
||||
You *must* use hostname and not the IP.
|
||||
If you use the IP it will default to NTLM authentication.
|
||||
The following conditions must be true for SPNEGO authentication to work:
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
This setup will enable you to authenticate a user via SPNEGO into your
|
||||
webapp.
|
||||
|
||||
To run with SPNEGO enabled the following command line options are required:
|
||||
|
||||
-Djava.security.krb5.conf=/path/to/jetty/etc/krb5.ini
|
||||
-Djava.security.auth.login.config=/path/to/jetty/etc/spnego.conf
|
||||
-Djavax.security.auth.useSubjectCredsOnly=false
|
||||
|
||||
The easiest place to put these lines are in the start.ini file.
|
||||
|
||||
For debugging the SPNEGO authentication the following options are helpful:
|
||||
|
||||
-Dorg.eclipse.jetty.LEVEL=debug
|
||||
-Dsun.security.spnego.debug=true
|
||||
|
||||
|
||||
SPNEGO Authentication is enabled in the webapp with the following setup.
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Secure Area</web-resource-name>
|
||||
<url-pattern>/secure/me/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>MORTBAY.ORG</role-name> <-- this is the domain that the user is a member of
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
|
||||
<login-config>
|
||||
<auth-method>SPNEGO</auth-method>
|
||||
<realm-name>Test Realm</realm-name>
|
||||
(optionally to add custom error page)
|
||||
<spnego-login-config>
|
||||
<spnego-error-page>/loginError.html?param=foo</spnego-error-page>
|
||||
</spnego-login-config>
|
||||
</login-config>
|
||||
|
||||
A corresponding UserRealm needs to be created either programmatically if
|
||||
embedded, via the jetty.xml or in a context file for the webapp.
|
||||
|
||||
(in the jetty.xml)
|
||||
|
||||
<Call name="addBean">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.security.SpnegoLoginService">
|
||||
<Set name="name">Test Realm</Set>
|
||||
<Set name="config"><Property name="jetty.home" default="."/>/etc/spnego.properties</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
(context file)
|
||||
<Get name="securityHandler">
|
||||
<Set name="loginService">
|
||||
<New class="org.eclipse.jetty.security.SpnegoLoginService">
|
||||
<Set name="name">Test Realm</Set>
|
||||
<Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/spnego.properties</Set>
|
||||
</New>
|
||||
</Set>
|
||||
<Set name="checkWelcomeFiles">true</Set>
|
||||
</Get>
|
|
@ -1,23 +0,0 @@
|
|||
[libdefaults]
|
||||
default_realm = MORTBAY.ORG
|
||||
default_keytab_name = FILE:/path/to/jetty/etc/krb5.keytab
|
||||
permitted_enctypes = aes128-cts aes256-cts arcfour-hmac-md5
|
||||
default_tgs_enctypes = aes128-cts aes256-cts arcfour-hmac-md5
|
||||
default_tkt_enctypes = aes128-cts aes256-cts arcfour-hmac-md5
|
||||
|
||||
|
||||
|
||||
[realms]
|
||||
MORTBAY.ORG = {
|
||||
kdc = 192.168.2.30
|
||||
admin_server = 192.168.2.30
|
||||
default_domain = MORTBAY.ORG
|
||||
}
|
||||
|
||||
[domain_realm]
|
||||
mortbay.org= MORTBAY.ORG
|
||||
.mortbay.org = MORTBAY.ORG
|
||||
|
||||
[appdefaults]
|
||||
autologin = true
|
||||
forwardable = true
|
|
@ -1,19 +0,0 @@
|
|||
com.sun.security.jgss.initiate {
|
||||
com.sun.security.auth.module.Krb5LoginModule required
|
||||
principal="HTTP/vm.mortbay.org@MORTBAY.ORG"
|
||||
keyTab="/path/to/jetty/etc/krb5.keytab"
|
||||
useKeyTab=true
|
||||
storeKey=true
|
||||
debug=true
|
||||
isInitiator=false;
|
||||
};
|
||||
|
||||
com.sun.security.jgss.accept {
|
||||
com.sun.security.auth.module.Krb5LoginModule required
|
||||
principal="HTTP/vm.mortbay.org@MORTBAY.ORG"
|
||||
useKeyTab=true
|
||||
keyTab="/path/to/jetty/etc/krb5.keytab"
|
||||
storeKey=true
|
||||
debug=true
|
||||
isInitiator=false;
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
targetName = HTTP/vm.mortbay.org
|
|
@ -23,9 +23,9 @@ import javax.servlet.ServletContext;
|
|||
import org.eclipse.jetty.security.Authenticator.AuthConfiguration;
|
||||
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
|
||||
import org.eclipse.jetty.security.authentication.ClientCertAuthenticator;
|
||||
import org.eclipse.jetty.security.authentication.ConfigurableSpnegoAuthenticator;
|
||||
import org.eclipse.jetty.security.authentication.DigestAuthenticator;
|
||||
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
||||
import org.eclipse.jetty.security.authentication.SpnegoAuthenticator;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
|
||||
|
@ -66,10 +66,10 @@ public class DefaultAuthenticatorFactory implements Authenticator.Factory
|
|||
authenticator=new DigestAuthenticator();
|
||||
else if (Constraint.__FORM_AUTH.equalsIgnoreCase(auth))
|
||||
authenticator=new FormAuthenticator();
|
||||
else if ( Constraint.__SPNEGO_AUTH.equalsIgnoreCase(auth) )
|
||||
authenticator = new SpnegoAuthenticator();
|
||||
else if ( Constraint.__NEGOTIATE_AUTH.equalsIgnoreCase(auth) ) // see Bug #377076
|
||||
authenticator = new SpnegoAuthenticator(Constraint.__NEGOTIATE_AUTH);
|
||||
else if (Constraint.__SPNEGO_AUTH.equalsIgnoreCase(auth))
|
||||
authenticator = new ConfigurableSpnegoAuthenticator();
|
||||
else if (Constraint.__NEGOTIATE_AUTH.equalsIgnoreCase(auth)) // see Bug #377076
|
||||
authenticator = new ConfigurableSpnegoAuthenticator(Constraint.__NEGOTIATE_AUTH);
|
||||
if (Constraint.__CERT_AUTH.equalsIgnoreCase(auth)||Constraint.__CERT_AUTH2.equalsIgnoreCase(auth))
|
||||
authenticator=new ClientCertAuthenticator();
|
||||
|
||||
|
|
|
@ -1,196 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.servlet.ServletRequest;
|
||||
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.eclipse.jetty.util.B64Code;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.ietf.jgss.GSSContext;
|
||||
import org.ietf.jgss.GSSCredential;
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.GSSManager;
|
||||
import org.ietf.jgss.GSSName;
|
||||
import org.ietf.jgss.Oid;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link ConfigurableSpnegoLoginService} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class SpnegoLoginService extends AbstractLifeCycle implements LoginService
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(SpnegoLoginService.class);
|
||||
|
||||
protected IdentityService _identityService;// = new LdapIdentityService();
|
||||
protected String _name;
|
||||
private String _config;
|
||||
|
||||
private String _targetName;
|
||||
|
||||
public SpnegoLoginService()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public SpnegoLoginService( String name )
|
||||
{
|
||||
setName(name);
|
||||
}
|
||||
|
||||
public SpnegoLoginService( String name, String config )
|
||||
{
|
||||
setName(name);
|
||||
setConfig(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
if (isRunning())
|
||||
{
|
||||
throw new IllegalStateException("Running");
|
||||
}
|
||||
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public String getConfig()
|
||||
{
|
||||
return _config;
|
||||
}
|
||||
|
||||
public void setConfig( String config )
|
||||
{
|
||||
if (isRunning())
|
||||
{
|
||||
throw new IllegalStateException("Running");
|
||||
}
|
||||
|
||||
_config = config;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
Properties properties = new Properties();
|
||||
Resource resource = Resource.newResource(_config);
|
||||
properties.load(resource.getInputStream());
|
||||
|
||||
_targetName = properties.getProperty("targetName");
|
||||
|
||||
LOG.debug("Target Name {}", _targetName);
|
||||
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* username will be null since the credentials will contain all the relevant info
|
||||
*/
|
||||
@Override
|
||||
public UserIdentity login(String username, Object credentials, ServletRequest request)
|
||||
{
|
||||
String encodedAuthToken = (String)credentials;
|
||||
|
||||
byte[] authToken = B64Code.decode(encodedAuthToken);
|
||||
|
||||
GSSManager manager = GSSManager.getInstance();
|
||||
try
|
||||
{
|
||||
Oid krb5Oid = new Oid("1.3.6.1.5.5.2"); // http://java.sun.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
|
||||
GSSName gssName = manager.createName(_targetName,null);
|
||||
GSSCredential serverCreds = manager.createCredential(gssName,GSSCredential.INDEFINITE_LIFETIME,krb5Oid,GSSCredential.ACCEPT_ONLY);
|
||||
GSSContext gContext = manager.createContext(serverCreds);
|
||||
|
||||
if (gContext == null)
|
||||
{
|
||||
LOG.debug("SpnegoUserRealm: failed to establish GSSContext");
|
||||
}
|
||||
else
|
||||
{
|
||||
while (!gContext.isEstablished())
|
||||
{
|
||||
authToken = gContext.acceptSecContext(authToken,0,authToken.length);
|
||||
}
|
||||
if (gContext.isEstablished())
|
||||
{
|
||||
String clientName = gContext.getSrcName().toString();
|
||||
String role = clientName.substring(clientName.indexOf('@') + 1);
|
||||
|
||||
LOG.debug("SpnegoUserRealm: established a security context");
|
||||
LOG.debug("Client Principal is: " + gContext.getSrcName());
|
||||
LOG.debug("Server Principal is: " + gContext.getTargName());
|
||||
LOG.debug("Client Default Role: " + role);
|
||||
|
||||
SpnegoUserPrincipal user = new SpnegoUserPrincipal(clientName,authToken);
|
||||
|
||||
Subject subject = new Subject();
|
||||
subject.getPrincipals().add(user);
|
||||
|
||||
return _identityService.newUserIdentity(subject,user, new String[]{role});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (GSSException gsse)
|
||||
{
|
||||
LOG.warn(gsse);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserIdentity user)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityService getIdentityService()
|
||||
{
|
||||
return _identityService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentityService(IdentityService service)
|
||||
{
|
||||
_identityService = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(UserIdentity user)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.authentication;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.security.ServerAuthException;
|
||||
import org.eclipse.jetty.security.UserAuthentication;
|
||||
import org.eclipse.jetty.server.Authentication;
|
||||
import org.eclipse.jetty.server.Authentication.User;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link ConfigurableSpnegoAuthenticator} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class SpnegoAuthenticator extends LoginAuthenticator
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(SpnegoAuthenticator.class);
|
||||
private String _authMethod = Constraint.__SPNEGO_AUTH;
|
||||
|
||||
public SpnegoAuthenticator()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow for a custom authMethod value to be set for instances where SPNEGO may not be appropriate
|
||||
* @param authMethod the auth method
|
||||
*/
|
||||
public SpnegoAuthenticator( String authMethod )
|
||||
{
|
||||
_authMethod = authMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthMethod()
|
||||
{
|
||||
return _authMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException
|
||||
{
|
||||
HttpServletRequest req = (HttpServletRequest)request;
|
||||
HttpServletResponse res = (HttpServletResponse)response;
|
||||
|
||||
String header = req.getHeader(HttpHeader.AUTHORIZATION.asString());
|
||||
String authScheme = getAuthSchemeFromHeader(header);
|
||||
|
||||
if (!mandatory)
|
||||
{
|
||||
return new DeferredAuthentication(this);
|
||||
}
|
||||
|
||||
// The client has responded to the challenge we sent previously
|
||||
if (header != null && isAuthSchemeNegotiate(authScheme))
|
||||
{
|
||||
String spnegoToken = header.substring(10);
|
||||
|
||||
UserIdentity user = login(null,spnegoToken, request);
|
||||
|
||||
if ( user != null )
|
||||
{
|
||||
return new UserAuthentication(getAuthMethod(),user);
|
||||
}
|
||||
}
|
||||
|
||||
// A challenge should be sent if any of the following cases are true:
|
||||
// 1. There was no Authorization header provided
|
||||
// 2. There was an Authorization header for a type other than Negotiate
|
||||
try
|
||||
{
|
||||
if (DeferredAuthentication.isDeferred(res))
|
||||
{
|
||||
return Authentication.UNAUTHENTICATED;
|
||||
}
|
||||
|
||||
LOG.debug("Sending challenge");
|
||||
res.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString());
|
||||
res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return Authentication.SEND_CONTINUE;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new ServerAuthException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the auth_scheme from the HTTP Authorization header, {@code Authorization: <auth_scheme> <token>}.
|
||||
*
|
||||
* @param header The HTTP Authorization header or null.
|
||||
* @return The parsed auth scheme from the header, or the empty string.
|
||||
*/
|
||||
String getAuthSchemeFromHeader(String header)
|
||||
{
|
||||
// No header provided, return the empty string
|
||||
if (header == null || header.isEmpty())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
// Trim any leading whitespace
|
||||
String trimmed_header = header.trim();
|
||||
// Find the first space, all characters prior should be the auth_scheme
|
||||
int index = trimmed_header.indexOf(' ');
|
||||
if (index > 0) {
|
||||
return trimmed_header.substring(0, index);
|
||||
}
|
||||
// If we don't find a space, this is likely malformed, just return the entire value
|
||||
return trimmed_header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if provided auth scheme text from the Authorization header is case-insensitively
|
||||
* equal to {@code negotiate}.
|
||||
*
|
||||
* @param authScheme The auth scheme component of the Authorization header
|
||||
* @return True if the auth scheme component is case-insensitively equal to {@code negotiate}, False otherwise.
|
||||
*/
|
||||
boolean isAuthSchemeNegotiate(String authScheme)
|
||||
{
|
||||
if (authScheme == null || authScheme.length() != HttpHeader.NEGOTIATE.asString().length())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Headers should be treated case-insensitively, so we have to jump through some extra hoops.
|
||||
return authScheme.equalsIgnoreCase(HttpHeader.NEGOTIATE.asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -18,10 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.security.authentication;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
|
@ -38,16 +34,15 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test class for {@link SpnegoAuthenticator}.
|
||||
*/
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class SpnegoAuthenticatorTest {
|
||||
private SpnegoAuthenticator _authenticator;
|
||||
private ConfigurableSpnegoAuthenticator _authenticator;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception
|
||||
public void setup()
|
||||
{
|
||||
_authenticator = new SpnegoAuthenticator();
|
||||
_authenticator = new ConfigurableSpnegoAuthenticator();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -67,7 +62,6 @@ public class SpnegoAuthenticatorTest {
|
|||
@Override
|
||||
public void close()
|
||||
{
|
||||
return;
|
||||
}
|
||||
};
|
||||
Response res = new Response(channel, out);
|
||||
|
@ -97,7 +91,6 @@ public class SpnegoAuthenticatorTest {
|
|||
@Override
|
||||
public void close()
|
||||
{
|
||||
return;
|
||||
}
|
||||
};
|
||||
Response res = new Response(channel, out);
|
||||
|
@ -112,37 +105,4 @@ public class SpnegoAuthenticatorTest {
|
|||
assertEquals(HttpHeader.NEGOTIATE.asString(), res.getHeader(HttpHeader.WWW_AUTHENTICATE.asString()));
|
||||
assertEquals(HttpServletResponse.SC_UNAUTHORIZED, res.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaseInsensitiveHeaderParsing()
|
||||
{
|
||||
assertFalse(_authenticator.isAuthSchemeNegotiate(null));
|
||||
assertFalse(_authenticator.isAuthSchemeNegotiate(""));
|
||||
assertFalse(_authenticator.isAuthSchemeNegotiate("Basic"));
|
||||
assertFalse(_authenticator.isAuthSchemeNegotiate("basic"));
|
||||
assertFalse(_authenticator.isAuthSchemeNegotiate("Digest"));
|
||||
assertFalse(_authenticator.isAuthSchemeNegotiate("LotsandLotsandLots of nonsense"));
|
||||
assertFalse(_authenticator.isAuthSchemeNegotiate("Negotiat asdfasdf"));
|
||||
assertFalse(_authenticator.isAuthSchemeNegotiate("Negotiated"));
|
||||
assertFalse(_authenticator.isAuthSchemeNegotiate("Negotiate-and-more"));
|
||||
|
||||
assertTrue(_authenticator.isAuthSchemeNegotiate("Negotiate"));
|
||||
assertTrue(_authenticator.isAuthSchemeNegotiate("negotiate"));
|
||||
assertTrue(_authenticator.isAuthSchemeNegotiate("negOtiAte"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractAuthScheme()
|
||||
{
|
||||
assertEquals("", _authenticator.getAuthSchemeFromHeader(null));
|
||||
assertEquals("", _authenticator.getAuthSchemeFromHeader(""));
|
||||
assertEquals("", _authenticator.getAuthSchemeFromHeader(" "));
|
||||
assertEquals("Basic", _authenticator.getAuthSchemeFromHeader(" Basic asdfasdf"));
|
||||
assertEquals("Basicasdf", _authenticator.getAuthSchemeFromHeader("Basicasdf asdfasdf"));
|
||||
assertEquals("basic", _authenticator.getAuthSchemeFromHeader(" basic asdfasdf "));
|
||||
assertEquals("Negotiate", _authenticator.getAuthSchemeFromHeader("Negotiate asdfasdf"));
|
||||
assertEquals("negotiate", _authenticator.getAuthSchemeFromHeader("negotiate asdfasdf"));
|
||||
assertEquals("negotiate", _authenticator.getAuthSchemeFromHeader(" negotiate asdfasdf"));
|
||||
assertEquals("negotiated", _authenticator.getAuthSchemeFromHeader(" negotiated asdfasdf"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue