001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.catalina.authenticator;
018:
019: import java.io.IOException;
020: import java.security.Principal;
021: import java.util.Enumeration;
022: import java.util.Iterator;
023: import java.util.Locale;
024: import java.util.Map;
025:
026: import javax.servlet.RequestDispatcher;
027: import javax.servlet.http.Cookie;
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030:
031: import org.apache.catalina.HttpRequest;
032: import org.apache.catalina.HttpResponse;
033: import org.apache.catalina.Realm;
034: import org.apache.catalina.Session;
035: import org.apache.catalina.deploy.LoginConfig;
036: import org.apache.commons.logging.Log;
037: import org.apache.commons.logging.LogFactory;
038: import org.apache.tomcat.util.buf.CharChunk;
039: import org.apache.tomcat.util.buf.MessageBytes;
040:
041: /**
042: * An <b>Authenticator</b> and <b>Valve</b> implementation of FORM BASED
043: * Authentication, as described in the Servlet API Specification, Version 2.2.
044: *
045: * @author Craig R. McClanahan
046: * @author Remy Maucherat
047: * @version $Revision: 1.9 $ $Date: 2004/03/31 08:34:53 $
048: */
049:
050: public class FormAuthenticator extends AuthenticatorBase {
051: private static Log log = LogFactory.getLog(FormAuthenticator.class);
052:
053: // ----------------------------------------------------- Instance Variables
054:
055: /**
056: * Descriptive information about this implementation.
057: */
058: protected static final String info = "org.apache.catalina.authenticator.FormAuthenticator/1.0";
059:
060: // ------------------------------------------------------------- Properties
061:
062: /**
063: * Return descriptive information about this Valve implementation.
064: */
065: public String getInfo() {
066:
067: return (info);
068:
069: }
070:
071: // --------------------------------------------------------- Public Methods
072:
073: /**
074: * Authenticate the user making this request, based on the specified
075: * login configuration. Return <code>true</code> if any specified
076: * constraint has been satisfied, or <code>false</code> if we have
077: * created a response challenge already.
078: *
079: * @param request Request we are processing
080: * @param response Response we are creating
081: * @param config Login configuration describing how authentication
082: * should be performed
083: *
084: * @exception IOException if an input/output error occurs
085: */
086: public boolean authenticate(HttpRequest request,
087: HttpResponse response, LoginConfig config)
088: throws IOException {
089:
090: // References to objects we will need later
091: HttpServletRequest hreq = (HttpServletRequest) request
092: .getRequest();
093: HttpServletResponse hres = (HttpServletResponse) response
094: .getResponse();
095: Session session = null;
096:
097: // Have we already authenticated someone?
098: Principal principal = hreq.getUserPrincipal();
099: String ssoId = (String) request
100: .getNote(Constants.REQ_SSOID_NOTE);
101: if (principal != null) {
102: if (log.isDebugEnabled())
103: log.debug("Already authenticated '"
104: + principal.getName() + "'");
105: // Associate the session with any existing SSO session
106: if (ssoId != null)
107: associate(ssoId, getSession(request, true));
108: return (true);
109: }
110:
111: // Is there an SSO session against which we can try to reauthenticate?
112: if (ssoId != null) {
113: if (log.isDebugEnabled())
114: log.debug("SSO Id " + ssoId + " set; attempting "
115: + "reauthentication");
116: // Try to reauthenticate using data cached by SSO. If this fails,
117: // either the original SSO logon was of DIGEST or SSL (which
118: // we can't reauthenticate ourselves because there is no
119: // cached username and password), or the realm denied
120: // the user's reauthentication for some reason.
121: // In either case we have to prompt the user for a logon */
122: if (reauthenticateFromSSO(ssoId, request))
123: return true;
124: }
125:
126: // Have we authenticated this user before but have caching disabled?
127: if (!cache) {
128: session = getSession(request, true);
129: if (log.isDebugEnabled())
130: log.debug("Checking for reauthenticate in session "
131: + session);
132: String username = (String) session
133: .getNote(Constants.SESS_USERNAME_NOTE);
134: String password = (String) session
135: .getNote(Constants.SESS_PASSWORD_NOTE);
136: if ((username != null) && (password != null)) {
137: if (log.isDebugEnabled())
138: log.debug("Reauthenticating username '" + username
139: + "'");
140: principal = context.getRealm().authenticate(username,
141: password);
142: if (principal != null) {
143: session.setNote(Constants.FORM_PRINCIPAL_NOTE,
144: principal);
145: if (!matchRequest(request)) {
146: register(request, response, principal,
147: Constants.FORM_METHOD, username,
148: password);
149: return (true);
150: }
151: }
152: if (log.isDebugEnabled())
153: log
154: .debug("Reauthentication failed, proceed normally");
155: }
156: }
157:
158: // Is this the re-submit of the original request URI after successful
159: // authentication? If so, forward the *original* request instead.
160: if (matchRequest(request)) {
161: session = getSession(request, true);
162: if (log.isDebugEnabled())
163: log.debug("Restore request from session '"
164: + session.getId() + "'");
165: principal = (Principal) session
166: .getNote(Constants.FORM_PRINCIPAL_NOTE);
167: register(request, response, principal,
168: Constants.FORM_METHOD, (String) session
169: .getNote(Constants.SESS_USERNAME_NOTE),
170: (String) session
171: .getNote(Constants.SESS_PASSWORD_NOTE));
172: if (restoreRequest(request, session)) {
173: if (log.isDebugEnabled())
174: log.debug("Proceed to restored request");
175: return (true);
176: } else {
177: if (log.isDebugEnabled())
178: log.debug("Restore of original request failed");
179: hres.sendError(HttpServletResponse.SC_BAD_REQUEST);
180: return (false);
181: }
182: }
183:
184: // Acquire references to objects we will need to evaluate
185: MessageBytes uriMB = MessageBytes.newInstance();
186: CharChunk uriCC = uriMB.getCharChunk();
187: uriCC.setLimit(-1);
188: String contextPath = hreq.getContextPath();
189: String requestURI = request.getDecodedRequestURI();
190: response.setContext(request.getContext());
191:
192: // Is this the action request from the login page?
193: boolean loginAction = requestURI.startsWith(contextPath)
194: && requestURI.endsWith(Constants.FORM_ACTION);
195:
196: // No -- Save this request and redirect to the form login page
197: if (!loginAction) {
198: session = getSession(request, true);
199: if (log.isDebugEnabled())
200: log.debug("Save request in session '" + session.getId()
201: + "'");
202: saveRequest(request, session);
203: RequestDispatcher disp = context.getServletContext()
204: .getRequestDispatcher(config.getLoginPage());
205: try {
206: disp.forward(hreq, hres);
207: response.finishResponse();
208: } catch (Throwable t) {
209: log
210: .warn(
211: "Unexpected error forwarding to login page",
212: t);
213: }
214: return (false);
215: }
216:
217: // Yes -- Validate the specified credentials and redirect
218: // to the error page if they are not correct
219: Realm realm = context.getRealm();
220: String username = hreq.getParameter(Constants.FORM_USERNAME);
221: String password = hreq.getParameter(Constants.FORM_PASSWORD);
222: if (log.isDebugEnabled())
223: log.debug("Authenticating username '" + username + "'");
224: principal = realm.authenticate(username, password);
225: if (principal == null) {
226: RequestDispatcher disp = context.getServletContext()
227: .getRequestDispatcher(config.getErrorPage());
228: try {
229: disp.forward(hreq, hres);
230: } catch (Throwable t) {
231: log
232: .warn(
233: "Unexpected error forwarding to error page",
234: t);
235: }
236: return (false);
237: }
238:
239: if (log.isDebugEnabled())
240: log.debug("Authentication of '" + username
241: + "' was successful");
242:
243: if (session == null)
244: session = getSession(request, false);
245: if (session == null) {
246: if (debug >= 1)
247: log("User took so long to log on the session expired");
248: hres.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT, sm
249: .getString("authenticator.sessionExpired"));
250: return (false);
251: }
252:
253: // Save the authenticated Principal in our session
254: session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
255:
256: // If we are not caching, save the username and password as well
257: if (!cache) {
258: session.setNote(Constants.SESS_USERNAME_NOTE, username);
259: session.setNote(Constants.SESS_PASSWORD_NOTE, password);
260: }
261:
262: // Redirect the user to the original request URI (which will cause
263: // the original request to be restored)
264: requestURI = savedRequestURL(session);
265: if (log.isDebugEnabled())
266: log.debug("Redirecting to original '" + requestURI + "'");
267: if (requestURI == null)
268: hres.sendError(HttpServletResponse.SC_BAD_REQUEST, sm
269: .getString("authenticator.formlogin"));
270: else
271: hres.sendRedirect(hres.encodeRedirectURL(requestURI));
272: return (false);
273:
274: }
275:
276: // ------------------------------------------------------ Protected Methods
277:
278: /**
279: * Does this request match the saved one (so that it must be the redirect
280: * we signalled after successful authentication?
281: *
282: * @param request The request to be verified
283: */
284: protected boolean matchRequest(HttpRequest request) {
285:
286: // Has a session been created?
287: Session session = getSession(request, false);
288: if (session == null)
289: return (false);
290:
291: // Is there a saved request?
292: SavedRequest sreq = (SavedRequest) session
293: .getNote(Constants.FORM_REQUEST_NOTE);
294: if (sreq == null)
295: return (false);
296:
297: // Is there a saved principal?
298: if (session.getNote(Constants.FORM_PRINCIPAL_NOTE) == null)
299: return (false);
300:
301: // Does the request URI match?
302: HttpServletRequest hreq = (HttpServletRequest) request
303: .getRequest();
304: String requestURI = hreq.getRequestURI();
305: if (requestURI == null)
306: return (false);
307: return (requestURI.equals(sreq.getRequestURI()));
308:
309: }
310:
311: /**
312: * Restore the original request from information stored in our session.
313: * If the original request is no longer present (because the session
314: * timed out), return <code>false</code>; otherwise, return
315: * <code>true</code>.
316: *
317: * @param request The request to be restored
318: * @param session The session containing the saved information
319: */
320: protected boolean restoreRequest(HttpRequest request,
321: Session session) {
322:
323: // Retrieve and remove the SavedRequest object from our session
324: SavedRequest saved = (SavedRequest) session
325: .getNote(Constants.FORM_REQUEST_NOTE);
326: session.removeNote(Constants.FORM_REQUEST_NOTE);
327: session.removeNote(Constants.FORM_PRINCIPAL_NOTE);
328: if (saved == null)
329: return (false);
330:
331: // Modify our current request to reflect the original one
332: request.clearCookies();
333: Iterator cookies = saved.getCookies();
334: while (cookies.hasNext()) {
335: request.addCookie((Cookie) cookies.next());
336: }
337: request.clearHeaders();
338: Iterator names = saved.getHeaderNames();
339: while (names.hasNext()) {
340: String name = (String) names.next();
341: Iterator values = saved.getHeaderValues(name);
342: while (values.hasNext()) {
343: request.addHeader(name, (String) values.next());
344: }
345: }
346: request.clearLocales();
347: Iterator locales = saved.getLocales();
348: while (locales.hasNext()) {
349: request.addLocale((Locale) locales.next());
350: }
351: request.clearParameters();
352: if ("POST".equalsIgnoreCase(saved.getMethod())) {
353: Iterator paramNames = saved.getParameterNames();
354: while (paramNames.hasNext()) {
355: String paramName = (String) paramNames.next();
356: String paramValues[] = saved
357: .getParameterValues(paramName);
358: request.addParameter(paramName, paramValues);
359: }
360: }
361: request.setMethod(saved.getMethod());
362: request.setQueryString(saved.getQueryString());
363: request.setRequestURI(saved.getRequestURI());
364: return (true);
365:
366: }
367:
368: /**
369: * Save the original request information into our session.
370: *
371: * @param request The request to be saved
372: * @param session The session to contain the saved information
373: */
374: private void saveRequest(HttpRequest request, Session session) {
375:
376: // Create and populate a SavedRequest object for this request
377: HttpServletRequest hreq = (HttpServletRequest) request
378: .getRequest();
379: SavedRequest saved = new SavedRequest();
380: Cookie cookies[] = hreq.getCookies();
381: if (cookies != null) {
382: for (int i = 0; i < cookies.length; i++)
383: saved.addCookie(cookies[i]);
384: }
385: Enumeration names = hreq.getHeaderNames();
386: while (names.hasMoreElements()) {
387: String name = (String) names.nextElement();
388: Enumeration values = hreq.getHeaders(name);
389: while (values.hasMoreElements()) {
390: String value = (String) values.nextElement();
391: saved.addHeader(name, value);
392: }
393: }
394: Enumeration locales = hreq.getLocales();
395: while (locales.hasMoreElements()) {
396: Locale locale = (Locale) locales.nextElement();
397: saved.addLocale(locale);
398: }
399: Map parameters = hreq.getParameterMap();
400: Iterator paramNames = parameters.keySet().iterator();
401: while (paramNames.hasNext()) {
402: String paramName = (String) paramNames.next();
403: String paramValues[] = (String[]) parameters.get(paramName);
404: saved.addParameter(paramName, paramValues);
405: }
406: saved.setMethod(hreq.getMethod());
407: saved.setQueryString(hreq.getQueryString());
408: saved.setRequestURI(hreq.getRequestURI());
409:
410: // Stash the SavedRequest in our session for later use
411: session.setNote(Constants.FORM_REQUEST_NOTE, saved);
412:
413: }
414:
415: /**
416: * Return the request URI (with the corresponding query string, if any)
417: * from the saved request so that we can redirect to it.
418: *
419: * @param session Our current session
420: */
421: private String savedRequestURL(Session session) {
422:
423: SavedRequest saved = (SavedRequest) session
424: .getNote(Constants.FORM_REQUEST_NOTE);
425: if (saved == null)
426: return (null);
427: StringBuffer sb = new StringBuffer(saved.getRequestURI());
428: if (saved.getQueryString() != null) {
429: sb.append('?');
430: sb.append(saved.getQueryString());
431: }
432: return (sb.toString());
433:
434: }
435:
436: }
|