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