From 5f8057835f1bc220f280d0bf9c050dabc559443d Mon Sep 17 00:00:00 2001 From: dayash Date: Sat, 18 Jan 2003 07:18:08 +0000 Subject: [PATCH] implemented BASIC authentication support and support for Servlet 2.3 public final draft required for Orion 1.5.2 application server. --- .../org/securityfilter/filter/SecurityFilter.java | 191 +++++++++++++++++---- 1 file changed, 155 insertions(+), 36 deletions(-) diff --git a/src/share/org/securityfilter/filter/SecurityFilter.java b/src/share/org/securityfilter/filter/SecurityFilter.java index 2adbefc..a124f05 100644 --- a/src/share/org/securityfilter/filter/SecurityFilter.java +++ b/src/share/org/securityfilter/filter/SecurityFilter.java @@ -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(); + } } // ------------------------------------------------------------------------ -- 2.11.0