From: markt
+ * TODO:
+ *
+ * TBDs:
+ * The SPNEGO Authenticator Valve is automatically added to
+ any Context that is configured to use SPNEGO
+ authentication. If any non-default settings are required, the valve may be configured
+ within Context element with the required
+ values. The SPNEGO Authenticator Valve supports the following
+ configuration attributes: Should we cache authenticated Principals if the request is part of an
+ HTTP session? If not specified, the default value of Java class name of the implementation to use. This MUST be set to
+ org.apache.catalina.authenticator.SpnegoAuthenticator.
+ Controls if the session ID is changed if a session exists at the
+ point where users are authenticated. This is to prevent session fixation
+ attacks. If not set, the default value of Controls the caching of pages that are protected by security
+ constraints. Setting this to Controls the caching of pages that are protected by security
+ constraints. Setting this to Name of the algorithm to use to create the
+ Name of the Java class that extends
+ Name of the provider to use to create the
+ Name of the Kerberos keytab file that contains the private key for
+ the service principal. The name of the service principal must match the
+ spn attribute. Relative file names are relative to
+ Service Principal Name (SPN) for this server. It must match the SPN
+ associated with the key in the serviceKetTab file. If not specified, the
+ default value of
+ *
+ *
+ *
+ */
+public class SpnegoAuthenticator extends AuthenticatorBase {
+
+ private static final Log log = LogFactory.getLog(SpnegoAuthenticator.class);
+
+ protected String serviceKeyTab = Constants.DEFAULT_KEYTAB;
+ protected String spn = null;
+
+ protected Subject serviceSubject = null;
+
+
+ @Override
+ protected String getAuthMethod() {
+ return Constants.SPNEGO_METHOD;
+ }
+
+
+ @Override
+ public String getInfo() {
+ return "org.apache.catalina.authenticator.SpnegoAuthenticator/1.0";
+ }
+
+
+ public String getServiceKeyTab() {
+ return serviceKeyTab;
+ }
+
+
+ public void setServiceKeyTab(String serviceKeyTab) {
+ this.serviceKeyTab = serviceKeyTab;
+ }
+
+
+ public String getSpn() {
+ return spn;
+ }
+
+
+ public void setSpn(String spn) {
+ this.spn = spn;
+ }
+
+
+ @Override
+ protected void initInternal() throws LifecycleException {
+ super.initInternal();
+
+ // Service keytab needs to be an absolute file name
+ File serviceKeyTabFile = new File(serviceKeyTab);
+ if (!serviceKeyTabFile.isAbsolute()) {
+ serviceKeyTabFile =
+ new File(Bootstrap.getCatalinaBase(), serviceKeyTab);
+ }
+
+ // SPN is HTTP/hostname
+ String serviceProvideName;
+ if (spn == null || spn.length() == 0) {
+ // Construct default
+ StringBuilder name = new StringBuilder(Constants.DEFAULT_SPN_CLASS);
+ name.append('/');
+ try {
+ name.append(InetAddress.getLocalHost().getCanonicalHostName());
+ } catch (UnknownHostException e) {
+ // TODO add a message
+ throw new LifecycleException(e);
+ }
+ serviceProvideName = name.toString();
+ } else {
+ serviceProvideName = spn;
+ }
+
+ LoginContext lc;
+ try {
+ lc = new LoginContext("", null, null,
+ new JaasConfig(serviceKeyTabFile.getAbsolutePath(),
+ serviceProvideName, log.isDebugEnabled()));
+ lc.login();
+ serviceSubject = lc.getSubject();
+ } catch (LoginException e) {
+ // TODO add a message
+ throw new LifecycleException(e);
+ }
+ }
+
+
+ @Override
+ public boolean authenticate(Request request, HttpServletResponse response,
+ LoginConfig config) throws IOException {
+
+ // Have we already authenticated someone?
+ Principal principal = request.getUserPrincipal();
+ String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
+ if (principal != null) {
+ if (log.isDebugEnabled())
+ log.debug("Already authenticated '" + principal.getName() + "'");
+ // Associate the session with any existing SSO session
+ if (ssoId != null)
+ associate(ssoId, request.getSessionInternal(true));
+ return true;
+ }
+
+ // Is there an SSO session against which we can try to reauthenticate?
+ if (ssoId != null) {
+ if (log.isDebugEnabled())
+ log.debug("SSO Id " + ssoId + " set; attempting " +
+ "reauthentication");
+ /* Try to reauthenticate using data cached by SSO. If this fails,
+ either the original SSO logon was of DIGEST or SSL (which
+ we can't reauthenticate ourselves because there is no
+ cached username and password), or the realm denied
+ the user's reauthentication for some reason.
+ In either case we have to prompt the user for a logon */
+ if (reauthenticateFromSSO(ssoId, request))
+ return true;
+ }
+
+ MessageBytes authorization =
+ request.getCoyoteRequest().getMimeHeaders()
+ .getValue("authorization");
+
+ if (authorization != null) {
+ authorization.toBytes();
+ ByteChunk authorizationBC = authorization.getByteChunk();
+ if (authorizationBC.startsWithIgnoreCase("negotiate ", 0)) {
+ authorizationBC.setOffset(authorizationBC.getOffset() + 10);
+ // FIXME: Add trimming
+ // authorizationBC.trim();
+
+ ByteChunk decoded = new ByteChunk();
+ Base64.decode(authorizationBC, decoded);
+
+ try {
+ principal = Subject.doAs(serviceSubject,
+ new KerberosAuthAction(decoded.getBytes(), response));
+ } catch (PrivilegedActionException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ if (principal != null) {
+ register(request, response, principal, Constants.SPNEGO_METHOD,
+ principal.getName(), null);
+ return true;
+ }
+ } else {
+ response.setHeader("WWW-Authenticate", "Negotiate");
+ }
+ } else {
+ response.setHeader("WWW-Authenticate", "Negotiate");
+ }
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return false;
+ }
+
+
+ private static class KerberosAuthAction
+ implements PrivilegedExceptionActiontrue
+ will be used.true will be
+ used.false may help work around
+ caching issues in some browsers but will also cause secured pages to be
+ cached by proxies which will almost certainly be a security issue.
+ securePagesWithPragma offers an alternative, secure,
+ workaround for browser caching issues. If not set, the default value of
+ true will be used.false may help work around
+ caching issues in some browsers by using
+ Cache-Control: private rather than the default of
+ Pragma: No-cache and Cache-control: No-cache.
+ If not set, the default value of true will be used.java.security.SecureRandom instances that generate session
+ IDs. If an invalid algorithm and/or provider is specified, the platform
+ default provider and the default algorithm will be used. If not
+ specified, the default algorithm of SHA1PRNG will be used. If the
+ default algorithm is not supported, the platform default will be used.
+ To specify that the platform default should be used, do not set the
+ secureRandomProvider attribute and set this attribute to the empty
+ string.java.security.SecureRandom to use to generate SSO session
+ IDs. If not specified, the default value is
+ java.security.SecureRandom.java.security.SecureRandom instances that generate SSO
+ session IDs. If an invalid algorithm and/or provider is specified, the
+ platform default provider and the default algorithm will be used. If not
+ specified, the platform default provider will be used.$CATALINA_BASE. If not specified, the default value of
+ conf/tomcat.keytab is used.HTTP/<hostname> where
+ <hostname> is obtained using
+ InetAddress.getLocalHost().getCanonicalHostName().
This is a work in progress. This warning should be removed once the -end-to-end testing is complete
+various questions and TODOs (see the Javadoc and implementation class) have been +resolved.There are four components to the configuration of the built-in Tomcat support for Windows authentication. The domain controller, the server hosting Tomcat, the web application wishing to use Windows authentication and the client @@ -60,8 +61,8 @@ machine. The following sections describe the configuration required for each component.
The names of the three machines used in the configuration examples below are win-dc01.dev.local (the domain controller), win-tc01.dev.local (the Tomcat -instance) and win-pc01.dev.local (client). The Tomcat server and the client are -both members of the domain.
+instance) and win-pc01.dev.local (client). All are members of the DEV.LOCAL +domain.Note: In order to use the passwords in the steps below, the domain password policy had to be relaxed. This is not recommended for production environments.
@@ -86,9 +87,9 @@ policy had to be relaxed. This is not recommended for production environments. itself to the domain controller. This file contains the Tomcat private key for the service provider account and should be protected accordingly. To generate the file, run the following command (all on a single line): -test with a password of testpass.TBD
+These steps assume that Tomcat and a Java 6 JDK/JRE have already been + installed and configured and that Tomcat is running as the tc01@DEV.LOCAL + user. The steps to configure the Tomcat instance for Windows authentication + are as follows: +
tomcat.keytab file created on the domain controller
+ to $CATALINA_BASE/conf.C:\Windows\krb5.ini. The file used in this how-to
+ contained:The above steps have been tested on a Tomcat server running Windows Server + 2008 R2 64-bit Standard with an Oracle 1.6.0_24 64-bit JDK.
TBD
+The web application needs to be configured to the use Tomcat specific
+ authentication method of SPNEGO (rather than BASIC etc.) in
+ web.xml. As with the other authenticators, behaviour can be customised by
+ explicitly configuring the
+ authentication valve and setting attributes on the Valve.
TBD
+The client must be configured to use Kerberos authentication. For Internet + Explorer this means making sure that the Tomcat instance is in the "Local + intranet" security domain and that it is configured (Tools > Internet + Options > Advanced) with integrated Windows authentication enabled. Note that + this will not work if you use the same machine for the client + and the Tomcat instance as Internet Explorer will use the unsupported NTLM + protocol.
+Correctly configuring Kerberos authentication can be tricky. The following + references may prove helpful. Advice is also always available from the + Tomcat users + mailing list. +