Index: tomcat/src/main/org/jboss/web/tomcat/security/authenticators/CustomNonLoginAuthenticator.java
===================================================================
--- tomcat/src/main/org/jboss/web/tomcat/security/authenticators/CustomNonLoginAuthenticator.java	(revision 0)
+++ tomcat/src/main/org/jboss/web/tomcat/security/authenticators/CustomNonLoginAuthenticator.java	(revision 0)
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jboss.web.tomcat.security.authenticators;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import org.apache.catalina.authenticator.AuthenticatorBase;
+import org.apache.catalina.authenticator.Constants;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.LoginConfig;
+import org.jboss.logging.Logger;
+
+/**
+ * An <b>Authenticator</b> and <b>Valve</b> implementation that checks
+ * only security constraints not involving user authentication.
+ *
+ * @author Craig R. McClanahan
+ * @version $Revision$ $Date$
+ */
+
+public class CustomNonLoginAuthenticator extends AuthenticatorBase {
+
+    private static Logger log = Logger.getLogger(CustomNonLoginAuthenticator.class);
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.authenticator.CustomNonLoginAuthenticator/1.0";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Authenticate the user making this request, based on the specified
+     * login configuration.  Return <code>true</code> if any specified
+     * constraint has been satisfied, or <code>false</code> if we have
+     * created a response challenge already.
+     *
+     * @param request Request we are processing
+     * @param response Response we are creating
+     * @param config    Login configuration describing how authentication
+     *              should be performed
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public boolean authenticate(Request request,
+                                Response 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) {
+            System.out.println("Already authenticated");
+            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) {
+            log.debug("SSO Id " + ssoId + " set; attempting reauthentication");
+
+            System.out.println("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;
+        }
+
+        return (true);
+    }
+}
\ No newline at end of file
Index: testsuite/imports/sections/web.xml
===================================================================
--- testsuite/imports/sections/web.xml	(revision 114411)
+++ testsuite/imports/sections/web.xml	(working copy)
@@ -440,6 +440,48 @@
             <include name="**/*.jsp"/>
          </fileset>
       </war>
+
+      <!-- JBAS-4077: Programmatic Web Login -->
+      <war destfile="${build.lib}/programmaticweblogin.war"
+         webxml="${build.resources}/web/programmatic/WEB-INF/jbosstest-web.xml">
+         <webinf dir="${build.resources}/web/programmatic/WEB-INF">
+            <include name="jboss-web.xml"/>
+         </webinf>
+         <classes dir="${build.classes}">
+            <include name="org/jboss/test/web/servlets/Programm*Servlet.class"/>
+         </classes>
+        <fileset dir="${build.resources}/web/programmatic">
+            <include name="restricted/*.html"/>
+            <include name="*.html"/>
+         </fileset>
+      </war>
+      <war destfile="${build.lib}/programmaticweblogin2.war"
+         webxml="${build.resources}/web/programmatic/WEB-INF/jbosstest-webauth.xml">
+         <webinf dir="${build.resources}/web/programmatic/WEB-INF">
+            <include name="jboss-web.xml"/>
+            <include name="context.xml"/>
+         </webinf>
+         <classes dir="${build.classes}">
+            <include name="org/jboss/test/web/servlets/Programm*Servlet.class"/>
+         </classes>
+        <fileset dir="${build.resources}/web/programmatic">
+            <include name="restricted/*.html"/>
+            <include name="*.html"/>
+         </fileset>
+      </war>
+      <zip destfile="${build.lib}/programmaticweblogin.ear">
+         <zipfileset dir="${build.resources}/web/programmatic" prefix="META-INF">
+            <include name="application.xml"/>
+         </zipfileset>
+         <zipfileset dir="${build.resources}/web">
+            <include name="users.properties"/>
+            <include name="roles.properties"/>
+         </zipfileset>
+         <zipfileset dir="${build.lib}" includes="programmaticweblogin.war"/>
+         <zipfileset dir="${build.lib}" includes="sso-form-auth.war"/>
+         <zipfileset dir="${build.lib}" includes="jbosstest-web-ejbs.jar"/>
+      </zip>
+
       <ear earfile="${build.lib}/web-sso.ear"
       	   appxml="${build.resources}/web/sso/application.xml">
          <zipfileset dir="${build.resources}/web">
@@ -460,6 +502,10 @@
             <include name="users.properties"/>
             <include name="roles.properties"/>
          </zipfileset>
