--- test/org/apache/catalina/authenticator/TestSSOnonLoginAndBasicAuthenticator.java (revision 1555554)
+++ test/org/apache/catalina/authenticator/TestSSOnonLoginAndBasicAuthenticator.java (working copy)
@@ -22,14 +22,19 @@
import java.util.List;
import java.util.Map;
+import javax.servlet.http.HttpServletResponse;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.apache.catalina.Context;
-import org.apache.catalina.startup.TesterServlet;
+import org.apache.catalina.Session;
+import org.apache.catalina.session.ManagerBase;
+import org.apache.catalina.startup.TesterServletEncodeUrl;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.tomcat.util.buf.ByteChunk;
@@ -47,12 +52,36 @@
* simply cannot access protected resources. These tests exercise the
* the way successfully authenticating a different webapp under the
* BasicAuthenticator triggers the additional SSO logic for both webapps.
+ *
+ *
+ * The two Authenticators are thoroughly exercised by two other unit test
+ * classes: TestBasicAuthParser and TestNonLoginAndBasicAuthenticator.
+ * This class mainly examines the way the Single SignOn Valve interacts with
+ * two webapps when the second cannot be authenticated directly, but needs
+ * to inherit its authentication via the other.
+ *
+ *
+ * When the server and client can both use cookies, the authentication
+ * is preserved through the exchange of a JSSOSESSIONID cookie, which
+ * is different to the individual and unique JSESSIONID cookies assigned
+ * separately to the two webapp sessions.
+ *
+ *
+ * The other situation examined is where the server returns authentication
+ * cookies, but the client is configured to ignore them. The Tomcat
+ * documentation clearly states that SSO requires the client to
+ * support cookies, so access to resources in other webapp containers
+ * receives no SSO assistance.
*/
public class TestSSOnonLoginAndBasicAuthenticator extends TomcatBaseTest {
+ protected static final boolean USE_COOKIES = true;
+ protected static final boolean NO_COOKIES = !USE_COOKIES;
+
private static final String USER = "user";
private static final String PWD = "pwd";
private static final String ROLE = "role";
+ private static final String NICE_METHOD = "Basic";
private static final String HTTP_PREFIX = "http://localhost:";
private static final String CONTEXT_PATH_NOLOGIN = "/nologin";
@@ -60,216 +89,370 @@
private static final String URI_PROTECTED = "/protected";
private static final String URI_PUBLIC = "/anyoneCanAccess";
- private static final int SHORT_TIMEOUT_SECS = 4;
- private static final long SHORT_TIMEOUT_DELAY_MSECS =
- ((SHORT_TIMEOUT_SECS + 3) * 1000);
- private static final int LONG_TIMEOUT_SECS = 10;
- private static final long LONG_TIMEOUT_DELAY_MSECS =
- ((LONG_TIMEOUT_SECS + 5) * 1000);
+ // session expiry in web.xml is defined in minutes
+ private static final int SHORT_SESSION_TIMEOUT_MINS = 1;
+ private static final int LONG_SESSION_TIMEOUT_MINS = 2;
- private static String CLIENT_AUTH_HEADER = "authorization";
- private static String SERVER_COOKIES = "Set-Cookie";
- private static String BROWSER_COOKIES = "Cookie";
+ // we don't change the expiry scan interval - just the iteration count
+ private static final int MANAGER_SCAN_INTERVAL_SECS = 10;
+ private static final int MANAGER_EXPIRE_SESSIONS_FAST = 1;
+ // now compute some delays - beware of the units!
+ private static final int EXTRA_DELAY_SECS = 5;
+ private static final long REASONABLE_MSECS_TO_EXPIRY =
+ (((MANAGER_SCAN_INTERVAL_SECS * MANAGER_EXPIRE_SESSIONS_FAST)
+ + EXTRA_DELAY_SECS) * 1000);
+
+ private static final String CLIENT_AUTH_HEADER = "authorization";
+ private static final String SERVER_AUTH_HEADER = "WWW-Authenticate";
+ private static final String SERVER_COOKIE_HEADER = "Set-Cookie";
+ private static final String CLIENT_COOKIE_HEADER = "Cookie";
+ private static final String ENCODE_SESSION_PARAM = "jsessionid";
+ private static final String ENCODE_SSOSESSION_PARAM = "jssosessionid";
+
+ private static final
+ TestSSOnonLoginAndBasicAuthenticator.BasicCredentials
+ NO_CREDENTIALS = null;
+ private static final
+ TestSSOnonLoginAndBasicAuthenticator.BasicCredentials
+ GOOD_CREDENTIALS =
+ new TestSSOnonLoginAndBasicAuthenticator.BasicCredentials(
+ NICE_METHOD, USER, PWD);
+
+ private Tomcat tomcat;
+ private Context basicContext;
+ private Context nonloginContext;
private List cookies;
+ private String encodedURL;
/*
- * Try to access an unprotected resource without an established
- * SSO session.
- * This should be permitted.
+ * Run some sanity checks without an established SSO session
+ * to make sure the test environment is correct.
*/
@Test
- public void testAcceptPublicNonLogin() throws Exception {
+ public void testEssentialEnvironment() throws Exception {
+
+ // should be permitted to access an unprotected resource.
doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PUBLIC,
- false, false, 200);
+ USE_COOKIES, HttpServletResponse.SC_OK);
+
+ // should not be permitted to access a protected resource
+ // with the two Authenticators used in the remaining tests.
+ doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
+ USE_COOKIES, HttpServletResponse.SC_FORBIDDEN);
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ NO_CREDENTIALS, USE_COOKIES,
+ HttpServletResponse.SC_UNAUTHORIZED);
}
- /*
- * Try to access a protected resource without an established
- * SSO session.
- * This should be rejected with SC_FORBIDDEN 403 status.
- */
@Test
- public void testRejectProtectedNonLogin() throws Exception {
+ public void testEssentialEnvironmentWithoutCookies() throws Exception {
+
+ // should be permitted to access an unprotected resource.
+ doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PUBLIC,
+ NO_COOKIES, HttpServletResponse.SC_OK);
+
+ // should not be permitted to access a protected resource
+ // with the two Authenticators used in the remaining tests.
doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
- false, true, 403);
+ NO_COOKIES, HttpServletResponse.SC_FORBIDDEN);
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ NO_CREDENTIALS, NO_COOKIES,
+ HttpServletResponse.SC_UNAUTHORIZED);
}
/*
* Logon to access a protected resource using BASIC authentication,
* which will establish an SSO session.
* Wait until the SSO session times-out, then try to re-access
- * the resource.
- * This should be rejected with SC_FORBIDDEN 401 status, which
- * will then be followed by successful re-authentication.
+ * the resource. This should be rejected with SC_FORBIDDEN 401 status.
+ *
+ * Note: this test will run for slightly more than 1 minute.
*/
@Test
- public void testBasicLoginSessionTimeout() throws Exception {
- doTestBasic(USER, PWD, CONTEXT_PATH_LOGIN + URI_PROTECTED,
- true, 401, false, 200);
- // wait long enough for my session to expire
- Thread.sleep(SHORT_TIMEOUT_DELAY_MSECS);
- doTestBasic(USER, PWD, CONTEXT_PATH_LOGIN + URI_PROTECTED,
- true, 401, false, 200);
+ public void testBasicAccessAndSessionTimeout() throws Exception {
+
+ setRapidSessionTimeoutDetection();
+
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ NO_CREDENTIALS, USE_COOKIES,
+ HttpServletResponse.SC_UNAUTHORIZED);
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ GOOD_CREDENTIALS, USE_COOKIES,
+ HttpServletResponse.SC_OK);
+
+ // verify the SSOID exists as a cookie
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ GOOD_CREDENTIALS, USE_COOKIES,
+ HttpServletResponse.SC_OK);
+
+ // make the session time out and lose authentication
+ doImminentSessionTimeout(basicContext);
+
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ NO_CREDENTIALS, USE_COOKIES,
+ HttpServletResponse.SC_UNAUTHORIZED);
}
+
/*
* Logon to access a protected resource using BASIC authentication,
* which will establish an SSO session.
* Immediately try to access a protected resource in the NonLogin
- * webapp, but without sending the SSO session cookie.
- * This should be rejected with SC_FORBIDDEN 403 status.
+ * webapp while providing the SSO session cookie received from the
+ * first webapp. This should be successful with SC_OK 200 status.
*/
@Test
- public void testBasicLoginRejectProtectedWithoutCookies() throws Exception {
- doTestBasic(USER, PWD, CONTEXT_PATH_LOGIN + URI_PROTECTED,
- true, 401, false, 200);
+ public void testBasicLoginThenAcceptWithCookies() throws Exception {
+
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ NO_CREDENTIALS, NO_COOKIES,
+ HttpServletResponse.SC_UNAUTHORIZED);
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ GOOD_CREDENTIALS, USE_COOKIES, HttpServletResponse.SC_OK);
+
+ // send the cookie which proves we have an authenticated SSO session
doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
- false, true, 403);
+ USE_COOKIES, HttpServletResponse.SC_OK);
}
/*
* Logon to access a protected resource using BASIC authentication,
* which will establish an SSO session.
* Immediately try to access a protected resource in the NonLogin
- * webapp while sending the SSO session cookie provided by the
- * first webapp.
- * This should be successful with SC_OK 200 status.
+ * webapp, but without sending the SSO session cookie.
+ * This should be rejected with SC_FORBIDDEN 403 status.
*/
@Test
- public void testBasicLoginAcceptProtectedWithCookies() throws Exception {
- doTestBasic(USER, PWD, CONTEXT_PATH_LOGIN + URI_PROTECTED,
- true, 401, false, 200);
+ public void testBasicLoginThenRejectWithoutCookie() throws Exception {
+
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ NO_CREDENTIALS, USE_COOKIES,
+ HttpServletResponse.SC_UNAUTHORIZED);
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ GOOD_CREDENTIALS, USE_COOKIES,
+ HttpServletResponse.SC_OK);
+
+ // fail to send the authentication cookie to the other webapp.
doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
- true, false, 200);
+ NO_COOKIES, HttpServletResponse.SC_FORBIDDEN);
}
/*
* Logon to access a protected resource using BASIC authentication,
* which will establish an SSO session.
+ * Then try to access a protected resource in the NonLogin
+ * webapp by sending the JSESSIONID from the redirect header.
+ * The access request should be rejected because the Basic webapp's
+ * sessionID is not valid for any other container.
+ */
+ @Test
+ public void testBasicAccessThenAcceptAuthWithUri() throws Exception {
+
+ setAlwaysUseSession();
+
+ // first, fail to access the protected resource without credentials
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ NO_CREDENTIALS, NO_COOKIES,
+ HttpServletResponse.SC_UNAUTHORIZED);
+
+ // now, access the protected resource with good credentials
+ // to establish the session
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ GOOD_CREDENTIALS, NO_COOKIES,
+ HttpServletResponse.SC_OK);
+
+ // next, access it again to harvest the session id url parameter
+ String forwardParam = "?nextUrl=" + CONTEXT_PATH_LOGIN + URI_PROTECTED;
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED + forwardParam,
+ GOOD_CREDENTIALS, NO_COOKIES,
+ HttpServletResponse.SC_OK);
+
+ // verify the sessionID was encoded in the absolute URL
+ String firstEncodedURL = encodedURL;
+ assertTrue(firstEncodedURL.contains(ENCODE_SESSION_PARAM));
+
+ // access the protected resource with the encoded url (with session id)
+ doTestBasic(firstEncodedURL + forwardParam,
+ NO_CREDENTIALS, NO_COOKIES,
+ HttpServletResponse.SC_OK);
+
+ // verify the sessionID has not changed
+ // verify the SSO sessionID was not encoded
+ String secondEncodedURL = encodedURL;
+ assertEquals(firstEncodedURL, secondEncodedURL);
+ assertFalse(firstEncodedURL.contains(ENCODE_SSOSESSION_PARAM));
+
+ // extract the first container's session ID
+ int ix = secondEncodedURL.indexOf(ENCODE_SESSION_PARAM);
+ String sessionId = secondEncodedURL.substring(ix);
+
+ // expect to fail using that sessionID in a different container
+ doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED + ";" + sessionId,
+ NO_COOKIES, HttpServletResponse.SC_FORBIDDEN);
+ }
+
+ /*
+ * Logon to access a protected resource using BASIC authentication,
+ * which will establish an SSO session.
* Immediately try to access a protected resource in the NonLogin
- * webapp while sending the SSO session cookie provided by the
- * first webapp.
- * This should be successful with SC_OK 200 status.
+ * webapp while providing the SSO session cookie received from the
+ * first webapp. This should be successful with SC_OK 200 status.
*
* Then, wait long enough for the BASIC session to expire. (The SSO
* session should remain active because the NonLogin session has
* not yet expired).
- *
* Try to access the protected resource again, before the SSO session
- * has expired.
- * This should be successful with SC_OK 200 status.
+ * has expired. This should be successful with SC_OK 200 status.
*
* Finally, wait for the non-login session to expire and try again..
* This should be rejected with SC_FORBIDDEN 403 status.
*
* (see bugfix https://issues.apache.org/bugzilla/show_bug.cgi?id=52303)
+ *
+ * Note: this test will run for slightly more than 3 minutes.
*/
@Test
public void testBasicExpiredAcceptProtectedWithCookies() throws Exception {
- doTestBasic(USER, PWD, CONTEXT_PATH_LOGIN + URI_PROTECTED,
- true, 401, false, 200);
+
+ setRapidSessionTimeoutDetection();
+
+ // begin with a repeat of testBasicLoginAcceptProtectedWithCookies
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ NO_CREDENTIALS, USE_COOKIES,
+ HttpServletResponse.SC_UNAUTHORIZED);
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ GOOD_CREDENTIALS, USE_COOKIES,
+ HttpServletResponse.SC_OK);
doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
- true, false, 200);
+ USE_COOKIES, HttpServletResponse.SC_OK);
// wait long enough for the BASIC session to expire,
- // but not long enough for NonLogin session expiry
- Thread.sleep(SHORT_TIMEOUT_DELAY_MSECS);
+ // but not long enough for the NonLogin session expiry.
+ doImminentSessionTimeout(basicContext);
+
+ // this successful NonLogin access should replenish the
+ // the individual session expiry time and keep the SSO session alive
doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
- true, false, 200);
+ USE_COOKIES, HttpServletResponse.SC_OK);
- // wait long enough for my NonLogin session to expire
- // and tear down the SSO session at the same time.
- Thread.sleep(LONG_TIMEOUT_DELAY_MSECS);
- doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED,
- false, true, 403);
+ // wait long enough for the NonLogin session to expire,
+ // which will also tear down the SSO session at the same time.
+ doImminentSessionTimeout(nonloginContext);
+
+ doTestNonLogin(CONTEXT_PATH_NOLOGIN + URI_PROTECTED, USE_COOKIES,
+ HttpServletResponse.SC_FORBIDDEN);
+ doTestBasic(CONTEXT_PATH_LOGIN + URI_PROTECTED,
+ NO_CREDENTIALS, USE_COOKIES,
+ HttpServletResponse.SC_UNAUTHORIZED);
+
}
- public void doTestNonLogin(String uri, boolean addCookies,
- boolean expectedReject, int expectedRC)
- throws Exception {
+ public void doTestNonLogin(String uri, boolean useCookie,
+ int expectedRC) throws Exception {
Map> reqHeaders = new HashMap<>();
- if (addCookies) {
- addCookies(reqHeaders);
- }
Map> respHeaders = new HashMap<>();
+ if (useCookie && (cookies != null)) {
+ reqHeaders.put(CLIENT_COOKIE_HEADER + ":", cookies);
+ }
+
ByteChunk bc = new ByteChunk();
int rc = getUrl(HTTP_PREFIX + getPort() + uri, bc, reqHeaders,
respHeaders);
- if (expectedReject) {
+ if (expectedRC != HttpServletResponse.SC_OK) {
assertEquals(expectedRC, rc);
assertTrue(bc.getLength() > 0);
}
else {
- assertEquals(200, rc);
assertEquals("OK", bc.toString());
- saveCookies(respHeaders);
}
}
- public void doTestBasic(String user, String pwd, String uri,
- boolean expectedReject1, int expectedRC1,
- boolean expectedReject2, int expectedRC2) throws Exception {
+ private void doTestBasic(String uri,
+ TestSSOnonLoginAndBasicAuthenticator.BasicCredentials credentials,
+ boolean useCookie, int expectedRC) throws Exception {
- // the first access attempt should be challenged
- Map> reqHeaders1 = new HashMap<>();
- Map> respHeaders1 = new HashMap<>();
+ Map> reqHeaders = new HashMap<>();
+ Map> respHeaders = new HashMap<>();
- ByteChunk bc = new ByteChunk();
- int rc = getUrl(HTTP_PREFIX + getPort() + uri, bc, reqHeaders1,
- respHeaders1);
-
- if (expectedReject1) {
- assertEquals(expectedRC1, rc);
- assertTrue(bc.getLength() > 0);
+ if (useCookie && (cookies != null)) {
+ reqHeaders.put(CLIENT_COOKIE_HEADER + ":", cookies);
}
else {
- assertEquals(200, rc);
- assertEquals("OK", bc.toString());
- return;
+ if (credentials != null) {
+ List auth = new ArrayList<>();
+ auth.add(credentials.getCredentials());
+ reqHeaders.put(CLIENT_AUTH_HEADER, auth);
+ }
}
- // the second access attempt should be successful
- String credentials = user + ":" + pwd;
+ ByteChunk bc = new ByteChunk();
+ int rc = getUrl(HTTP_PREFIX + getPort() + uri, bc, reqHeaders,
+ respHeaders);
- String base64auth = Base64.encodeBase64String(
- credentials.getBytes(StandardCharsets.ISO_8859_1));
- String authLine = "Basic " + base64auth;
-
- List auth = new ArrayList<>();
- auth.add(authLine);
- Map> reqHeaders2 = new HashMap<>();
- reqHeaders2.put(CLIENT_AUTH_HEADER, auth);
-
- Map> respHeaders2 = new HashMap<>();
-
- bc.recycle();
- rc = getUrl(HTTP_PREFIX + getPort() + uri, bc, reqHeaders2,
- respHeaders2);
-
- if (expectedReject2) {
- assertEquals(expectedRC2, rc);
- assertNull(bc.toString());
+ assertEquals("Unexpected Return Code", expectedRC, rc);
+ if (expectedRC != HttpServletResponse.SC_OK) {
+ assertTrue(bc.getLength() > 0);
+ if (expectedRC == HttpServletResponse.SC_UNAUTHORIZED) {
+ // The server should identify the acceptable method(s)
+ boolean methodFound = false;
+ List authHeaders = respHeaders.get(SERVER_AUTH_HEADER);
+ for (String authHeader : authHeaders) {
+ if (authHeader.indexOf(NICE_METHOD) > -1) {
+ methodFound = true;
+ break;
+ }
+ }
+ assertTrue(methodFound);
+ }
}
else {
- assertEquals(200, rc);
- assertEquals("OK", bc.toString());
- saveCookies(respHeaders2);
+ String thePage = bc.toString();
+ assertNotNull(thePage);
+ assertTrue(thePage.startsWith("OK"));
+ if (useCookie) {
+ List newCookies = respHeaders.get(SERVER_COOKIE_HEADER);
+ if (newCookies != null) {
+ // harvest cookies whenever the server sends some new ones
+ cookies = newCookies;
+ }
+ }
+ else {
+ encodedURL = "";
+ final String start = "";
+ int iStart = thePage.indexOf(start);
+ int iEnd = 0;
+ if (iStart > -1) {
+ iStart += start.length();
+ iEnd = thePage.indexOf(end, iStart);
+ if (iEnd > -1) {
+ encodedURL = thePage.substring(iStart, iEnd);
+ }
+ }
+ }
}
}
+
+
+ /*
+ * setup two webapps for every test
+ *
+ * note: the super class tearDown method will stop tomcat
+ */
@Override
public void setUp() throws Exception {
super.setUp();
// create a tomcat server using the default in-memory Realm
- Tomcat tomcat = getTomcatInstance();
+ tomcat = getTomcatInstance();
// associate the SingeSignOn Valve before the Contexts
SingleSignOn sso = new SingleSignOn();
@@ -280,71 +463,92 @@
tomcat.addRole(USER, ROLE);
// setup both NonLogin and Login webapps
- setUpNonLogin(tomcat);
- setUpLogin(tomcat);
+ setUpNonLogin();
+ setUpLogin();
tomcat.start();
}
- private void setUpNonLogin(Tomcat tomcat) throws Exception {
+ @Override
+ public void tearDown() throws Exception {
+ tomcat.stop();
+ }
+
+ private void setUpNonLogin() throws Exception {
+
// Must have a real docBase for webapps - just use temp
- Context ctxt = tomcat.addContext(CONTEXT_PATH_NOLOGIN,
+ nonloginContext = tomcat.addContext(CONTEXT_PATH_NOLOGIN,
System.getProperty("java.io.tmpdir"));
- ctxt.setSessionTimeout(LONG_TIMEOUT_SECS);
+ nonloginContext.setSessionTimeout(LONG_SESSION_TIMEOUT_MINS);
- // Add protected servlet
- Tomcat.addServlet(ctxt, "TesterServlet1", new TesterServlet());
- ctxt.addServletMapping(URI_PROTECTED, "TesterServlet1");
+ // Add protected servlet to the context
+ Tomcat.addServlet(nonloginContext, "TesterServlet1",
+ new TesterServletEncodeUrl());
+ nonloginContext.addServletMapping(URI_PROTECTED, "TesterServlet1");
SecurityCollection collection1 = new SecurityCollection();
collection1.addPattern(URI_PROTECTED);
SecurityConstraint sc1 = new SecurityConstraint();
sc1.addAuthRole(ROLE);
sc1.addCollection(collection1);
- ctxt.addConstraint(sc1);
+ nonloginContext.addConstraint(sc1);
- // Add unprotected servlet
- Tomcat.addServlet(ctxt, "TesterServlet2", new TesterServlet());
- ctxt.addServletMapping(URI_PUBLIC, "TesterServlet2");
+ // Add unprotected servlet to the context
+ Tomcat.addServlet(nonloginContext, "TesterServlet2",
+ new TesterServletEncodeUrl());
+ nonloginContext.addServletMapping(URI_PUBLIC, "TesterServlet2");
SecurityCollection collection2 = new SecurityCollection();
collection2.addPattern(URI_PUBLIC);
SecurityConstraint sc2 = new SecurityConstraint();
// do not add a role - which signals access permitted without one
sc2.addCollection(collection2);
- ctxt.addConstraint(sc2);
+ nonloginContext.addConstraint(sc2);
// Configure the authenticator and inherit the Realm from Engine
LoginConfig lc = new LoginConfig();
lc.setAuthMethod("NONE");
- ctxt.setLoginConfig(lc);
- ctxt.getPipeline().addValve(new NonLoginAuthenticator());
+ nonloginContext.setLoginConfig(lc);
+ AuthenticatorBase nonloginAuthenticator = new NonLoginAuthenticator();
+ nonloginContext.getPipeline().addValve(nonloginAuthenticator);
}
- private void setUpLogin(Tomcat tomcat) throws Exception {
+ private void setUpLogin() throws Exception {
// Must have a real docBase for webapps - just use temp
- Context ctxt = tomcat.addContext(CONTEXT_PATH_LOGIN,
+ basicContext = tomcat.addContext(CONTEXT_PATH_LOGIN,
System.getProperty("java.io.tmpdir"));
- ctxt.setSessionTimeout(SHORT_TIMEOUT_SECS);
+ basicContext.setSessionTimeout(SHORT_SESSION_TIMEOUT_MINS);
- // Add protected servlet
- Tomcat.addServlet(ctxt, "TesterServlet3", new TesterServlet());
- ctxt.addServletMapping(URI_PROTECTED, "TesterServlet3");
-
+ // Add protected servlet to the context
+ Tomcat.addServlet(basicContext, "TesterServlet3",
+ new TesterServletEncodeUrl());
+ basicContext.addServletMapping(URI_PROTECTED, "TesterServlet3");
SecurityCollection collection = new SecurityCollection();
collection.addPattern(URI_PROTECTED);
SecurityConstraint sc = new SecurityConstraint();
sc.addAuthRole(ROLE);
sc.addCollection(collection);
- ctxt.addConstraint(sc);
+ basicContext.addConstraint(sc);
- // Configure the appropriate authenticator
+ // Add unprotected servlet to the context
+ Tomcat.addServlet(basicContext, "TesterServlet4",
+ new TesterServletEncodeUrl());
+ basicContext.addServletMapping(URI_PUBLIC, "TesterServlet4");
+ SecurityCollection collection2 = new SecurityCollection();
+ collection2.addPattern(URI_PUBLIC);
+ SecurityConstraint sc2 = new SecurityConstraint();
+ // do not add a role - which signals access permitted without one
+ sc2.addCollection(collection2);
+ basicContext.addConstraint(sc2);
+
+ // Configure the authenticator and inherit the Realm from Engine
LoginConfig lc = new LoginConfig();
lc.setAuthMethod("BASIC");
- ctxt.setLoginConfig(lc);
- ctxt.getPipeline().addValve(new BasicAuthenticator());
+ basicContext.setLoginConfig(lc);
+ AuthenticatorBase basicAuthenticator = new BasicAuthenticator();
+ basicContext.getPipeline().addValve(basicAuthenticator);
}
/*
@@ -353,7 +557,7 @@
protected void saveCookies(Map> respHeaders) {
// we only save the Cookie values, not header prefix
- cookies = respHeaders.get(SERVER_COOKIES);
+ cookies = respHeaders.get(SERVER_COOKIE_HEADER);
}
/*
@@ -362,7 +566,90 @@
protected void addCookies(Map> reqHeaders) {
if ((cookies != null) && (cookies.size() > 0)) {
- reqHeaders.put(BROWSER_COOKIES + ":", cookies);
+ reqHeaders.put(CLIENT_COOKIE_HEADER + ":", cookies);
}
}
-}
+
+ /*
+ * Force non-default behaviour for both Authenticators.
+ * The session id will not be regenerated after authentication,
+ * which is less secure but needed for browsers that will not
+ * handle cookies.
+ */
+ private void setAlwaysUseSession() {
+
+ ((AuthenticatorBase) basicContext.getAuthenticator())
+ .setAlwaysUseSession(true);
+ ((AuthenticatorBase) nonloginContext.getAuthenticator())
+ .setAlwaysUseSession(true);
+ }
+
+ /*
+ * Force faster timeout for an active Container than can
+ * be defined in web.xml. By getting to the active Session we
+ * can choose seconds instead of minutes.
+ * Note: shamelessly cloned from ManagerBase - beware of synch issues
+ * on the underlying sessions.
+ */
+ private void doImminentSessionTimeout(Context activeContext) {
+
+ ManagerBase manager = (ManagerBase) activeContext.getManager();
+ Session[] sessions = manager.findSessions();
+ for (int i = 0; i < sessions.length; i++) {
+ if (sessions[i]!=null && sessions[i].isValid()) {
+ sessions[i].setMaxInactiveInterval(EXTRA_DELAY_SECS);
+ // leave it to be expired by the manager
+ }
+ }
+ try {
+ Thread.sleep(REASONABLE_MSECS_TO_EXPIRY);
+ }
+ catch (InterruptedException ie) {/* ignored */};
+
+ // paranoid verification that active sessions have now gone
+ sessions = manager.findSessions();
+ assertTrue(sessions.length == 0);
+ }
+
+ /*
+ * Force rapid timeout scanning for both webapps
+ * The StandardManager default service cycle time is 10 seconds,
+ * with a session expiry scan every 6 cycles.
+ */
+ private void setRapidSessionTimeoutDetection() {
+
+ ((ManagerBase) basicContext.getManager())
+ .setProcessExpiresFrequency(MANAGER_EXPIRE_SESSIONS_FAST);
+ ((ManagerBase) nonloginContext.getManager())
+ .setProcessExpiresFrequency(MANAGER_EXPIRE_SESSIONS_FAST);
+ }
+
+ /*
+ * Encapsulate the logic to generate an HTTP header
+ * for BASIC Authentication.
+ * Note: only used internally, so no need to validate arguments.
+ */
+ private static final class BasicCredentials {
+
+ private final String method;
+ private final String username;
+ private final String password;
+ private final String credentials;
+
+ private BasicCredentials(String aMethod,
+ String aUsername, String aPassword) {
+ method = aMethod;
+ username = aUsername;
+ password = aPassword;
+ String userCredentials = username + ":" + password;
+ byte[] credentialsBytes =
+ userCredentials.getBytes(StandardCharsets.ISO_8859_1);
+ String base64auth = Base64.encodeBase64String(credentialsBytes);
+ credentials= method + " " + base64auth;
+ }
+
+ private String getCredentials() {
+ return credentials;
+ }
+ }
+}
--- test/org/apache/catalina/startup/TesterServletEncodeUrl.java (working copy)
+++ test/org/apache/catalina/startup/TesterServletEncodeUrl.java (working copy)
@@ -24,10 +24,21 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-public class TesterServlet extends HttpServlet {
+/**
+ * A test servlet that will always encode the url in case the client requires
+ * session persistence but is not configured to support cookies.
+ */
+public class TesterServletEncodeUrl extends HttpServlet {
private static final long serialVersionUID = 1L;
+ /**
+ * Almost minimal processing for a servlet.
+ *
+ * @param nextUrl The url the caller would like to go to next. If
+ * supplied, put an encoded url into the returned
+ * html page as a hyperlink.
+ */
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
@@ -35,5 +46,14 @@
resp.setContentType("text/plain");
PrintWriter out = resp.getWriter();
out.print("OK");
+
+ String param = req.getParameter("nextUrl");
+ if (param!=null) {
+ // append an encoded url to carry the sessionids
+ String targetUrl = resp.encodeURL(param);
+ out.print(". You want to go here next.");
+ }
}
}