Author: markt
Date: Thu Mar 31 18:02:10 2011
New Revision: 1087392
URL: http://svn.apache.org/viewvc?rev=1087392&view=rev
Log:
Switch SPNEGO authenticator to use file based JAAS config as this provides
greater flexibility including making it easier to work with non-Oracle JVMs.
Clean up the code, add debug logging and improve error handling.
Modified:
tomcat/trunk/java/org/apache/catalina/authenticator/Constants.java
tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties
tomcat/trunk/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
tomcat/trunk/webapps/docs/config/valve.xml
tomcat/trunk/webapps/docs/windows-auth-howto.xml
Modified: tomcat/trunk/java/org/apache/catalina/authenticator/Constants.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/Constants.java?rev=1087392&r1=1087391&r2=1087392&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/authenticator/Constants.java
(original)
+++ tomcat/trunk/java/org/apache/catalina/authenticator/Constants.java Thu Mar
31 18:02:10 2011
@@ -40,6 +40,15 @@ public class Constants {
// SPNEGO authentication constants
public static final String DEFAULT_KEYTAB = "conf/tomcat.keytab";
public static final String DEFAULT_SPN_CLASS = "HTTP";
+ public static final String KRB5_CONF_PROPERTY = "java.security.krb5.conf";
+ public static final String DEFAULT_KRB5_CONF = "conf/krb5.ini";
+ public static final String JAAS_CONF_PROPERTY =
+ "java.security.auth.login.config";
+ public static final String DEFAULT_JAAS_CONF = "conf/jaas.conf";
+ public static final String DEFAULT_LOGIN_MODULE_NAME =
+ "com.sun.security.jgss.krb5.accept";
+ public static final String USE_SUBJECT_CREDS_ONLY_PROPERTY =
+ "javax.security.auth.useSubjectCredsOnly";
// Cookie name for single sign on support
public static final String SINGLE_SIGN_ON_COOKIE =
Modified:
tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties?rev=1087392&r1=1087391&r2=1087392&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties
(original)
+++ tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties
Thu Mar 31 18:02:10 2011
@@ -20,6 +20,7 @@ authenticator.invalid=Invalid client cer
authenticator.loginFail=Login failed
authenticator.keystore=Exception loading key store
authenticator.manager=Exception initializing trust managers
+authenticator.noAuthHeader=No authorization header sent by client
authenticator.notAuthenticated=Configuration error: Cannot perform access
control without an authenticated principal
authenticator.notContext=Configuration error: Must be attached to a Context
authenticator.requestBodyTooBig=The request body was too large to be cached
during the authentication process
@@ -30,6 +31,8 @@ authenticator.userDataConstraint=This re
formAuthenticator.forwardErrorFail=Unexpected error forwarding to error page
formAuthenticator.forwardLoginFail=Unexpected error forwarding to login page
+spnegoAuthenticator.authHeaderNoToken=The Negotiate authorization header sent
by the client did include a token
+spnegoAuthenticator.authHeaderNotNego=The authorization header sent by the
client did not start with Negotiate
spnegoAuthenticator.hostnameFail=Unable to determine the host name to
construct the default SPN. Please set the spn attribute of the authenticator.
spnegoAuthenticator.serviceLoginFail=Unable to login as the service principal
spnegoAuthenticator.ticketValidateFail=Failed to validate client supplied
ticket
\ No newline at end of file
Modified:
tomcat/trunk/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java?rev=1087392&r1=1087391&r2=1087392&view=diff
==============================================================================
---
tomcat/trunk/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
(original)
+++
tomcat/trunk/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
Thu Mar 31 18:02:10 2011
@@ -18,22 +18,12 @@ package org.apache.catalina.authenticato
import java.io.File;
import java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.security.Principal;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.AppConfigurationEntry;
-import javax.security.auth.login.Configuration;
+
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletResponse;
-import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.deploy.LoginConfig;
@@ -47,6 +37,7 @@ import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.Oid;
/**
@@ -69,8 +60,6 @@ import org.ietf.jgss.GSSManager;
* <li>Does the SPN have to start with HTTP/...?</li>
* <li>Can a port number be appended to the end of the host in the SPN?</li>
* <li>Can the domain be left off the user in the ktpass command?</li>
- * <li>Can -Djava.security.krb5.conf be used to change the location of
krb5.ini?
- * </li>
* <li>What are the limitations on the account that Tomcat can run as? SPN
* associated account works, domain admin works, local admin doesn't
* work</li>
@@ -80,12 +69,15 @@ public class SpnegoAuthenticator extends
private static final Log log =
LogFactory.getLog(SpnegoAuthenticator.class);
- protected String serviceKeyTab = Constants.DEFAULT_KEYTAB;
- protected String spn = null;
+ private String loginConfigName = Constants.DEFAULT_LOGIN_MODULE_NAME;
+ public String getLoginConfigName() {
+ return loginConfigName;
+ }
+ public void setLoginConfigName(String loginConfigName) {
+ this.loginConfigName = loginConfigName;
+ }
- protected Subject serviceSubject = null;
-
@Override
protected String getAuthMethod() {
return Constants.SPNEGO_METHOD;
@@ -98,65 +90,32 @@ public class SpnegoAuthenticator extends
}
- 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) {
- throw new LifecycleException(
- sm.getString("spnegoAuthenticator.hostnameFail"), 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) {
- throw new LifecycleException(
- sm.getString("spnegoAuthenticator.serviceLoginFail"), e);
+ // Kerberos configuration file location
+ String krb5Conf = System.getProperty(Constants.KRB5_CONF_PROPERTY);
+ if (krb5Conf == null) {
+ // System property not set, use the Tomcat default
+ File krb5ConfFile = new File(Bootstrap.getCatalinaBase(),
+ Constants.DEFAULT_KRB5_CONF);
+ System.setProperty(Constants.KRB5_CONF_PROPERTY,
+ krb5ConfFile.getAbsolutePath());
+ }
+
+ // JAAS configuration file location
+ String jaasConf = System.getProperty(Constants.JAAS_CONF_PROPERTY);
+ if (jaasConf == null) {
+ // System property not set, use the Tomcat default
+ File jaasConfFile = new File(Bootstrap.getCatalinaBase(),
+ Constants.DEFAULT_JAAS_CONF);
+ System.setProperty(Constants.JAAS_CONF_PROPERTY,
+ jaasConfFile.getAbsolutePath());
}
+
+ // This property must be false for SPNEGO to work
+ System.setProperty(Constants.USE_SUBJECT_CREDS_ONLY_PROPERTY, "false");
}
@@ -195,122 +154,118 @@ public class SpnegoAuthenticator extends
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, context));
- } catch (PrivilegedActionException e) {
- if (log.isDebugEnabled()) {
- log.debug(sm.getString(
- "spnegoAuthenticator.ticketValidateFail"));
- }
- }
-
- if (principal != null) {
- register(request, response, principal,
Constants.SPNEGO_METHOD,
- principal.getName(), null);
- return true;
- }
- } else {
- response.setHeader("WWW-Authenticate", "Negotiate");
+ if (authorization == null) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("authenticator.noAuthHeader"));
}
- } else {
response.setHeader("WWW-Authenticate", "Negotiate");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return false;
}
- response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
- return false;
- }
+
+ authorization.toBytes();
+ ByteChunk authorizationBC = authorization.getByteChunk();
+ if (!authorizationBC.startsWithIgnoreCase("negotiate ", 0)) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString(
+ "spnegoAuthenticator.authHeaderNotNego"));
+ }
+ response.setHeader("WWW-Authenticate", "Negotiate");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return false;
+ }
- private static class KerberosAuthAction
- implements PrivilegedExceptionAction<Principal> {
+ authorizationBC.setOffset(authorizationBC.getOffset() + 10);
+ // FIXME: Add trimming
+ // authorizationBC.trim();
+
+ ByteChunk decoded = new ByteChunk();
+ Base64.decode(authorizationBC, decoded);
- private byte[] inToken;
- private HttpServletResponse resp;
- private Context context;
-
- public KerberosAuthAction(byte[] inToken, HttpServletResponse resp,
- Context context) {
- this.inToken = inToken;
- this.resp = resp;
- this.context = context;
+ if (decoded.getLength() == 0) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString(
+ "spnegoAuthenticator.authHeaderNoToken"));
+ }
+ response.setHeader("WWW-Authenticate", "Negotiate");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return false;
}
- @Override
- public Principal run() throws Exception {
-
+ LoginContext lc = null;
+ GSSContext gssContext = null;
+ byte[] outToken = null;
+ try {
+ try {
+ lc = new LoginContext(loginConfigName);
+ lc.login();
+ } catch (LoginException e) {
+ log.error(sm.getString("spnegoAuthenticator.serviceLoginFail"),
+ e);
+ response.sendError(
+ HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return false;
+ }
// Assume the GSSContext is stateless
// TODO: Confirm this assumption
- GSSContext gssContext =
- GSSManager.getInstance().createContext((GSSCredential) null);
-
- Principal principal = null;
+ GSSManager manager = GSSManager.getInstance();
+ gssContext = manager.createContext(manager.createCredential(null,
+ GSSCredential.DEFAULT_LIFETIME,
+ new Oid("1.3.6.1.5.5.2"),
+ GSSCredential.ACCEPT_ONLY));
- if (inToken == null) {
- throw new IllegalArgumentException("inToken cannot be null");
- }
-
- byte[] outToken =
- gssContext.acceptSecContext(inToken, 0, inToken.length);
+ outToken = gssContext.acceptSecContext(decoded.getBytes(),
+ decoded.getOffset(), decoded.getLength());
if (outToken == null) {
- throw new GSSException(GSSException.DEFECTIVE_TOKEN);
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString(
+ "spnegoAuthenticator.ticketValidateFail"));
+ }
+ // Start again
+ response.setHeader("WWW-Authenticate", "Negotiate");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return false;
}
principal = context.getRealm().authenticate(gssContext);
-
- // Send response token on success and failure
- resp.setHeader("WWW-Authenticate", "Negotiate "
- + Base64.encode(outToken));
-
- gssContext.dispose();
- return principal;
+ } catch (GSSException e) {
+ if (log.isDebugEnabled()) {
+
log.debug(sm.getString("spnegoAuthenticator.ticketValidateFail",
+ e));
+ }
+ response.setHeader("WWW-Authenticate", "Negotiate");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return false;
+ } finally {
+ if (gssContext != null) {
+ try {
+ gssContext.dispose();
+ } catch (GSSException e) {
+ // Ignore
+ }
+ }
+ if (lc != null) {
+ try {
+ lc.logout();
+ } catch (LoginException e) {
+ // Ignore
+ }
+ }
}
- }
+ // Send response token on success and failure
+ response.setHeader("WWW-Authenticate", "Negotiate "
+ + Base64.encode(outToken));
- /**
- * Provides the JAAS login configuration required to create
- */
- private static class JaasConfig extends Configuration {
-
- private String keytab;
- private String spn;
- private boolean debug;
-
- public JaasConfig(String keytab, String spn, boolean debug) {
- this.keytab = keytab;
- this.spn = spn;
- this.debug = debug;
+ if (principal != null) {
+ register(request, response, principal, Constants.SPNEGO_METHOD,
+ principal.getName(), null);
+ return true;
}
- @Override
- public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
- Map<String, String> options = new HashMap<String, String>();
- options.put("useKeyTab", "true");
- options.put("keyTab", keytab);
- options.put("principal", spn);
- options.put("storeKey", "true");
- options.put("doNotPrompt", "true");
- options.put("isInitiator", "false");
- options.put("debug", Boolean.toString(debug));
-
- return new AppConfigurationEntry[] {
- new AppConfigurationEntry(
- "com.sun.security.auth.module.Krb5LoginModule",
- AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
- options) };
- }
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return false;
}
}
Modified: tomcat/trunk/webapps/docs/config/valve.xml
URL:
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/valve.xml?rev=1087392&r1=1087391&r2=1087392&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/config/valve.xml (original)
+++ tomcat/trunk/webapps/docs/config/valve.xml Thu Mar 31 18:02:10 2011
@@ -849,6 +849,12 @@
<code>true</code> will be used.</p>
</attribute>
+ <attribute name="loginConfigName" required="false">
+ <p>The name of the JAAS login configuration to be used to login as the
+ service. If not specified, the default of
+ <code>com.sun.security.jgss.krb5.accept</code> is used.</p>
+ </attribute>
+
<attribute name="securePagesWithPragma" required="false">
<p>Controls the caching of pages that are protected by security
constraints. Setting this to <code>false</code> may help work around
@@ -885,22 +891,6 @@
specified, the platform default provider will be used.</p>
</attribute>
- <attribute name="serviceKeyTab" required="false">
- <p>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
- <code>$CATALINA_BASE</code>. If not specified, the default value of
- <code>conf/tomcat.keytab</code> is used.</p>
- </attribute>
-
- <attribute name="spn" required="false">
- <p>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 <code>HTTP/<hostname></code> where
- <code><hostname></code> is obtained using
- <code>InetAddress.getLocalHost().getCanonicalHostName()</code>.</p>
- </attribute>
-
</attributes>
</subsection>
Modified: tomcat/trunk/webapps/docs/windows-auth-howto.xml
URL:
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/windows-auth-howto.xml?rev=1087392&r1=1087391&r2=1087392&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/windows-auth-howto.xml (original)
+++ tomcat/trunk/webapps/docs/windows-auth-howto.xml Thu Mar 31 18:02:10 2011
@@ -107,9 +107,9 @@ policy had to be relaxed. This is not re
user. The steps to configure the Tomcat instance for Windows authentication
are as follows:
<li>Copy the <code>tomcat.keytab</code> file created on the domain controller
- to <code>$CATALINA_BASE/conf</code>.</li>
+ to <code>$CATALINA_BASE/conf/tomcat.keytab</code>.</li>
<li>Create the kerberos configuration file
- <code>C:\Windows\krb5.ini</code>. The file used in this how-to
+ <code>$CATALINA_BASE/conf/krb5.ini</code>. The file used in this how-to
contained:<source>[libdefaults]
default_realm = DEV.LOCAL
default_keytab_name = FILE:c:\apache-tomcat-7.0.x\conf\tomcat.keytab
@@ -124,7 +124,37 @@ DEV.LOCAL = {
[domain_realm]
dev.local= DEV.LOCAL
-.dev.local= DEV.LOCAL</source></li>
+.dev.local= DEV.LOCAL</source>
+ The location of this file can be changed by setting the
+ <code>java.security.krb5.conf</code> systm property.</li>
+ <li>Create the JAAS login configuration file
+ <code>$CATALINA_BASE/conf/jaas.conf</code>. The file used in this how-to
+ contained:<source>com.sun.security.jgss.krb5.initiate {
+ com.sun.security.auth.module.Krb5LoginModule required
+ doNotPrompt=true
+ principal="HTTP/[email protected]"
+ useKeyTab=true
+ keyTab="c:/apache-tomcat-7.0.x/conf/tomcat.keytab"
+ storeKey=true;
+};
+
+com.sun.security.jgss.krb5.accept {
+ com.sun.security.auth.module.Krb5LoginModule required
+ doNotPrompt=true
+ principal="HTTP/[email protected]"
+ useKeyTab=true
+ keyTab="c:/apache-tomcat-7.0.x/conf/tomcat.keytab"
+ storeKey=true;
+};</source>
+ The location of this file can be changed by setting the
+ <code>java.security.auth.login.config</code> system property. The LoginModule
+ used is a JVM specific one so ensure that the LoginModule specified matches
+ the JVM being used. The name of the login configuration must match the
+ value used by the <a href="config/valve.html#SPNEGO_Valve">authentication
+ valve</a>.</li>
+ <li>The system property <code>javax.security.auth.useSubjectCredsOnly</code>
+ is automatically set to the required value of false if a web application is
+ configured to use the SPNEGO authentication method.</li>
</p>
<p>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.</p>
@@ -156,8 +186,10 @@ dev.local= DEV.LOCAL
<ol>
<li><a
href="http://www.adopenstatic.com/cs/blogs/ken/archive/2006/10/19/512.aspx">
IIS and Kerberos</a></li>
- <li><a
href="http://blog.springsource.com/2009/09/28/spring-security-kerberos/">
- Spring Security Kerberos extension</a></li>
+ <li><a href="http://spnego.sourceforge.net/index.html">
+ SPNEGO project at SourceForge</a></li>
+ <li><a
href="http://download.oracle.com/javase/1.5.0/docs/guide/security/jgss/tutorials/index.html">
+ Oracle JGSS tutorial</a></li>
<li><a
href="https://cwiki.apache.org/GMOxDOC21/using-spengo-in-geronimo.html#UsingSpengoingeronimo-SettinguptheActiveDirectoryDomainController">
Geronimo configuration for Windows authentication</a></li>
<li><a
href="http://blogs.msdn.com/b/openspecification/archive/2010/11/17/encryption-type-selection-in-kerberos-exchanges.aspx">
@@ -194,6 +226,17 @@ dev.local= DEV.LOCAL
</ul>
</p>
</subsection>
+
+ <subsection name="SPNEGO project at SourceForge">
+ <p>Full details of this solution can be found through the
+ <a href="http://spnego.sourceforge.net/index.html/">project site</a>. The key
+ features are:
+ <ul>
+ <li>Uses Kerberos</li>
+ <li>Pure Java solution</li>
+ </ul>
+ </p>
+ </subsection>
</section>
<section name="Reverse proxies">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]