/*
* Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
* Distributed under the terms of either:
* - the common development and distribution license (CDDL), v1.0; or
* - the GNU Lesser General Public License, v2.1 or later
*/
package winstone.auth;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.w3c.dom.Node;
import winstone.AuthenticationPrincipal;
import winstone.AuthenticationRealm;
import winstone.Logger;
import winstone.WebAppConfiguration;
import winstone.WinstoneRequest;
/**
* Handles FORM based authentication configurations. Fairly simple ... it just
* redirects any unauthorized requests to the login page, and any bad logins to
* the error page. The auth values are stored in the session in a special slot.
*
* @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
* @version $Id: FormAuthenticationHandler.java,v 1.7 2006/12/13 14:07:43 rickknowles Exp $
*/
public class FormAuthenticationHandler extends BaseAuthenticationHandler {
private static final String ELEM_FORM_LOGIN_CONFIG = "form-login-config";
private static final String ELEM_FORM_LOGIN_PAGE = "form-login-page";
private static final String ELEM_FORM_ERROR_PAGE = "form-error-page";
private static final String FORM_ACTION = "j_security_check";
private static final String FORM_USER = "j_username";
private static final String FORM_PASS = "j_password";
private static final String AUTHENTICATED_USER = "winstone.auth.FormAuthenticationHandler.AUTHENTICATED_USER";
private static final String CACHED_REQUEST = "winstone.auth.FormAuthenticationHandler.CACHED_REQUEST";
private String loginPage;
private String errorPage;
/**
* Constructor for the FORM authenticator
*
* @param realm
* The realm against which we are authenticating
* @param constraints
* The array of security constraints that might apply
* @param resources
* The list of resource strings for messages
* @param realmName
* The name of the realm this handler claims
*/
public FormAuthenticationHandler(Node loginConfigNode,
List constraintNodes, Set rolesAllowed,
AuthenticationRealm realm) {
super(loginConfigNode, constraintNodes, rolesAllowed, realm);
for (int n = 0; n < loginConfigNode.getChildNodes().getLength(); n++) {
Node loginElm = loginConfigNode.getChildNodes().item(n);
if (loginElm.getNodeName().equals(ELEM_FORM_LOGIN_CONFIG)) {
for (int k = 0; k < loginElm.getChildNodes().getLength(); k++) {
Node formElm = loginElm.getChildNodes().item(k);
if (formElm.getNodeType() != Node.ELEMENT_NODE)
continue;
else if (formElm.getNodeName().equals(ELEM_FORM_LOGIN_PAGE))
loginPage = WebAppConfiguration.getTextFromNode(formElm);
else if (formElm.getNodeName().equals(ELEM_FORM_ERROR_PAGE))
errorPage = WebAppConfiguration.getTextFromNode(formElm);
}
}
}
Logger.log(Logger.DEBUG, AUTH_RESOURCES,
"FormAuthenticationHandler.Initialised", realmName);
}
/**
* Evaluates any authentication constraints, intercepting if auth is
* required. The relevant authentication handler subclass's logic is used to
* actually authenticate.
*
* @return A boolean indicating whether to continue after this request
*/
public boolean processAuthentication(ServletRequest request,
ServletResponse response, String pathRequested) throws IOException,
ServletException {
if (pathRequested.equals(this.loginPage)
|| pathRequested.equals(this.errorPage)) {
return true;
} else {
return super.processAuthentication(request, response, pathRequested);
}
}
/**
* Call this once we know that we need to authenticate
*/
protected void requestAuthentication(HttpServletRequest request,
HttpServletResponse response, String pathRequested)
throws ServletException, IOException {
// Save the critical details of the request into the session map
ServletRequest unwrapped = request;
while (unwrapped instanceof HttpServletRequestWrapper) {
unwrapped = ((HttpServletRequestWrapper) unwrapped).getRequest();
}
HttpSession session = request.getSession(true);
session.setAttribute(CACHED_REQUEST, new RetryRequestParams(unwrapped));
// Forward on to the login page
Logger.log(Logger.FULL_DEBUG, AUTH_RESOURCES,
"FormAuthenticationHandler.GoToLoginPage");
javax.servlet.RequestDispatcher rdLogin = request
.getRequestDispatcher(this.loginPage);
setNoCache(response);
rdLogin.forward(request, response);
}
/**
* Check the response - is it a response to the login page ?
*
* @return A boolean indicating whether to continue with the request or not
*/
protected boolean validatePossibleAuthenticationResponse(
HttpServletRequest request, HttpServletResponse response,
String pathRequested) throws ServletException, IOException {
// Check if this is a j_security_check uri
if (pathRequested.endsWith(FORM_ACTION)) {
String username = request.getParameter(FORM_USER);
String password = request.getParameter(FORM_PASS);
// Send to error page if invalid
AuthenticationPrincipal principal = this.realm
.authenticateByUsernamePassword(username, password);
if (principal == null) {
javax.servlet.RequestDispatcher rdError = request
.getRequestDispatcher(this.errorPage);
rdError.forward(request, response);
}
// Send to stashed request
else {
// Iterate back as far as we can
ServletRequest wrapperCheck = request;
while (wrapperCheck instanceof HttpServletRequestWrapper) {
wrapperCheck = ((HttpServletRequestWrapper) wrapperCheck).getRequest();
}
// Get the stashed request
WinstoneRequest actualRequest = null;
if (wrapperCheck instanceof WinstoneRequest) {
actualRequest = (WinstoneRequest) wrapperCheck;
actualRequest.setRemoteUser(principal);
} else {
Logger.log(Logger.WARNING, AUTH_RESOURCES,
"FormAuthenticationHandler.CantSetUser",
wrapperCheck.getClass().getName());
}
HttpSession session = request.getSession(true);
String previousLocation = this.loginPage;
RetryRequestParams cachedRequest = (RetryRequestParams)
session.getAttribute(CACHED_REQUEST);
if ((cachedRequest != null) && (actualRequest != null)) {
// Repopulate this request from the params we saved
request = new RetryRequestWrapper(request, cachedRequest);
previousLocation =
(request.getServletPath() == null ? "" : request.getServletPath()) +
(request.getPathInfo() == null ? "" : request.getPathInfo());
} else {
Logger.log(Logger.DEBUG, AUTH_RESOURCES,
"FormAuthenticationHandler.NoCachedRequest");
}
// do role check, since we don't know that this user has permission
if (doRoleCheck(request, response, previousLocation)) {
principal.setAuthType(HttpServletRequest.FORM_AUTH);
session.setAttribute(AUTHENTICATED_USER, principal);
javax.servlet.RequestDispatcher rdPrevious = request
.getRequestDispatcher(previousLocation);
rdPrevious.forward(request, response);
} else {
javax.servlet.RequestDispatcher rdError = request
.getRequestDispatcher(this.errorPage);
rdError.forward(request, response);
}
}
return false;
}
// If it's not a login, get the session, and look up the auth user variable
else {
WinstoneRequest actualRequest = null;
if (request instanceof WinstoneRequest) {
actualRequest = (WinstoneRequest) request;
} else if (request instanceof HttpServletRequestWrapper) {
HttpServletRequestWrapper wrapper = (HttpServletRequestWrapper) request;
if (wrapper.getRequest() instanceof WinstoneRequest) {
actualRequest = (WinstoneRequest) wrapper.getRequest();
} else {
Logger.log(Logger.WARNING, AUTH_RESOURCES,
"FormAuthenticationHandler.CantSetUser", wrapper
.getRequest().getClass().getName());
}
} else {
Logger.log(Logger.WARNING, AUTH_RESOURCES,
"FormAuthenticationHandler.CantSetUser", request
.getClass().getName());
}
HttpSession session = actualRequest.getSession(false);
if (session != null) {
AuthenticationPrincipal authenticatedUser = (AuthenticationPrincipal)
session.getAttribute(AUTHENTICATED_USER);
if (authenticatedUser != null) {
actualRequest.setRemoteUser(authenticatedUser);
Logger.log(Logger.FULL_DEBUG, AUTH_RESOURCES,
"FormAuthenticationHandler.GotUserFromSession");
}
}
return true;
}
}
}