implemented BASIC authentication support and support for Servlet 2.3 public final...
authordayash <dayash>
Sat, 18 Jan 2003 07:18:08 +0000 (07:18 +0000)
committerdayash <dayash>
Sat, 18 Jan 2003 07:18:08 +0000 (07:18 +0000)
required for Orion 1.5.2 application server.

src/share/org/securityfilter/filter/SecurityFilter.java

index 2adbefc..a124f05 100644 (file)
@@ -1,7 +1,7 @@
 /*
- * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityFilter.java,v 1.14 2003/01/06 04:16:32 maxcooper Exp $
- * $Revision: 1.14 $
- * $Date: 2003/01/06 04:16:32 $
+ * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityFilter.java,v 1.15 2003/01/18 07:18:08 dayash Exp $
+ * $Revision: 1.15 $
+ * $Date: 2003/01/18 07:18:08 $
  *
  * ====================================================================
  * The SecurityFilter Software License, Version 1.1
@@ -60,6 +60,7 @@ import org.securityfilter.config.SecurityConfig;
 import org.securityfilter.config.SecurityConstraint;
 import org.securityfilter.config.WebResourceCollection;
 import org.securityfilter.realm.SecurityRealmInterface;
+import org.apache.catalina.util.Base64;
 
 import javax.servlet.*;
 import javax.servlet.http.HttpServletRequest;
@@ -74,8 +75,9 @@ import java.util.*;
  * SecurityFilter provides authentication and authorization services.
  *
  * @author Max Cooper (max@maxcooper.com)
+ * @author Daya Sharma (iamdaya@yahoo.com, billydaya@sbcglobal.net)
  * @author Torgeir Veimo (torgeir@pobox.com)
- * @version $Revision: 1.14 $ $Date: 2003/01/06 04:16:32 $
+ * @version $Revision: 1.15 $ $Date: 2003/01/18 07:18:08 $
  */
 public class SecurityFilter implements Filter {
    public static final String SAVED_REQUEST_URL = SecurityFilter.class.getName() + ".SAVED_REQUEST_URL";
@@ -86,6 +88,10 @@ public class SecurityFilter implements Filter {
    public static final String DEFAULT_CONFIG_FILE = "/WEB-INF/securityfilter-config.xml";
    public static final String VALIDATE_KEY = "validate";
    public static final String TRUE = "true";
+   public static final String BASIC_WINDOW_SHOWN = "basic_window_shown";
+   public static final String LOGIN_ATTEMPTS = "loginAttempts";
+
+   public static final Base64 base64Helper = new Base64();
 
    protected FilterConfig config;
    protected SecurityRealmInterface realm;
@@ -96,10 +102,15 @@ public class SecurityFilter implements Filter {
    protected String defaultPage;
    protected URLPatternFactory patternFactory;
    protected List patternList;
+   protected String authMethod;
+   protected boolean basic = false;
+   protected String tooManyInCorrectLogins;
 
+   protected static final String DUMMY_TOKEN = "dummyToken";
    protected static final String FORM_USERNAME = "j_username";
    protected static final String FORM_PASSWORD = "j_password";
-   protected static final String FORM_SUBMIT_URL = "/j_security_check";
+   protected static final String FORM_PATTERN = "formPattern";
+   protected String form_submit_url = "/j_security_check";
 
    /**
     * Perform filtering operation, and optionally pass the request down the chain.
@@ -111,10 +122,10 @@ public class SecurityFilter implements Filter {
     * @exception ServletException
     */
    public void doFilter(
-      ServletRequest request,
-      ServletResponse response,
-      FilterChain chain
-   ) throws IOException, ServletException {
+           ServletRequest request,
+           ServletResponse response,
+           FilterChain chain
+           ) throws IOException, ServletException {
 
       HttpServletRequest hReq = (HttpServletRequest) request;
       HttpServletResponse hRes = (HttpServletResponse) response;
@@ -140,9 +151,15 @@ public class SecurityFilter implements Filter {
          URLPattern match = null;
          try {
             // check if this is a login form submittal
-            if (requestURL.endsWith(FORM_SUBMIT_URL)) {
+            if (basicAuthentication(hReq)) {
+               hReq.getSession().removeAttribute(BASIC_WINDOW_SHOWN);
                processLogin(wrappedRequest, hRes);
                return;
+            } else {
+               if (requestURL.endsWith(form_submit_url)) {
+                  processLogin(wrappedRequest, hRes);
+                  return;
+               }
             }
 
             // only check the request for a security constraint match if it doesn't
@@ -150,8 +167,7 @@ public class SecurityFilter implements Filter {
             // login page and error pages to be viewed even when their URLs would otherwise
             // be subject to a security constraint
             if (!patternMatcher.match(requestURL, loginPagePattern)
-               && !patternMatcher.match(requestURL, loginPagePattern)
-            ) {
+                    && !patternMatcher.match(requestURL, loginPagePattern)) {
                // check if request matches security constraint
                match = matchPattern(requestURL, wrappedRequest.getMethod(), patternMatcher);
             }
@@ -168,7 +184,7 @@ public class SecurityFilter implements Filter {
                Collection roles = authConstraint.getRoles();
                Principal principal = wrappedRequest.getUserPrincipal();
                // if roles is empty, access will be blocked no matter who the user is, so skip the login
-               if (!roles.isEmpty() && principal == null) {
+               if (!roles.isEmpty() && principal == null && hReq.getSession().getAttribute(DUMMY_TOKEN) == null) {
                   // user needs to be authenticated
                   showLogin(hReq, hRes);
                   return;
@@ -199,6 +215,11 @@ public class SecurityFilter implements Filter {
       chain.doFilter(request, response);
    }
 
+   private boolean basicAuthentication(HttpServletRequest hReq) {
+      return authMethod.equalsIgnoreCase("basic") && hReq.getSession().getAttribute(BASIC_WINDOW_SHOWN) != null
+              && hReq.getHeader("Authorization") != null;
+   }
+
    /**
     * Initialize the SecurityFilter.
     *
@@ -214,6 +235,7 @@ public class SecurityFilter implements Filter {
          if (configFile == null) {
             configFile = DEFAULT_CONFIG_FILE;
          }
+         form_submit_url = config.getInitParameter(FORM_PATTERN);
          URL configURL = config.getServletContext().getResource(configFile);
          String validate = config.getInitParameter(VALIDATE_KEY);
          SecurityConfig securityConfig = new SecurityConfig(TRUE.equalsIgnoreCase(validate));
@@ -228,6 +250,10 @@ public class SecurityFilter implements Filter {
          loginPagePattern = patternFactory.createURLPattern(loginPage, null, null, 0);
          errorPage = securityConfig.getErrorPage();
          errorPagePattern = patternFactory.createURLPattern(errorPage, null, null, 0);
+         authMethod = securityConfig.getAuthMethod();
+         // todo: support DIGEST and CERT authentication schemes.
+         if (authMethod.equalsIgnoreCase(HttpServletRequest.BASIC_AUTH))
+            basic = true;
 
          // create pattern list
          patternList = new ArrayList();
@@ -239,10 +265,10 @@ public class SecurityFilter implements Filter {
                WebResourceCollection resourceCollection = (WebResourceCollection) rIter.next();
                for (Iterator pIter = resourceCollection.getURLPatterns().iterator(); pIter.hasNext();) {
                   URLPattern pattern = patternFactory.createURLPattern(
-                     (String) pIter.next(),
-                     constraint,
-                     resourceCollection,
-                     order++
+                          (String) pIter.next(),
+                          constraint,
+                          resourceCollection,
+                          order++
                   );
                   patternList.add(pattern);
                }
@@ -310,13 +336,44 @@ public class SecurityFilter implements Filter {
     * @exception ServletException
     */
    protected void showLogin(
-      HttpServletRequest request,
-      HttpServletResponse response
-   ) throws IOException, ServletException {
+           HttpServletRequest request,
+           HttpServletResponse response
+           ) throws IOException, ServletException {
       // save this request
       saveRequestInformation(request);
       // redirect to login page
-      response.sendRedirect(response.encodeRedirectURL(request.getContextPath() + loginPage));
+      request.getSession().setAttribute(BASIC_WINDOW_SHOWN, "shown");
+      int loginAttempts = 1;
+      if (request.getSession().getAttribute(LOGIN_ATTEMPTS) != null) {
+         loginAttempts = ((Integer) request.getSession().getAttribute(LOGIN_ATTEMPTS)).intValue();
+         loginAttempts += 1;
+      }
+      // todo: we can put some useful message here, perhaps a internationlizable format of message.
+      tooManyInCorrectLogins = "Sorry you are having problems logging in, please try again";
+      String loginAttemptMessage = "Login attempt number " + loginAttempts;
+      String logo;
+      if (basic) {
+         if (loginAttempts <= 3) {
+            String realm = String.valueOf(Math.random());
+            if (loginAttempts < 2) {
+               logo = "Basic Auth with Security Filter";
+            } else {
+               logo = loginAttemptMessage;
+            }
+            String blankLine = "                                                                   ";
+            logo = blankLine + blankLine + blankLine + logo + blankLine + blankLine;
+            System.err.println("response.setHeader \"WWW-Authenticate\", \"BASIC realm=\"" + logo + realm + "\"");
+            response.setHeader("WWW-Authenticate", "BASIC realm=\"" + realm + logo + "\"");
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            request.getSession().setAttribute(LOGIN_ATTEMPTS, new Integer(loginAttempts));
+         } else {
+            request.getSession().removeAttribute(LOGIN_ATTEMPTS);
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, tooManyInCorrectLogins);
+         }
+      } else {
+         response.sendRedirect(request.getContextPath() + loginPage);
+         return;
+      }
    }
 
    /**
@@ -328,30 +385,46 @@ public class SecurityFilter implements Filter {
     * @exception ServletException
     */
    protected void processLogin(
-      SecurityRequestWrapper request,
-      HttpServletResponse response
-   ) throws IOException, ServletException {
+           SecurityRequestWrapper request,
+           HttpServletResponse response
+           ) throws IOException, ServletException {
       String username = request.getParameter(FORM_USERNAME);
       String password = request.getParameter(FORM_PASSWORD);
+      if (basic && username == null && password == null) {
+         username = parseUsername(request.getHeader("Authorization"));
+         password = parsePassword(request.getHeader("Authorization"));
+      }
       Principal principal = realm.authenticate(username, password);
       if (principal != null) {
          // login successful
          // invalidate old session if the user was already authenticated
          // NOTE: we may want to check if the user re-authenticated as the same user, currently
          // the session will be invalidated even if the user authenticates as the same user.
-         if (request.getUserPrincipal() != null) {
-            request.getSession().invalidate();
-         }
          request.setUserPrincipal(principal);
          String continueToURL = getContinueToURL(request);
+         request.getSession().setAttribute(DUMMY_TOKEN, DUMMY_TOKEN);
+         // remove the saved request from the session.
+         // This is the url that the user was initially accessing before being prompted for login.
+         removeSavedRequest(request.getSession());
          response.sendRedirect(response.encodeRedirectURL(continueToURL));
       } else {
-         // login failed, set response status and forward to error page
-         response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-         request.getRequestDispatcher(errorPage).forward(request, response);
+         // login failed
+         if (!basic) {
+            // set response status and forward to error page
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            request.getRequestDispatcher(errorPage).forward(request, response);
+         } else {
+            // show the basic authentication window again.
+            showLogin(request.getCurrentRequest(), response);
+         }
       }
    }
 
+   private void removeSavedRequest(HttpSession session) {
+      session.removeAttribute(SecurityFilter.SAVED_REQUEST_URL);
+      session.removeAttribute(SecurityFilter.SAVED_REQUEST);
+   }
+
    /**
     * Get the URL to continue to after successful login. This may be the SAVED_REQUEST_URL if the authorization
     * sequence was initiated by the filter, or the default URL (as specified in the config file) if a login
@@ -392,11 +465,8 @@ public class SecurityFilter implements Filter {
       String savedURL = (String) session.getAttribute(SecurityFilter.SAVED_REQUEST_URL);
       if (savedURL != null && savedURL.equals(getSaveableURL(request))) {
          // this is a request for the request that caused the login,
-         // return the SavedRequest and remove it from the session
-         SavedRequest savedRequest = (SavedRequest) session.getAttribute(SecurityFilter.SAVED_REQUEST);
-         session.removeAttribute(SecurityFilter.SAVED_REQUEST_URL);
-         session.removeAttribute(SecurityFilter.SAVED_REQUEST);
-         return savedRequest;
+         // return the SavedRequest
+         return (SavedRequest) session.getAttribute(SecurityFilter.SAVED_REQUEST);
       } else {
          return null;
       }
@@ -431,7 +501,29 @@ public class SecurityFilter implements Filter {
     * @param request the request to construct a saveable URL for
     */
    private String getSaveableURL(HttpServletRequest request) {
-      StringBuffer saveableURL = request.getRequestURL();
+      String url = null;
+      StringBuffer saveableURL = null;
+      String protocol = request.getProtocol();
+      try {
+         saveableURL = request.getRequestURL();
+      } catch (NoSuchMethodError e) {
+         // this is done to support app servers like orion 1.5.2
+         // which have not implemented the servlet 2.3 specification but have implemented the final draft of 2.3 spec.
+         if (protocol.equals("HTTP/1.1")) { // todo: provide support for ftp, webdav protocol among others.
+            protocol = "http://";
+            if (request.isSecure())
+               protocol = "https://";
+         }
+         url = protocol + request.getServerName() + ":" + request.getServerPort() + request.getRequestURI();
+      }
+      if (saveableURL == null) {
+         saveableURL = new StringBuffer(url);
+      } else {
+         // since HTTP is the same regardless of whether it runs on TCP or on SSL/TCP
+         if (protocol.equals("HTTP/1.1") && request.isSecure() && saveableURL.toString().startsWith("http://")) {
+            saveableURL.replace(0, 4, "https"); // todo: this needs to be tested extensively.
+         }
+      }
       // add the query string, if any
       String queryString = request.getQueryString();
       if (queryString != null) {
@@ -439,6 +531,33 @@ public class SecurityFilter implements Filter {
       }
       return saveableURL.toString();
    }
+
+   private String parseUsername(String authorization) {
+
+      String unencoded = getdecodedString(authorization);
+      if (unencoded == null) return null;
+      int colon = unencoded.indexOf(':');
+      if (colon < 0) return (null);
+      return unencoded.substring(0, colon).trim();
+   }
+
+   private String getdecodedString(String authorization) {
+      if (authorization == null)
+         return (null);
+      if (!authorization.toLowerCase().startsWith("basic "))
+         return (null);
+      authorization = authorization.substring(6).trim();
+      // Decode and parse the authorization credentials
+      return new String(base64Helper.decode(authorization.getBytes()));
+   }
+
+   private String parsePassword(String authorization) {
+      String unencoded = getdecodedString(authorization);
+      if (unencoded == null) return null;
+      int colon = unencoded.indexOf(':');
+      if (colon < 0) return (null);
+      return unencoded.substring(colon + 1).trim();
+   }
 }
 
 // ------------------------------------------------------------------------