+         <zipfileset dir="${build.lib}" includes="programmaticweblogin2.war"
+            fullpath="sso-web-auth1.war"/>
+         <zipfileset dir="${build.lib}" includes="programmaticweblogin2.war"
+            fullpath="sso-web-auth2.war"/>
          <zipfileset dir="${build.lib}" includes="sso-form-auth-clustered.war"
             fullpath="sso-form-auth1.war"/>
          <zipfileset dir="${build.lib}" includes="sso-form-auth-clustered.war"
@@ -730,33 +776,6 @@
             <include name="index.html"/>
          </fileset>
       </war>
-     
-      <!-- JBAS-4077: Programmatic Web Login -->
-      <war destfile="${build.lib}/programmaticweblogin.war"
-         webxml="${build.resources}/web/programmatic/WEB-INF/jbosstest-web.xml">
-         <webinf dir="${build.resources}/web/programmatic/WEB-INF">
-            <include name="jboss-web.xml"/>
-         </webinf>
-         <classes dir="${build.classes}">
-            <include name="org/jboss/test/web/servlets/Programm*Servlet.class"/>
-         </classes>
-        <fileset dir="${build.resources}/web/programmatic">
-            <include name="restricted/*.html"/>
-            <include name="*.html"/>
-         </fileset>
-      </war>
-      <zip destfile="${build.lib}/programmaticweblogin.ear">
-         <zipfileset dir="${build.resources}/web/programmatic" prefix="META-INF">
-            <include name="application.xml"/>
-         </zipfileset>
-         <zipfileset dir="${build.resources}/web">
-            <include name="users.properties"/>
-            <include name="roles.properties"/>
-         </zipfileset>
-         <zipfileset dir="${build.lib}" includes="programmaticweblogin.war"/>
-         <zipfileset dir="${build.lib}" includes="sso-form-auth.war"/>
-         <zipfileset dir="${build.lib}" includes="jbosstest-web-ejbs.jar"/>
-      </zip>
       	
       <!-- War java2ClassLoadingCompliance=true test  -->
       <war destfile="${build.lib}/class-loading.war"
Index: testsuite/src/main/org/jboss/test/web/servlets/ProgrammaticLoginTestServlet.java
===================================================================
--- testsuite/src/main/org/jboss/test/web/servlets/ProgrammaticLoginTestServlet.java	(revision 114411)
+++ testsuite/src/main/org/jboss/test/web/servlets/ProgrammaticLoginTestServlet.java	(working copy)
@@ -27,6 +27,7 @@
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
 
 import org.jboss.web.tomcat.security.login.WebAuthentication;
 
@@ -76,6 +77,12 @@
    
    private void logout(HttpServletRequest request) throws ServletException
    {
+      //invalide session
+      HttpSession session = request.getSession();
+      if (session != null) 
+      {
+         session.invalidate();
+      }
       //Log the user out
       new WebAuthentication().logout();
       if(request.getUserPrincipal() != null || request.isUserInRole("AuthorizedUser"))
Index: testsuite/src/main/org/jboss/test/web/test/ClusteredSingleSignOnUnitTestCase.java
===================================================================
--- testsuite/src/main/org/jboss/test/web/test/ClusteredSingleSignOnUnitTestCase.java	(revision 114411)
+++ testsuite/src/main/org/jboss/test/web/test/ClusteredSingleSignOnUnitTestCase.java	(working copy)
@@ -63,6 +63,7 @@
       suite.addTest(new ClusteredSingleSignOnUnitTestCase("testNoAuthSingleSignOn"));
       suite.addTest(new ClusteredSingleSignOnUnitTestCase("testUndeployNonClusteredWebapp"));
       suite.addTest(new ClusteredSingleSignOnUnitTestCase("testUndeployClusteredWebapp"));
+      suite.addTest(new ClusteredSingleSignOnUnitTestCase("testWebAuthClusteredSingleSignOn"));
       
       return JBossClusteredTestCase.getDeploySetup(suite, "web-sso-clustered.ear");
    }
@@ -213,6 +214,23 @@
       SSOBaseCase.executeNoAuthSingleSignOnTest(serverA, serverB, getLog());
    }
    
