* multiple components. If the configuration is invalid, the error messages are
* often cryptic although a Google search will usually point you in the right
* direction.
- * <p>
- * TODO:
- * <ul>
- * <li>Add support for delegating credentials? Need this if we want to
- * authenticate to a realm as the user. This is likely to result in a fair
- * amount of internal refactoring.</li>
- * </ul>
- * <p>
- * TBDs:
- * <ul>
- * <li>Does the domain name have to be in upper case?</li>
- * <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>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>
- * </ul>
*/
public class SpnegoAuthenticator extends AuthenticatorBase {
import org.apache.catalina.util.Base64;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.CharChunk;
+import org.ietf.jgss.GSSCredential;
/**
* <p>Implementation of <strong>Realm</strong> that works with a directory
*/
protected int timeLimit = 0;
+
+ /**
+ * Should delegated credentials from the SPNEGO authenticator be used if
+ * available
+ */
+ protected boolean useDelegatedCredential = true;
+
+
// ------------------------------------------------------------- Properties
/**
}
+
+ public boolean isUseDelegatedCredential() {
+ return useDelegatedCredential;
+ }
+
+ public void setUseDelegatedCredential(boolean useDelegatedCredential) {
+ this.useDelegatedCredential = useDelegatedCredential;
+ }
+
/**
* Return descriptive information about this Realm implementation and
* the corresponding version number, in the format
*/
@Override
protected Principal getPrincipal(String username) {
+ return getPrincipal(username, null);
+ }
+
+ @Override
+ protected Principal getPrincipal(String username,
+ GSSCredential gssCredential) {
DirContext context = null;
Principal principal = null;
try {
// Authenticate the specified username if possible
- principal = getPrincipal(context, username);
+ principal = getPrincipal(context, username, gssCredential);
} catch (CommunicationException e) {
context = open();
// Try the authentication again.
- principal = getPrincipal(context, username);
+ principal = getPrincipal(context, username, gssCredential);
} catch (ServiceUnavailableException e) {
context = open();
// Try the authentication again.
- principal = getPrincipal(context, username);
+ principal = getPrincipal(context, username, gssCredential);
}
* Return the Principal associated with the given user name.
*/
protected synchronized Principal getPrincipal(DirContext context,
- String username)
+ String username, GSSCredential gssCredential)
throws NamingException {
- User user = getUser(context, username);
+ User user = null;
+ List<String> roles = null;
+
+ try {
+ if (gssCredential != null && isUseDelegatedCredential()) {
+ // Set up context
+ context.addToEnvironment(
+ Context.SECURITY_AUTHENTICATION, "GSSAPI");
+ context.addToEnvironment(
+ "javax.security.sasl.server.authentication", "true");
+ context.addToEnvironment(
+ "javax.security.sasl.qop", "auth-conf");
+ // Note: Subject already set in SPNEGO authenticator so no need
+ // for Subject.doAs() here
+ }
+ user = getUser(context, username);
+ if (user != null) {
+ roles = getRoles(context, user);
+ }
+ } finally {
+ try {
+ context.removeFromEnvironment(
+ Context.SECURITY_AUTHENTICATION);
+ } catch (NamingException e) {
+ // Ignore
+ }
+ try {
+ context.removeFromEnvironment(
+ "javax.security.sasl.server.authentication");
+ } catch (NamingException e) {
+ // Ignore
+ }
+ try {
+ context.removeFromEnvironment(
+ "javax.security.sasl.qop");
+ } catch (NamingException e) {
+ // Ignore
+ }
+ }
if (user != null) {
return new GenericPrincipal(user.getUserName(), user.getPassword(),
- getRoles(context, user));
+ roles, null, null, gssCredential);
}
return null;
}
- // ------------------------------------------------------ Private Classes
-
- /**
- * A protected class representing a User
- */
- protected static class User {
+ // ------------------------------------------------------ Private Classes
+
+ /**
+ * A protected class representing a User
+ */
+ protected static class User {
- private final String username;
- private final String dn;
- private final String password;
- private final List<String> roles;
-
- public User(String username, String dn, String password,
- List<String> roles) {
- this.username = username;
- this.dn = dn;
- this.password = password;
- if (roles == null) {
- this.roles = Collections.emptyList();
- } else {
- this.roles = Collections.unmodifiableList(roles);
- }
- }
+ private final String username;
+ private final String dn;
+ private final String password;
+ private final List<String> roles;
+
+ public User(String username, String dn, String password,
+ List<String> roles) {
+ this.username = username;
+ this.dn = dn;
+ this.password = password;
+ if (roles == null) {
+ this.roles = Collections.emptyList();
+ } else {
+ this.roles = Collections.unmodifiableList(roles);
+ }
+ }
- public String getUserName() {
- return username;
- }
-
- public String getDN() {
- return dn;
- }
+ public String getUserName() {
+ return username;
+ }
- public String getPassword() {
- return password;
- }
+ public String getDN() {
+ return dn;
+ }
+
+ public String getPassword() {
+ return password;
+ }
- public List<String> getRoles() {
- return roles;
- }
- }
+ public List<String> getRoles() {
+ return roles;
+ }
+ }
}
protected AllRolesMode allRolesMode = AllRolesMode.STRICT_MODE;
+ /**
+ * When processing users authenticated via the GSS-API, should any
+ * "@..." be stripped from the end of the user name?
+ */
+ protected boolean stripAtForGss = true;
+
+
// ------------------------------------------------------------- Properties
}
+ public boolean isStripAtForGss() {
+ return stripAtForGss;
+ }
+
+
+ public void setStripAtForGss(boolean stripAtForGss) {
+ this.stripAtForGss = stripAtForGss;
+ }
+
+
// --------------------------------------------------------- Public Methods
@Override
public Principal authenticate(GSSContext gssContext, boolean storeCred) {
if (gssContext.isEstablished()) {
- GSSName name = null;
+ GSSName gssName = null;
try {
- name = gssContext.getSrcName();
+ gssName = gssContext.getSrcName();
} catch (GSSException e) {
log.warn(sm.getString("realmBase.gssNameFail"), e);
}
- if (name!= null) {
+ if (gssName!= null) {
+ String name = gssName.toString();
+
+ if (isStripAtForGss()) {
+ int i = name.indexOf('@');
+ if (i > 0) {
+ // Zero so we don;t leave a zero length name
+ name = name.substring(0, i);
+ }
+ }
GSSCredential gssCredential = null;
if (storeCred && gssContext.getCredDelegState()) {
try {
}
}
}
- return getPrincipal(name.toString(), gssCredential);
+ return getPrincipal(name, gssCredential);
}
}
Don't register Contexts that fail to start with the Mapper. (markt)
</fix>
<add>
- Add initial support for SPNEGO/Kerberos authentication also referred to
- as integrated Windows authentication. This is a work in progress. See
- the documentation for details. (markt)
+ <bug>48685</bug>: Add initial support for SPNEGO/Kerberos authentication
+ also referred to as integrated Windows authentication. This includes
+ user authentication, authorisation via the directory using the
+ user's delegated credentials and exposing the user's delegated
+ credentials via a request attribute so applications can make use of the
+ to impersonate the current user when accessing third-party systems that
+ use a compatible authentication mechanism. (markt)
</add>
<fix>
HTTP range requests cannot be reliably served when a Writer is in use so
a role name assigned to the corresponding user.</p>
</attribute>
+ <attribute name="stripAtForGss" required="false">
+ <p>When processing users authenticated via the GSS-API, this attribute
+ controls if any "@..." is removed from the end of the user
+ name. If not specified, the default is <code>true</code>.</p>
+ </attribute>
+
<attribute name="userCredCol" required="true">
<p>Name of the column, in the "users" table, which contains
the user's credentials (i.e. password(. If a value for the
a role name assigned to the corresponding user.</p>
</attribute>
+ <attribute name="stripAtForGss" required="false">
+ <p>When processing users authenticated via the GSS-API, this attribute
+ controls if any "@..." is removed from the end of the user
+ name. If not specified, the default is <code>true</code>.</p>
+ </attribute>
+
<attribute name="userCredCol" required="true">
<p>Name of the column, in the "users" table, which contains
the user's credentials (i.e. password(. If a value for the
user currently being authenticated? If false,
<code>connectionName</code>} and <code>connectionPassword</code> will be
used if specified, else an anonymous. If not specified, the default
- value of <code>false</code> is used.</p>
+ value of <code>false</code> is used. Note that when accessing the
+ directory using delegated credentials, this attribute is always ignored
+ and the search is performed using the delegated credentials.</p>
</attribute>
<attribute name="roleSubtree" required="false">
<code>0</code> is used which indicates no limit.</p>
</attribute>
+ <attribute name="stripAtForGss" required="false">
+ <p>When processing users authenticated via the GSS-API, this attribute
+ controls if any "@..." is removed from the end of the user
+ name. If not specified, the default is <code>true</code>.</p>
+ </attribute>
+
<attribute name="timeLimit" required="false">
<p>Specifies the time (in milliseconds) to wait for records to be
returned when using the <code>userSearch</code> attribute. If not
limit.</p>
</attribute>
+ <attribute name="useDelegatedCredential" required="false">
+ <p>When the JNIRealm is used with the SPNEGO authenticator, delegated
+ credentials for the user may be available. If such credentials are
+ present, this attribute controls whether are not they are used to
+ connect to the directory. If not specified, the default value of
+ <code>true</code> is used.</p>
+ </attribute>
+
<attribute name="userBase" required="false">
<p>The base element for user searches performed using the
<code>userSearch</code> expression. Not used if you are using
actual username should be inserted. You can use this property
instead of <code>userSearch</code>, <code>userSubtree</code>
and <code>userBase</code> when the distinguished name contains
- the username and is otherwise the same for all users.</p>
+ the username and is otherwise the same for all users. Note that
+ when accessing the directory using delegated credentials, this
+ attribute is always ignored and <code>userSearch</code>,
+ <code>userSubtree</code> and <code>userBase</code> are always
+ used instead.</p>
</attribute>
<attribute name="userRoleName" required="false">
default value is <code>conf/tomcat-users.xml</code>.</p>
</attribute>
+ <attribute name="stripAtForGss" required="false">
+ <p>When processing users authenticated via the GSS-API, this attribute
+ controls if any "@..." is removed from the end of the user
+ name. If not specified, the default is <code>true</code>.</p>
+ </attribute>
+
</attributes>
<p>The XML document referenced by the <code>pathname</code> attribute must
for your role <code>Principals</code>.</p>
</attribute>
+ <attribute name="stripAtForGss" required="false">
+ <p>When processing users authenticated via the GSS-API, this attribute
+ controls if any "@..." is removed from the end of the user
+ name. If not specified, the default is <code>true</code>.</p>
+ </attribute>
+
<attribute name="useContextClassLoader" required="false">
<p>Instructs JAASRealm to use the context class loader for loading the
user-specified <code>LoginModule</code> class and associated
</section>
<section name="Built-in Tomcat support">
-<p><strong>This is a work in progress. This warning should be removed once the
-various questions and TODOs (see the Javadoc and implementation class) have been
-resolved.</strong> In particular, onwards delegation is not yet supported and
-roles are not retrieved from the domain controller.</p>
+<p><strong>This is a work in progress. There are a number of outstanding
+questions that require further testing.</strong> These include:
+<ul>
+<li>Does the domain name have to be in upper case?</li>
+<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>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>
+</ul>
+</p>
<p>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
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 SPNEGO authenticator will work with any <a href="config/realm.html">
+ Realm</a> but if used with the JNDI Realm, by default the JNDI Realm will use
+ the user's delegated credentials to connect to the Active Directory.
+ </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>
</subsection>