Part 2 of SPNEGO/Windows authentication support.
authormarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Tue, 29 Mar 2011 20:05:04 +0000 (20:05 +0000)
committermarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Tue, 29 Mar 2011 20:05:04 +0000 (20:05 +0000)
Authorisation support. Works essentially the same way as CLIENT-CERT, i.e. the Realm doesn't actually authenticate the user but it does create the Principal and add the roles.

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1086706 13f79535-47bb-0310-9956-ffa450edef68

java/org/apache/catalina/Realm.java
java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
java/org/apache/catalina/realm/CombinedRealm.java
java/org/apache/catalina/realm/LocalStrings.properties
java/org/apache/catalina/realm/LockOutRealm.java
java/org/apache/catalina/realm/RealmBase.java

index f66a91d..b9e85c1 100644 (file)
@@ -26,6 +26,7 @@ import java.security.cert.X509Certificate;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
 import org.apache.catalina.deploy.SecurityConstraint;
+import org.ietf.jgss.GSSContext;
 /**
  * A <b>Realm</b> is a read-only facade for an underlying security realm
  * used to authenticate individual users, and identify the security roles
@@ -110,7 +111,16 @@ public interface Realm {
      * Return the Principal associated with the specified chain of X509
      * client certificates.  If there is none, return <code>null</code>.
      *
-     * @param certs Array of client certificates, with the first one in
+     * @param certs The gssContext processed by the {@link Authenticator}.
+     */
+    public Principal authenticate(GSSContext gssContext);
+    
+    
+    /**
+     * Return the Principal associated with the specified {@link GSSContext}.
+     * If there is none, return <code>null</code>.
+     *
+     * @param gssContext Array of client certificates, with the first one in
      *  the array being the certificate of the client itself.
      */
     public Principal authenticate(X509Certificate certs[]);
index 3719a64..6a3013a 100644 (file)
@@ -33,10 +33,10 @@ 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;
-import org.apache.catalina.realm.GenericPrincipal;
 import org.apache.catalina.startup.Bootstrap;
 import org.apache.catalina.util.Base64;
 import org.apache.juli.logging.Log;