+   /** Test single sign-on across two web apps using form based auth
+    * 
+    * @throws Exception
+    */ 
+   public void testWebAuthClusteredSingleSignOn() throws Exception
+   {
+      log.info("+++ testWebAuthSingleSignOn");
+      String[] httpURLs  = super.getHttpURLs();
+
+      String serverA = httpURLs[0];
+      String serverB = httpURLs[1];
+      log.info(System.getProperties());
+      log.info("serverA: "+serverA);
+      log.info("serverB: "+serverB);
+      SSOBaseCase.executeWebAuthSingleSignOnTest(serverA, serverB, getLog());
+   }
+   
    /** 
     * Tests that use of transactions in ClusteredSSO does not interfere 
     * with session expiration thread.  See JBAS-2212.
Index: testsuite/src/main/org/jboss/test/web/test/SSOBaseCase.java
===================================================================
--- testsuite/src/main/org/jboss/test/web/test/SSOBaseCase.java	(revision 114411)
+++ testsuite/src/main/org/jboss/test/web/test/SSOBaseCase.java	(working copy)
@@ -29,6 +29,7 @@
 import org.apache.commons.httpclient.Header;
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.HttpMethod;
 import org.apache.commons.httpclient.HttpState;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.commons.httpclient.methods.PostMethod;
@@ -147,7 +148,54 @@
       checkAccessAllowed(httpConn, warB2 + "EJBServlet");
 
    }
+   
+   protected static void executeWebAuthSingleSignOnTest(String serverA, String serverB, Logger log) throws Exception {
+       String webwarA1 = serverA + "/webwar1/";
+       String webwarB2 = serverB + "/webwar2/";
+       
+       // Start by accessing the secured index.html of war1
+       HttpClient httpConn = new HttpClient();
+       
+       checkAccessDeniedWebAuth(httpConn, webwarA1 + "restricted/restricted.html");
 
+       HttpState state = httpConn.getState();
+       
+       String sessionID = getSessionIdFromState(state);
+       log.debug("Saw JSESSIONID="+sessionID);
+
+       // Submit the web login
+       executeWebAuthLogin(httpConn, webwarA1);
+
+       String ssoID = processSSOCookie(state, serverA, serverB);
+       log.debug("Saw JSESSIONIDSSO="+ssoID);
+
+       // Pause a moment before switching wars to better simulate real life
+       // use cases.  Otherwise, the test case can "outrun" the async
+       // replication in the TreeCache used by the clustered SSO
+       // 500 ms is a long time, but this isn't a test of replication speed
+       // and we don't want spurious failures.
+       if (!serverA.equals(serverB))
+          Thread.sleep(500);
+
+       // Now try getting the war2 index using the JSESSIONIDSSO cookie 
+       log.debug("Prepare /webwar2/restricted/restricted.html get");
+       checkAccessAllowed(httpConn, webwarB2 + "restricted/restricted.html");
+
+       // Now try logging out of war2 
+       executeWebAuthLogout(httpConn, webwarB2+"TestServlet?operation=logout");
+       
+       // Again, pause before switching wars
+       if (!serverA.equals(serverB))
+          Thread.sleep(500);
+       
+       // Try accessing war1 again      
+       checkAccessDeniedWebAuth(httpConn, webwarA1 + "restricted/restricted.html");
+       
+       // Try accessing war2 again      
+       checkAccessDeniedWebAuth(httpConn, webwarB2 + "restricted/restricted.html");      
+       
+   }
+
    public static void executeLogout(HttpClient httpConn, String warURL) throws IOException, HttpException
    {
       GetMethod logout = new GetMethod(warURL + "Logout");
@@ -161,6 +209,13 @@
       if( indexURI.indexOf("index.html") < 0 )
          fail("get of " + warURL + "Logout not redirected to login page");
    }
+   
+   public static void executeWebAuthLogout(HttpClient httpConn, String warURL) throws IOException, HttpException
+   {
+      GetMethod logout = new GetMethod(warURL);
+      int responseCode = httpConn.executeMethod(logout);
+      assertTrue("Get OK("+responseCode+")", responseCode == HttpURLConnection.HTTP_OK);
+   }
 
    public static void checkAccessAllowed(HttpClient httpConn, String url) throws IOException, HttpException
    {
@@ -195,7 +250,28 @@
       if( body.indexOf("j_security_check") > 0 )
          fail("get of "+indexURI+" redirected to login page");
    }
+   
+   public static void executeWebAuthLogin(HttpClient httpConn, String warURL) throws HttpException, IOException {
+       
+       String path = "/TestServlet?operation=login&username=jduke&pass=theduke";
+       HttpMethod indexGet = null;
+       indexGet = new GetMethod(warURL + path);
+       
+       int responseCode = httpConn.executeMethod(indexGet);
+       assertTrue("Get OK(" + responseCode + ")", responseCode == HttpURLConnection.HTTP_OK);
+       
+       SSOBaseCase.checkAccessAllowed(httpConn, warURL +
+               "restricted/restricted.html");
+   }
 
+   public static void checkAccessDeniedWebAuth(HttpClient httpConn, String url) throws IOException, HttpException
+   {
+      GetMethod indexGet = new GetMethod(url);
+      int responseCode = httpConn.executeMethod(indexGet);
+      String body = indexGet.getResponseBodyAsString();
+      assertTrue("Access Denied", responseCode == HttpURLConnection.HTTP_FORBIDDEN);
+   }
+   
    public static void checkAccessDenied(HttpClient httpConn, String url) throws IOException, HttpException
    {
       GetMethod indexGet = new GetMethod(url);
Index: testsuite/src/resources/web/programmatic/WEB-INF/jbosstest-webauth.xml
===================================================================
--- testsuite/src/resources/web/programmatic/WEB-INF/jbosstest-webauth.xml	(revision 0)
+++ testsuite/src/resources/web/programmatic/WEB-INF/jbosstest-webauth.xml	(revision 0)
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<web-app version="2.4"
+   xmlns="http://java.sun.com/xml/ns/j2ee"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+
+   <description>Programmatic Web Login Tests</description>
+
+   <!-- ### Servlets -->
+   <servlet>
+      <servlet-name>TestServlet</servlet-name>
+      <servlet-class>org.jboss.test.web.servlets.ProgrammaticLoginTestServlet</servlet-class>
+   </servlet>
+
+   <!-- The servlet and jsp page mappings -->
+   <servlet-mapping>
+      <servlet-name>TestServlet</servlet-name>
+      <url-pattern>/TestServlet</url-pattern>
+   </servlet-mapping>
+
+   <security-constraint>
+      <web-resource-collection>
+         <web-resource-name>Restricted</web-resource-name>
+         <description>Restricted Area</description>
+         <url-pattern>/restricted/*</url-pattern>
+      </web-resource-collection>
+      <auth-constraint>
+         <description>Only authenticated users can access secure content</description>
+         <role-name>AuthorizedUser</role-name>
+      </auth-constraint>
+   </security-constraint>
+   
+   <login-config>
+      <auth-method>NONE</auth-method>
+   </login-config>
+
+   <security-role>
+      <description>An AuthorizedUser is one with a valid username and password</description>
+      <role-name>AuthorizedUser</role-name>
+   </security-role>
+</web-app>
+
Index: testsuite/src/resources/web/programmatic/WEB-INF/context.xml
===================================================================
--- testsuite/src/resources/web/programmatic/WEB-INF/context.xml	(revision 0)
+++ testsuite/src/resources/web/programmatic/WEB-INF/context.xml	(revision 0)
@@ -0,0 +1,5 @@
+<Context cookies="true" crossContext="true">
+
+   <Valve className="org.jboss.web.tomcat.security.authenticators.CustomNonLoginAuthenticator" />
+   
+</Context>
Index: testsuite/src/resources/web/sso/clustered-application.xml
===================================================================
--- testsuite/src/resources/web/sso/clustered-application.xml	(revision 114411)
+++ testsuite/src/resources/web/sso/clustered-application.xml	(working copy)
@@ -35,14 +35,26 @@
          <web-uri>sso-form-auth5.war</web-uri>
          <context-root>/war5</context-root>
       </web>
-   </module>
-   <module>
-      <web>
-         <web-uri>sso-with-no-auth.war</web-uri>
-         <context-root>/war6</context-root>
-      </web>
    </module>
    <module>
+      <web>
+         <web-uri>sso-with-no-auth.war</web-uri>
+         <context-root>/war6</context-root>
+      </web>
+   </module>
+   <module>
       <ejb>jbosstest-web-ejbs.jar</ejb>
    </module>
+   <module>
+      <web>
+         <web-uri>sso-web-auth1.war</web-uri>
+         <context-root>/webwar1</context-root>
+      </web>
+   </module>
+   <module>
+      <web>
+         <web-uri>sso-web-auth2.war</web-uri>
+         <context-root>/webwar2</context-root>
+      </web>
+   </module>
 </application>
