Author: markt
Date: Fri Jul 3 18:58:24 2015
New Revision: 1689060
URL: http://svn.apache.org/r1689060
Log:
Port FORM auth to JASPIC
Patch by fjodorver
Modified:
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/FormAuthModule.java
Modified:
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/FormAuthModule.java
URL:
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/FormAuthModule.java?rev=1689060&r1=1689059&r2=1689060&view=diff
==============================================================================
---
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/FormAuthModule.java
(original)
+++
tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/FormAuthModule.java
Fri Jul 3 18:58:24 2015
@@ -16,20 +16,47 @@
*/
package org.apache.catalina.authenticator.jaspic.provider.modules;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Locale;
import java.util.Map;
import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
+import javax.security.auth.message.callback.CallerPrincipalCallback;
+import javax.security.auth.message.callback.GroupPrincipalCallback;
+import javax.security.auth.message.callback.PasswordValidationCallback;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Context;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Session;
+import org.apache.catalina.authenticator.Constants;
+import org.apache.catalina.authenticator.SavedRequest;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.coyote.ActionCode;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.descriptor.web.LoginConfig;
+import org.apache.tomcat.util.http.MimeHeaders;
/**
* This class implements JASPIC FORM-based authentication.
@@ -61,8 +88,219 @@ public class FormAuthModule extends Tomc
if (!isMandatory(messageInfo)) {
return AuthStatus.SUCCESS;
}
- return AuthStatus.FAILURE;
+ try {
+ Request request = (Request) messageInfo.getRequestMessage();
+ HttpServletResponse response = (HttpServletResponse)
messageInfo.getResponseMessage();
+ // References to objects we will need later
+ Session session = null;
+ Principal principal = null;
+
+ // Have we authenticated this user before but have caching
disabled?
+ if (!isCache()) {
+ session = request.getSessionInternal(true);
+ if (log.isDebugEnabled()) {
+ log.debug("Checking for reauthenticate in session " +
session);
+ }
+ String username = (String)
session.getNote(Constants.SESS_USERNAME_NOTE);
+ String password = (String)
session.getNote(Constants.SESS_PASSWORD_NOTE);
+ if ((username != null) && (password != null)) {
+ if (log.isDebugEnabled()) {
+ log.debug("Reauthenticating username '" + username +
"'");
+ }
+ PasswordValidationCallback passwordCallback = new
PasswordValidationCallback(
+ clientSubject, username, password.toCharArray());
+ handler.handle(new Callback[] { passwordCallback });
+
+ if (!passwordCallback.getResult()) {
+ forwardToErrorPage(request, response,
context.getLoginConfig());
+ }
+ principal = getPrincipal(passwordCallback);
+ if (principal != null) {
+ session.setNote(Constants.FORM_PRINCIPAL_NOTE,
principal);
+ if (!matchRequest(request)) {
+ CallerPrincipalCallback principalCallback = new
CallerPrincipalCallback(
+ clientSubject, principal);
+ GroupPrincipalCallback groupCallback = new
GroupPrincipalCallback(
+ clientSubject,
context.getRealm().getRoles(principal));
+ handler.handle(new Callback[] { principalCallback,
groupCallback });
+ return AuthStatus.SUCCESS;
+ }
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Reauthentication failed, proceed normally");
+ }
+ }
+ }
+
+ // Is this the re-submit of the original request URI after
+ // successful
+ // authentication? If so, forward the *original* request instead.
+ if (matchRequest(request)) {
+ session = request.getSessionInternal(true);
+ if (log.isDebugEnabled()) {
+ log.debug("Restore request from session '" +
session.getIdInternal() + "'");
+ }
+ principal = (Principal)
session.getNote(Constants.FORM_PRINCIPAL_NOTE);
+ CallerPrincipalCallback principalCallback = new
CallerPrincipalCallback(
+ clientSubject, principal);
+ GroupPrincipalCallback groupCallback = new
GroupPrincipalCallback(clientSubject,
+ context.getRealm().getRoles(principal));
+ handler.handle(new Callback[] { principalCallback,
groupCallback });
+
+ // If we're caching principals we no longer needgetPrincipal
the
+ // username
+ // and password in the session, so remove them
+ if (isCache()) {
+ session.removeNote(Constants.SESS_USERNAME_NOTE);
+ session.removeNote(Constants.SESS_PASSWORD_NOTE);
+ }
+ if (restoreRequest(request, session)) {
+ if (log.isDebugEnabled()) {
+ log.debug("Proceed to restored request");
+ }
+ return AuthStatus.SUCCESS;
+ } else {
+ if (log.isDebugEnabled()) {
+ log.debug("Restore of original request failed");
+ }
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return AuthStatus.FAILURE;
+ }
+ }
+
+ // Acquire references to objects we will need to evaluate
+ MessageBytes uriMB = MessageBytes.newInstance();
+ CharChunk uriCC = uriMB.getCharChunk();
+ uriCC.setLimit(-1);
+ String contextPath = request.getContextPath();
+ String requestURI = request.getDecodedRequestURI();
+
+ // Is this the action request from the login page?
+ boolean loginAction = requestURI.startsWith(contextPath)
+ && requestURI.endsWith(Constants.FORM_ACTION);
+
+ LoginConfig config = context.getLoginConfig();
+
+ // No -- Save this request and redirect to the form login page
+ if (!loginAction) {
+ session = request.getSessionInternal(true);
+ if (log.isDebugEnabled()) {
+ log.debug("Save request in session '" +
session.getIdInternal() + "'");
+ }
+ try {
+ saveRequest(request, session);
+ } catch (IOException ioe) {
+ log.debug("Request body too big to save during
authentication");
+ response.sendError(HttpServletResponse.SC_FORBIDDEN,
+ sm.getString("authenticator.requestBodyTooBig"));
+ return AuthStatus.FAILURE;
+ }
+ forwardToLoginPage(request, response, config);
+ return AuthStatus.SEND_CONTINUE;
+ }
+
+ // Yes -- Acknowledge the request, validate the specified
+ // credentials
+ // and redirect to the error page if they are not correct
+ request.getResponse().sendAcknowledgement();
+ Realm realm = context.getRealm();
+ // TODO fix character encoding
+ // if (characterEncoding != null) {
+ // request.setCharacterEncoding(characterEncoding);
+ // }
+ String username = request.getParameter(Constants.FORM_USERNAME);
+ String password = request.getParameter(Constants.FORM_PASSWORD);
+ if (log.isDebugEnabled()) {
+ log.debug("Authenticating username '" + username + "'");
+ }
+ principal = realm.authenticate(username, password);
+ if (principal == null) {
+ forwardToErrorPage(request, response, config);
+ return AuthStatus.FAILURE;
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Authentication of '" + username + "' was
successful");
+ }
+
+ if (session == null) {
+ session = request.getSessionInternal(false);
+ }
+ if (session == null) {
+ // if (containerLog.isDebugEnabled()) {
+ // containerLog.debug
+ // ("User took so long to log on the session expired");
+ // }
+ if (landingPage == null) {
+ response.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT,
+ sm.getString("authenticator.sessionExpired"));
+ } else {
+ // Make the authenticator think the user originally
+ // requested
+ // the landing page
+ String uri = request.getContextPath() + landingPage;
+ SavedRequest saved = new SavedRequest();
+ saved.setMethod("GET");
+ saved.setRequestURI(uri);
+ saved.setDecodedRequestURI(uri);
+
request.getSessionInternal(true).setNote(Constants.FORM_REQUEST_NOTE, saved);
+ response.sendRedirect(response.encodeRedirectURL(uri));
+ }
+ return AuthStatus.FAILURE;
+ }
+
+ // Save the authenticated Principal in our session
+ session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
+
+ // Save the username and password as well
+ session.setNote(Constants.SESS_USERNAME_NOTE, username);
+ session.setNote(Constants.SESS_PASSWORD_NOTE, password);
+
+ // Redirect the user to the original request URI (which will cause
+ // the original request to be restored)
+ requestURI = savedRequestURL(session);
+ if (log.isDebugEnabled()) {
+ log.debug("Redirecting to original '" + requestURI + "'");
+ }
+ if (requestURI == null) {
+ if (landingPage == null) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+ sm.getString("authenticator.formlogin"));
+ } else {
+ // Make the authenticator think the user originally
+ // requested
+ // the landing page
+ String uri = request.getContextPath() + landingPage;
+ SavedRequest saved = new SavedRequest();
+ saved.setMethod("GET");
+ saved.setRequestURI(uri);
+ saved.setDecodedRequestURI(uri);
+ session.setNote(Constants.FORM_REQUEST_NOTE, saved);
+ response.sendRedirect(response.encodeRedirectURL(uri));
+ }
+ } else {
+ // Until the Servlet API allows specifying the type of redirect
+ // to
+ // use.
+ Response internalResponse = request.getResponse();
+ String location = response.encodeRedirectURL(requestURI);
+ if ("HTTP/1.1".equals(request.getProtocol())) {
+ internalResponse.sendRedirect(location,
HttpServletResponse.SC_SEE_OTHER);
+ } else {
+ internalResponse.sendRedirect(location,
HttpServletResponse.SC_FOUND);
+ }
+ }
+ return AuthStatus.FAILURE;
+ } catch (Exception e) {
+ throw new AuthException(e.getMessage());
+ }
+
+ }
+
+
+ private boolean isCache() {
+ return true;
}
@@ -82,4 +320,319 @@ public class FormAuthModule extends Tomc
public Class<?>[] getSupportedMessageTypes() {
return supportedMessageTypes;
}
+
+
+ private GenericPrincipal getPrincipal(PasswordValidationCallback
passwordCallback) {
+ Iterator<Object> credentials =
passwordCallback.getSubject().getPrivateCredentials()
+ .iterator();
+ return (GenericPrincipal) credentials.next();
+ }
+
+
+ /**
+ * Called to forward to the login page
+ *
+ * @param request Request we are processing
+ * @param response Response we are populating
+ * @param config Login configuration describing how authentication should
be
+ * performed
+ * @throws IOException If the forward to the login page fails and the call
+ * to {@link HttpServletResponse#sendError(int, String)} throws
+ * an {@link IOException}
+ */
+ protected void forwardToLoginPage(Request request, HttpServletResponse
response,
+ LoginConfig config) throws IOException {
+
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("formAuthenticator.forwardLogin",
request.getRequestURI(),
+ request.getMethod(), config.getLoginPage(),
context.getName()));
+ }
+
+ String loginPage = config.getLoginPage();
+ if (loginPage == null || loginPage.length() == 0) {
+ String msg = sm.getString("formAuthenticator.noLoginPage",
context.getName());
+ log.warn(msg);
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
msg);
+ return;
+ }
+
+ if (getChangeSessionIdOnAuthentication()) {
+ Session session = request.getSessionInternal(false);
+ if (session != null) {
+ Manager manager = request.getContext().getManager();
+ manager.changeSessionId(session);
+ request.changeSessionId(session.getId());
+ }
+ }
+
+ // Always use GET for the login page, regardless of the method used
+ String oldMethod = request.getMethod();
+ request.getCoyoteRequest().method().setString("GET");
+
+ RequestDispatcher disp =
context.getServletContext().getRequestDispatcher(loginPage);
+ try {
+ if (context.fireRequestInitEvent(request)) {
+ disp.forward(request.getRequest(), response);
+ context.fireRequestDestroyEvent(request);
+ }
+ } catch (Throwable t) {
+ ExceptionUtils.handleThrowable(t);
+ String msg = sm.getString("formAuthenticator.forwardLoginFail");
+ log.warn(msg, t);
+ request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
msg);
+ } finally {
+ // Restore original method so that it is written into access log
+ request.getCoyoteRequest().method().setString(oldMethod);
+ }
+ }
+
+
+ private boolean getChangeSessionIdOnAuthentication() {
+ return true; // FIXME
+ }
+
+
+ /**
+ * Called to forward to the error page
+ *
+ * @param request Request we are processing
+ * @param response Response we are populating
+ * @param config Login configuration describing how authentication should
be
+ * performed
+ * @throws IOException If the forward to the error page fails and the call
+ * to {@link HttpServletResponse#sendError(int, String)} throws
+ * an {@link IOException}
+ */
+ protected void forwardToErrorPage(Request request, HttpServletResponse
response,
+ LoginConfig config) throws IOException {
+
+ String errorPage = config.getErrorPage();
+ if (errorPage == null || errorPage.length() == 0) {
+ String msg = sm.getString("formAuthenticator.noErrorPage",
context.getName());
+ log.warn(msg);
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
msg);
+ return;
+ }
+
+ RequestDispatcher disp =
context.getServletContext().getRequestDispatcher(
+ config.getErrorPage());
+ try {
+ if (context.fireRequestInitEvent(request)) {
+ disp.forward(request.getRequest(), response);
+ context.fireRequestDestroyEvent(request);
+ }
+ } catch (Throwable t) {
+ ExceptionUtils.handleThrowable(t);
+ String msg = sm.getString("formAuthenticator.forwardErrorFail");
+ log.warn(msg, t);
+ request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
msg);
+ }
+ }
+
+
+ /**
+ * Does this request match the saved one (so that it must be the redirect
we
+ * signaled after successful authentication?
+ *
+ * @param request The request to be verified
+ */
+ protected boolean matchRequest(Request request) {
+ // Has a session been created?
+ Session session = request.getSessionInternal(false);
+ if (session == null) {
+ return false;
+ }
+
+ // Is there a saved request?
+ SavedRequest sreq = (SavedRequest)
session.getNote(Constants.FORM_REQUEST_NOTE);
+ if (sreq == null) {
+ return false;
+ }
+
+ // Is there a saved principal?
+ if (session.getNote(Constants.FORM_PRINCIPAL_NOTE) == null) {
+ return false;
+ }
+
+ // Does the request URI match?
+ String decodedRequestURI = request.getDecodedRequestURI();
+ if (decodedRequestURI == null) {
+ return false;
+ }
+ return decodedRequestURI.equals(sreq.getDecodedRequestURI());
+ }
+
+
+ /**
+ * Restore the original request from information stored in our session. If
+ * the original request is no longer present (because the session timed
+ * out), return <code>false</code>; otherwise, return <code>true</code>.
+ *
+ * @param request The request to be restored
+ * @param session The session containing the saved information
+ */
+ protected boolean restoreRequest(Request request, Session session) throws
IOException {
+
+ // Retrieve and remove the SavedRequest object from our session
+ SavedRequest saved = (SavedRequest)
session.getNote(Constants.FORM_REQUEST_NOTE);
+ session.removeNote(Constants.FORM_REQUEST_NOTE);
+ session.removeNote(Constants.FORM_PRINCIPAL_NOTE);
+ if (saved == null) {
+ return false;
+ }
+
+ // Swallow any request body since we will be replacing it
+ // Need to do this before headers are restored as AJP connector uses
+ // content length header to determine how much data needs to be read
for
+ // request body
+ byte[] buffer = new byte[4096];
+ InputStream is = request.createInputStream();
+ while (is.read(buffer) >= 0) {
+ // Ignore request body
+ }
+
+ // Modify our current request to reflect the original one
+ request.clearCookies();
+ Iterator<Cookie> cookies = saved.getCookies();
+ while (cookies.hasNext()) {
+ request.addCookie(cookies.next());
+ }
+
+ String method = saved.getMethod();
+ MimeHeaders rmh = request.getCoyoteRequest().getMimeHeaders();
+ rmh.recycle();
+ boolean cachable = "GET".equalsIgnoreCase(method) ||
"HEAD".equalsIgnoreCase(method);
+ Iterator<String> names = saved.getHeaderNames();
+ while (names.hasNext()) {
+ String name = names.next();
+ // The browser isn't expecting this conditional response now.
+ // Assuming that it can quietly recover from an unexpected 412.
+ // BZ 43687
+ if (!("If-Modified-Since".equalsIgnoreCase(name) || (cachable &&
"If-None-Match"
+ .equalsIgnoreCase(name)))) {
+ Iterator<String> values = saved.getHeaderValues(name);
+ while (values.hasNext()) {
+ rmh.addValue(name).setString(values.next());
+ }
+ }
+ }
+
+ request.clearLocales();
+ Iterator<Locale> locales = saved.getLocales();
+ while (locales.hasNext()) {
+ request.addLocale(locales.next());
+ }
+
+ request.getCoyoteRequest().getParameters().recycle();
+ request.getCoyoteRequest().getParameters()
+
.setQueryStringEncoding(request.getConnector().getURIEncoding());
+
+ ByteChunk body = saved.getBody();
+
+ if (body != null) {
+ request.getCoyoteRequest().action(ActionCode.REQ_SET_BODY_REPLAY,
body);
+
+ // Set content type
+ MessageBytes contentType = MessageBytes.newInstance();
+
+ // If no content type specified, use default for POST
+ String savedContentType = saved.getContentType();
+ if (savedContentType == null && "POST".equalsIgnoreCase(method)) {
+ savedContentType = "application/x-www-form-urlencoded";
+ }
+
+ contentType.setString(savedContentType);
+ request.getCoyoteRequest().setContentType(contentType);
+ }
+
+ request.getCoyoteRequest().method().setString(method);
+
+ return true;
+ }
+
+
+ /**
+ * Save the original request information into our session.
+ *
+ * @param request The request to be saved
+ * @param session The session to contain the saved information
+ * @throws IOException
+ */
+ protected void saveRequest(Request request, Session session) throws
IOException {
+
+ // Create and populate a SavedRequest object for this request
+ SavedRequest saved = new SavedRequest();
+ Cookie cookies[] = request.getCookies();
+ if (cookies != null) {
+ for (int i = 0; i < cookies.length; i++) {
+ saved.addCookie(cookies[i]);
+ }
+ }
+ Enumeration<String> names = request.getHeaderNames();
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ Enumeration<String> values = request.getHeaders(name);
+ while (values.hasMoreElements()) {
+ String value = values.nextElement();
+ saved.addHeader(name, value);
+ }
+ }
+ Enumeration<Locale> locales = request.getLocales();
+ while (locales.hasMoreElements()) {
+ Locale locale = locales.nextElement();
+ saved.addLocale(locale);
+ }
+
+ // May need to acknowledge a 100-continue expectation
+ request.getResponse().sendAcknowledgement();
+
+ ByteChunk body = new ByteChunk();
+ body.setLimit(request.getConnector().getMaxSavePostSize());
+
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ InputStream is = request.getInputStream();
+
+ while ((bytesRead = is.read(buffer)) >= 0) {
+ body.append(buffer, 0, bytesRead);
+ }
+
+ // Only save the request body if there is something to save
+ if (body.getLength() > 0) {
+ saved.setContentType(request.getContentType());
+ saved.setBody(body);
+ }
+
+ saved.setMethod(request.getMethod());
+ saved.setQueryString(request.getQueryString());
+ saved.setRequestURI(request.getRequestURI());
+ saved.setDecodedRequestURI(request.getDecodedRequestURI());
+
+ // Stash the SavedRequest in our session for later use
+ session.setNote(Constants.FORM_REQUEST_NOTE, saved);
+ }
+
+
+ /**
+ * Return the request URI (with the corresponding query string, if any)
from
+ * the saved request so that we can redirect to it.
+ *
+ * @param session Our current session
+ */
+ protected String savedRequestURL(Session session) {
+
+ SavedRequest saved = (SavedRequest)
session.getNote(Constants.FORM_REQUEST_NOTE);
+ if (saved == null) {
+ return (null);
+ }
+ StringBuilder sb = new StringBuilder(saved.getRequestURI());
+ if (saved.getQueryString() != null) {
+ sb.append('?');
+ sb.append(saved.getQueryString());
+ }
+ return (sb.toString());
+
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]