@@ -47,7 +47,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.GSSName;
+
 
 /**
  * A SPNEGO authenticator that uses the SPENGO/Kerberos support built in to Java
@@ -208,7 +208,8 @@ public class SpnegoAuthenticator extends AuthenticatorBase {
 
                 try {
                     principal = Subject.doAs(serviceSubject,
-                            new KerberosAuthAction(decoded.getBytes(), response));
+                            new KerberosAuthAction(decoded.getBytes(),
+                                    response, context));
                 } catch (PrivilegedActionException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
@@ -235,10 +236,13 @@ public class SpnegoAuthenticator extends AuthenticatorBase {
 
         private byte[] inToken;
         private HttpServletResponse resp;
+        private Context context;
 
-        public KerberosAuthAction(byte[] inToken, HttpServletResponse resp) {
+        public KerberosAuthAction(byte[] inToken, HttpServletResponse resp,
+                Context context) {
             this.inToken = inToken;
             this.resp = resp;
+            this.context = context;
         }
 
         @Override
@@ -246,7 +250,7 @@ public class SpnegoAuthenticator extends AuthenticatorBase {
 
             // Assume the GSSContext is stateless
             // TODO: Confirm this assumption
-            GSSContext context =
+            GSSContext gssContext =
                 GSSManager.getInstance().createContext((GSSCredential) null);
 
             Principal principal = null;
@@ -256,26 +260,19 @@ public class SpnegoAuthenticator extends AuthenticatorBase {
             }
 
             byte[] outToken =
-                context.acceptSecContext(inToken, 0, inToken.length);
+                gssContext.acceptSecContext(inToken, 0, inToken.length);
 
             if (outToken == null) {
                 throw new GSSException(GSSException.DEFECTIVE_TOKEN);
             }
 
-            GSSName initiatorName = context.getSrcName();
-
-            if (context.isEstablished()) {
-                // TODO This (and a lot of the surrounding code) needs to move
-                // to RealmBase so authorisation will work. This is just a quick
-                // hack to get authentication working.
-                principal = new GenericPrincipal(initiatorName.toString(), null);
-            }
+            principal = context.getRealm().authenticate(gssContext);
 
             // Send response token on success and failure
             resp.setHeader("WWW-Authenticate", "Negotiate "
                     + Base64.encode(outToken));
 
-            context.dispose();
+            gssContext.dispose();
             return principal;
         }
     }
index c3858bc..b6cd91e 100644 (file)
@@ -31,6 +31,9 @@ import org.apache.catalina.LifecycleException;
 import org.apache.catalina.Realm;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSName;
 
 /**
  * Realm implementation that contains one or more realms. Authentication is
@@ -264,6 +267,53 @@ public class CombinedRealm extends RealmBase {
         return authenticatedUser;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Principal authenticate(GSSContext gssContext) {
+        if (gssContext.isEstablished()) {
+            Principal authenticatedUser = null;
+            String username = null;
+            
+            GSSName name = null;
+            try {
+                name = gssContext.getSrcName();
+            } catch (GSSException e) {
+                log.warn(sm.getString("realmBase.gssNameFail"), e);
+                return null;
+            }
+            
+            username = name.toString();
+
+            for (Realm realm : realms) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("combinedRealm.authStart",
+                            username, realm.getInfo()));
+                }
+
+                authenticatedUser = realm.authenticate(gssContext);
+
+                if (authenticatedUser == null) {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("combinedRealm.authFail",
+                                username, realm.getInfo()));
+                    }
+                } else {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("combinedRealm.authSucess",
+                                username, realm.getInfo()));
+                    }
+                    break;
+                }
+            }
+            return authenticatedUser;
+        }
+        
+        // Fail in all other cases
+        return null;
+    }
+    
     @Override
     protected String getName() {
         return name;
index 9173ab9..01fb5a8 100644 (file)
@@ -73,6 +73,7 @@ realmBase.notAuthenticated=Configuration error:  Cannot perform access control w
 realmBase.notStarted=This Realm has not yet been started
 realmBase.authenticateFailure=Username {0} NOT successfully authenticated
 realmBase.authenticateSuccess=Username {0} successfully authenticated
+realmBase.gssNameFail=Failed to extract name from established GSSContext
 userDatabaseRealm.authenticateError=Login configuration error authenticating username {0}
 userDatabaseRealm.lookup=Exception looking up UserDatabase under key {0}
 userDatabaseRealm.noDatabase=No UserDatabase component found under key {0}
index bd06929..7059a85 100644 (file)
@@ -26,6 +26,9 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.catalina.LifecycleException;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSName;
 
 /**
  * This class extends the CombinedRealm (hence it can wrap other Realms) to
@@ -219,6 +222,46 @@ public class LockOutRealm extends CombinedRealm {
 
 
     /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Principal authenticate(GSSContext gssContext) {
+        if (gssContext.isEstablished()) {
+            String username = null;
+            GSSName name = null;
+            try {
+                name = gssContext.getSrcName();
+            } catch (GSSException e) {
+                log.warn(sm.getString("realmBase.gssNameFail"), e);
+                return null;
+            }
+            
+            username = name.toString();
+            
+            if (isLocked(username)) {
+                // Trying to authenticate a locked user is an automatic failure
+                registerAuthFailure(username);
+                
+                log.warn(sm.getString("lockOutRealm.authLockedUser", username));
+                return null;
+            }
+
+            Principal authenticatedUser = super.authenticate(gssContext);
+            
+            if (authenticatedUser == null) {
+                registerAuthFailure(username);
+            } else {
+                registerAuthSuccess(username);
+            }
+            return authenticatedUser;
+        }
+        
+        // Fail in all other cases
+        return null;
+    }
+
+
+    /**
      * Unlock the specified username. This will remove all records of
      * authentication failures for this user.
      * 
index 7a6532a..fc5ad22 100644 (file)
@@ -54,6 +54,9 @@ import org.apache.catalina.util.MD5Encoder;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSName;
 
 /**
  * Simple implementation of <b>Realm</b> that reads an XML file to configure
@@ -418,6 +421,29 @@ public abstract class RealmBase extends LifecycleMBeanBase implements Realm {
 
     
     /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Principal authenticate(GSSContext gssContext) {
+        if (gssContext.isEstablished()) {
+            GSSName name = null;
+            try {
+                name = gssContext.getSrcName();
+            } catch (GSSException e) {
+                log.warn(sm.getString("realmBase.gssNameFail"), e);
+            }
+            
+            if (name!= null) {
+                return getPrincipal(name.toString());
+            }
+        }
+        
+        // Fail in all other cases
+        return null;
+    }
+
+    
+    /**
      * Execute a periodic task, such as reloading, etc. This method will be
      * invoked inside the classloading context of this container. Unexpected
      * throwables will be caught and logged.