/*
- * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/authenticator/FormAuthenticator.java,v 1.11 2005/02/22 11:02:16 maxcooper Exp $
- * $Revision: 1.11 $
- * $Date: 2005/02/22 11:02:16 $
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/authenticator/FormAuthenticator.java,v 1.12 2007/11/02 16:31:24 chris_schultz Exp $
+ * $Revision: 1.12 $
+ * $Date: 2007/11/02 16:31:24 $
*
* ====================================================================
* The SecurityFilter Software License, Version 1.1
import java.io.IOException;
import java.security.Principal;
+import java.util.Enumeration;
+import java.net.URLEncoder;
+import java.io.UnsupportedEncodingException;
+
/**
* FormAuthenticator - authenticator implementation for the FORM auth method.
*
* @author Max Cooper (max@maxcooper.com)
- * @version $Revision: 1.11 $ $Date: 2005/02/22 11:02:16 $
+ * @author Chris Schultz (chris@christopherschultz.net)
+ * @version $Revision: 1.12 $ $Date: 2007/11/02 16:31:24 $
*/
public class FormAuthenticator implements Authenticator {
public static final String LOGIN_SUBMIT_PATTERN_KEY = "loginSubmitPattern";
public static final String DEFAULT_LOGIN_SUBMIT_PATTERN = "/j_security_check";
+
protected String loginSubmitPattern;
protected static final String FORM_USERNAME = "j_username";
protected SecurityRealmInterface realm;
+
+ /**
+ * The key that will be used to look up the filter init parameter
+ * that specifies the "forward" parameter used for post-login forward
+ * requests.
+ *
+ * @see #forwardParameterName
+ */
+ public static final String FORWARD_PARAMETER_KEY = "forwardParameter";
+
+ /**
+ * The key that will be used to look up the filter init parameter
+ * that specifies the "forwardMode" parameter used for post-login forward
+ * requests.
+ *
+ * @see #forwardModeParameterName
+ */
+ public static final String FORWARD_MODE_PARAMETER_KEY = "forwardModeParameter";
+
+ /**
+ * The key that will be used to look up the filter init parameter
+ * that specifies the "forwardParameters" parameter used for post-login
+ * forward requests.
+ *
+ * @see #forwardParametersParameterName
+ */
+ public static final String FORWARD_PARAMETERS_PARAMETER_KEY = "forwardParametersParameter";
+
+ /**
+ * The default value for {@link #forwardParameterName}.
+ */
+ public static final String DEFAULT_FORWARD_PARAMETER_NAME = "forward";
+ /**
+ * The default value for {@link #forwardModeParameterName}.
+ */
+ public static final String DEFAULT_FORWARD_MODE_PARAMETER_NAME = "forward-mode";
+ /**
+ * The default value for {@link #forwardParametersParameterName}.
+ */
+ public static final String DEFAULT_FORWARD_PARAMETERS_PARAMETER_NAME = "forward-parameters";
+
+ /**
+ * The name of the request parameter that will be recognized as a
+ * post-login forward request.
+ *
+ * @see #forwardParameterName
+ * @see #DEFAULT_FORWARD_PARAMETER_NAME
+ */
+ protected String forwardParameterName;
+
+ /**
+ * The name of the request parameter that will be checked for
+ * either "forward" or "redirect" when processing a post-login forward
+ * request. The default is "redirect".
+ *
+ * @see #forwardModeParameterName
+ * @see #DEFAULT_FORWARD_MODE_PARAMETER_NAME
+ */
+ protected String forwardModeParameterName;
+
+ /**
+ * The name of the request parameter that will be checked to see
+ * whether the login request's request parameters should be forwarded
+ * to the destination URI when processing a post-login forward request.
+ * The options are "true" (to forward the request parameters) or "false"
+ * (to forward to the destination URI with no request parameter
+ * pass-through. The default is "false".
+ *
+ * @see #forwardParameterName
+ * @see #DEFAULT_FORWARD_PARAMETERS_PARAMETER_NAME
+ */
+ protected String forwardParametersParameterName;
+
/**
* Initilize this Authenticator.
*
loginSubmitPattern = DEFAULT_LOGIN_SUBMIT_PATTERN;
}
+ // "forward" parameter
+ forwardParameterName = filterConfig.getInitParameter(FORWARD_PARAMETER_KEY);
+ if(null == forwardParameterName)
+ forwardParameterName = DEFAULT_FORWARD_PARAMETER_NAME;
+
+ // "forward-mode" parameter name
+ forwardModeParameterName = filterConfig.getInitParameter(FORWARD_MODE_PARAMETER_KEY);
+ if(null == forwardModeParameterName)
+ forwardModeParameterName = DEFAULT_FORWARD_MODE_PARAMETER_NAME;
+
+ // "forward-parameters" parameter name
+ forwardParametersParameterName = filterConfig.getInitParameter(FORWARD_PARAMETERS_PARAMETER_KEY);
+ if(null == forwardParametersParameterName)
+ forwardParametersParameterName = DEFAULT_FORWARD_PARAMETERS_PARAMETER_NAME;
+
// default page
defaultPage = securityConfig.getDefaultPage();
// process login form submittal
if (request.getMatchableURL().endsWith(loginSubmitPattern)) {
- String username = request.getParameter(FORM_USERNAME);
- String password = request.getParameter(FORM_PASSWORD);
- Principal principal = realm instanceof FlexibleRealmInterface ?
- ((FlexibleRealmInterface) realm).authenticate(request)
- : realm.authenticate(username, password);
- if (principal != null) {
- // login successful
-
- // invalidate old session if the user was already authenticated, and they logged in as a different user
- if (request.getUserPrincipal() != null
- && false == request.getUserPrincipal().equals(principal)) {
- request.getSession().invalidate();
- }
-
- // manage persistent login info, if persistent login management is enabled
- // and username/password are passed as part of logon
- if (persistentLoginManager != null
- && username != null && password != null) {
- String rememberme = request.getParameter(FORM_REMEMBERME);
- // did the user request that their login be persistent?
- if (rememberme != null) {
- // remember login
- persistentLoginManager.rememberLogin(request, response, username, password);
- } else {
- // forget login
- persistentLoginManager.forgetLogin(request, response);
- }
- }
-
- request.setUserPrincipal(principal);
- String continueToURL = getContinueToURL(request);
- // This is the url that the user was initially accessing before being prompted for login.
- response.sendRedirect(response.encodeRedirectURL(continueToURL));
- } else {
- // login failed - forward to error page
- request.getRequestDispatcher(errorPage).forward(request, response);
- }
- return true;
+ String username = request.getParameter(FORM_USERNAME);
+ String password = request.getParameter(FORM_PASSWORD);
+ Principal principal = realm instanceof FlexibleRealmInterface ?
+ ((FlexibleRealmInterface) realm).authenticate(request)
+ : realm.authenticate(username, password);
+ if (principal != null) {
+ // login successful
+
+ // invalidate old session if the user was already authenticated, and they logged in as a different user
+ if (request.getUserPrincipal() != null
+ && false == request.getUserPrincipal().equals(principal)) {
+ request.getSession().invalidate();
+ }
+
+ // manage persistent login info, if persistent login management is enabled
+ // and username/password are passed as part of logon
+ if (persistentLoginManager != null
+ && username != null && password != null) {
+ String rememberme = request.getParameter(FORM_REMEMBERME);
+ // did the user request that their login be persistent?
+ if (rememberme != null) {
+ // remember login
+ persistentLoginManager.rememberLogin(request, response, username, password);
+ } else {
+ // forget login
+ persistentLoginManager.forgetLogin(request, response);
+ }
+ }
+
+ request.setUserPrincipal(principal);
+
+
+ Forward fwd = getForward(request);
+
+ if(fwd.redirect)
+ {
+ String uri = response.encodeRedirectURL(fwd.uri);
+
+ // Parameters only need to be explicitly forwarded
+ // when we're doing a redirect.
+ if(fwd.forwardParameters)
+ {
+ StringBuffer q = this.getFilteredQueryString(request);
+ if(null != q)
+ uri += q;
+ }
+
+ response.sendRedirect(uri);
+ }
+ else
+ request.getRequestDispatcher(fwd.uri).
+ forward(request, response);
+ } else {
+ // login failed - forward to error page
+ request.getRequestDispatcher(errorPage).forward(request, response);
+ }
+ return true;
}
return false;
}
return uri;
}
+
+ /**
+ * A class to represent information about the destination after login.
+ */
+ private static class Forward
+ {
+ /**
+ * The destination URI.
+ */
+ String uri;
+
+ /**
+ * <code>true</code> if this Forward should be redirected through the
+ * client.
+ */
+ boolean redirect;
+
+ /**
+ * <code>true</code> if the forward should include all the parameters
+ * from the current request.
+ */
+ boolean forwardParameters;
+
+ Forward(String uri, boolean redirect, boolean forwardParameters)
+ {
+ this.uri = uri;
+ this.redirect = redirect;
+ this.forwardParameters = forwardParameters;
+ }
+ }
+
+ /**
+ * Gets post-login destination information.
+ */
+ private Forward getForward(HttpServletRequest request)
+ {
+ String uri = request.getParameter(forwardParameterName);
+ boolean redirect;
+ boolean forwardParameters;
+
+ // Was there a request to forward somewhere else after login?
+ if(null != uri && 0 < uri.trim().length())
+ {
+ // Default to redirect
+ redirect = !"forward".equalsIgnoreCase(request.getParameter(forwardModeParameterName));
+ // Default to do-not-forward-parameters
+ forwardParameters = "true".equalsIgnoreCase(request.getParameter(forwardParametersParameterName));
+ }
+ else
+ {
+ // No forward request: go to the "continue URL" which is either
+ // the user's original request or the default page to hit after login.
+ uri = getContinueToURL(request);
+ redirect = true;
+ forwardParameters = false;
+ }
+
+ return new Forward(uri, redirect, forwardParameters);
+ }
+
+ /**
+ * Gets the query string that will be used when a login request
+ * has included a "forward" directive. We don't want to include
+ * username and password information in the resulting URL, so we
+ * re-build the query string by stripping-out the sensitive
+ * parameters. We also strip-out the "forward" parameter information
+ * because it has served its purpose.
+ *
+ * @param request The request being processed.
+ *
+ * @return A StringBuffer containing the query string (starting with '?')
+ * with all of the current request's parameters except for
+ * the username, password, and forward-related parameters.
+ */
+ private StringBuffer getFilteredQueryString(HttpServletRequest request)
+ throws UnsupportedEncodingException
+ {
+ Enumeration e = request.getParameterNames();
+
+ StringBuffer queryString = null;
+
+ if(e.hasMoreElements())
+ {
+ boolean first = true;
+ queryString = new StringBuffer();
+
+ while(e.hasMoreElements())
+ {
+ String name = (String)e.nextElement();
+
+ // Filter-out login-related parameters
+ if(!(FORM_USERNAME.equals(name)
+ || FORM_PASSWORD.equals(name)
+ || forwardParameterName.equals(name)
+ || forwardModeParameterName.equals(name)
+ || forwardParametersParameterName.equals(name)))
+ {
+ String[] values = request.getParameterValues(name);
+
+ for(int i=0; i<values.length; ++i)
+ {
+ if(first)
+ {
+ queryString.append('?');
+ first = false;
+ }
+ else
+ queryString.append('&');
+
+ queryString
+ .append(URLEncoder.encode(name, "UTF-8"))
+ .append('=')
+ .append(URLEncoder.encode(values[i], "UTF-8"));
+ }
+ }
+ }
+ }
+
+ return queryString;
+ }
}
// ------------------------------------------------------------------------
--- /dev/null
+/*
+ * $Header: /cvsroot/securityfilter/securityfilter/src/test/org/securityfilter/test/http/form/ForwardAfterLoginTest.java,v 1.1 2007/11/02 16:31:24 chris_schultz Exp $
+ * $Revision: 1.1 $
+ * $Date: 2007/11/02 16:31:24 $
+ *
+ * ====================================================================
+ * The SecurityFilter Software License, Version 1.1
+ *
+ * (this license is derived and fully compatible with the Apache Software
+ * License - see http://www.apache.org/LICENSE.txt)
+ *
+ * Copyright (c) 2002 SecurityFilter.org. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by
+ * SecurityFilter.org (http://www.securityfilter.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The name "SecurityFilter" must not be used to endorse or promote
+ * products derived from this software without prior written permission.
+ * For written permission, please contact license@securityfilter.org .
+ *
+ * 5. Products derived from this software may not be called "SecurityFilter",
+ * nor may "SecurityFilter" appear in their name, without prior written
+ * permission of SecurityFilter.org.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE SECURITY FILTER PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.securityfilter.test.http.form;
+
+import com.meterware.httpunit.*;
+import junit.framework.Assert;
+import org.securityfilter.example.Constants;
+import org.securityfilter.test.http.TestBase;
+import org.securityfilter.authenticator.FormAuthenticator;
+
+/**
+ * ForwardAfterLoginTest - test forward-afterlogin behavior.
+ *
+ * @author Chris Schultz (chris@christopherschultz.net)
+ * @version $Revision: 1.1 $ $Date: 2007/11/02 16:31:24 $
+ */
+public class ForwardAfterLoginTest extends TestBase {
+ /**
+ * Constructor
+ *
+ * @param name
+ */
+ public ForwardAfterLoginTest(String name) {
+ super(name);
+ }
+
+ public void testForwardAfterLogin()
+ throws Exception
+ {
+ // request the login page
+ WebConversation session = new WebConversation();
+ WebRequest request = new GetMethodWebRequest(baseUrl + "/loginForm.jsp");
+ WebResponse response = session.getResponse(request);
+
+ // make sure the response leads us to login page
+ assertPageTitle(Constants.LOGIN_TITLE, response);
+
+ // submit valid login credentials
+ WebForm loginForm = response.getFormWithID(Constants.LOGIN_FORM_ID);
+ loginForm.setParameter(Constants.LOGIN_USERNAME_FIELD, Constants.VALID_USERNAME);
+ loginForm.setParameter(Constants.LOGIN_PASSWORD_FIELD, Constants.VALID_PASSWORD);
+
+ loginForm.setParameter(FormAuthenticator.DEFAULT_FORWARD_PARAMETER_NAME, "/securePage.jsp");
+ loginForm.setParameter(FormAuthenticator.DEFAULT_FORWARD_MODE_PARAMETER_NAME, "forward");
+
+ // Disable automatic redirection so we can detect it ourselves.
+ session.getClientProperties().setAutoRedirect(false);
+
+ response = session.getResponse(loginForm.getRequest());
+
+ // make sure the response leads to the default page (Home page -- index.jsp)
+ assertPageTitle(Constants.SECURE_TITLE, response);
+ }
+
+ public void testRedirectAfterLogin()
+ throws Exception
+ {
+ String contextPath = baseUrl.substring(baseUrl.lastIndexOf('/'));
+
+ // request the login page
+ WebConversation session = new WebConversation();
+ WebRequest request = new GetMethodWebRequest(baseUrl + "/loginForm.jsp");
+ WebResponse response = session.getResponse(request);
+
+ // make sure the response leads us to login page
+ assertPageTitle(Constants.LOGIN_TITLE, response);
+
+ // submit valid login credentials
+ WebForm loginForm = response.getFormWithID(Constants.LOGIN_FORM_ID);
+ loginForm.setParameter(Constants.LOGIN_USERNAME_FIELD, Constants.VALID_USERNAME);
+ loginForm.setParameter(Constants.LOGIN_PASSWORD_FIELD, Constants.VALID_PASSWORD);
+
+ loginForm.setParameter(FormAuthenticator.DEFAULT_FORWARD_PARAMETER_NAME, contextPath + "/securePage.jsp");
+ loginForm.setParameter(FormAuthenticator.DEFAULT_FORWARD_MODE_PARAMETER_NAME, "redirect");
+
+ // Disable automatic redirection so we can detect it ourselves.
+ session.getClientProperties().setAutoRedirect(false);
+
+ response = session.getResponse(loginForm.getRequest());
+
+ String location = response.getHeaderField("Location");
+
+ // Remove any ";jsessionid" parameter.
+
+ if(0 <= location.indexOf(";jsessionid="))
+ location = location.replaceAll(";jsessionid=[a-fA-F0-9]+", "");
+
+ // Check for correct redirect (fully-qualified URL)
+ Assert.assertEquals(baseUrl + "/securePage.jsp",
+ location);
+ }
+
+ public void testRedirectParametersAfterLogin()
+ throws Exception
+ {
+ String contextPath = baseUrl.substring(baseUrl.lastIndexOf('/'));
+
+ // request the login page
+ WebConversation session = new WebConversation();
+ WebRequest request = new GetMethodWebRequest(baseUrl + FormAuthenticator.DEFAULT_LOGIN_SUBMIT_PATTERN);
+ WebResponse response;
+
+ request.setParameter(Constants.LOGIN_USERNAME_FIELD, Constants.VALID_USERNAME);
+ request.setParameter(Constants.LOGIN_PASSWORD_FIELD, Constants.VALID_PASSWORD);
+ request.setParameter(FormAuthenticator.DEFAULT_FORWARD_PARAMETER_NAME, contextPath + "/securePage.jsp");
+ request.setParameter(FormAuthenticator.DEFAULT_FORWARD_MODE_PARAMETER_NAME, "redirect");
+ request.setParameter(FormAuthenticator.DEFAULT_FORWARD_PARAMETERS_PARAMETER_NAME, "true");
+ request.setParameter("extra", "data");
+
+ // Disable automatic redirection so we can detect it ourselves.
+ session.getClientProperties().setAutoRedirect(false);
+
+ response = session.getResponse(request);
+
+ String location = response.getHeaderField("Location");
+
+ // Remove any ";jsessionid" parameter.
+
+ if(0 <= location.indexOf(";jsessionid="))
+ location = location.replaceAll(";jsessionid=[a-fA-F0-9]+", "");
+
+ // Check for correct redirect (fully-qualified URL)
+ Assert.assertEquals(baseUrl + "/securePage.jsp?extra=data",
+ location);
+ }
+
+ public void testRedirectNoParametersAfterLogin()
+ throws Exception
+ {
+ String contextPath = baseUrl.substring(baseUrl.lastIndexOf('/'));
+
+ // request the login page
+ WebConversation session = new WebConversation();
+ WebRequest request = new GetMethodWebRequest(baseUrl + FormAuthenticator.DEFAULT_LOGIN_SUBMIT_PATTERN);
+ WebResponse response;
+
+ request.setParameter(Constants.LOGIN_USERNAME_FIELD, Constants.VALID_USERNAME);
+ request.setParameter(Constants.LOGIN_PASSWORD_FIELD, Constants.VALID_PASSWORD);
+ request.setParameter(FormAuthenticator.DEFAULT_FORWARD_PARAMETER_NAME, contextPath + "/securePage.jsp");
+ request.setParameter(FormAuthenticator.DEFAULT_FORWARD_MODE_PARAMETER_NAME, "redirect");
+ request.setParameter(FormAuthenticator.DEFAULT_FORWARD_PARAMETERS_PARAMETER_NAME, "false");
+ request.setParameter("extra", "data");
+
+ // Disable automatic redirection so we can detect it ourselves.
+ session.getClientProperties().setAutoRedirect(false);
+
+ response = session.getResponse(request);
+
+ String location = response.getHeaderField("Location");
+
+ // Remove any ";jsessionid" parameter.
+
+ if(0 <= location.indexOf(";jsessionid="))
+ location = location.replaceAll(";jsessionid=[a-fA-F0-9]+", "");
+
+ // Check for correct redirect (fully-qualified URL)
+ Assert.assertEquals(baseUrl + "/securePage.jsp",
+ location);
+ }
+}