From: maxcooper Date: Mon, 7 Jul 2003 13:12:56 +0000 (+0000) Subject: refactored BASIC and FORM authentication functionality out of SecurityFilter and... X-Git-Tag: rel-2_0-alpha1~56 X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=3cfbdc7ede37009e53c1873878bed3390f62e8bb;p=securityfilter.git refactored BASIC and FORM authentication functionality out of SecurityFilter and into their own classes FORM based authentication is tested and functional, will test BASIC and make any needed fixes soon --- diff --git a/src/share/org/securityfilter/authenticator/Authenticator.java b/src/share/org/securityfilter/authenticator/Authenticator.java new file mode 100644 index 0000000..05b7150 --- /dev/null +++ b/src/share/org/securityfilter/authenticator/Authenticator.java @@ -0,0 +1,124 @@ +/* + * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/authenticator/Authenticator.java,v 1.1 2003/07/07 13:12:56 maxcooper Exp $ + * $Revision: 1.1 $ + * $Date: 2003/07/07 13:12:56 $ + * + * ==================================================================== + * The SecurityFilter Software License, Version 1.1 + * + * (this license is derived and fully compatible with the Apache Software + * License - see http://www.apache.org/LICENSE.txt) + * + * Copyright (c) 2002 SecurityFilter.org. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * SecurityFilter.org (http://www.securityfilter.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The name "SecurityFilter" must not be used to endorse or promote + * products derived from this software without prior written permission. + * For written permission, please contact license@securityfilter.org . + * + * 5. Products derived from this software may not be called "SecurityFilter", + * nor may "SecurityFilter" appear in their name, without prior written + * permission of SecurityFilter.org. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE SECURITY FILTER PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.securityfilter.authenticator; + +import org.securityfilter.config.SecurityConfig; +import org.securityfilter.filter.*; + +import javax.servlet.FilterConfig; +import javax.servlet.http.*; +import java.io.IOException; + +/** + * Authenticator - interface for a SecurityFilter authenticator module. Implementations correspond to an implementation + * method, such as FORM or BASIC (others are possible). + * + * @author Max Cooper (max@maxcooper.com) + * @version $Revision: 1.1 $ $Date: 2003/07/07 13:12:56 $ + */ +public interface Authenticator { + + /** + * Initialize this Authenticator. + * + * @param filterConfig + * @param securityConfig + */ + public void init(FilterConfig filterConfig, SecurityConfig securityConfig) throws Exception; + + /** + * Get the auth method string for this authentication scheme. + * + * @return the auth method string for this Authenticator + */ + public String getAuthMethod(); + + /** + * Process any login information that was included in the request, if any. + * Returns true if SecurityFilter should abort further processing after the method completes (for example, if a + * redirect was sent as part of the login processing). + * + * @param request + * @param response + * @return true if the filter should return after this method ends, false otherwise + */ + public boolean processLogin(SecurityRequestWrapper request, HttpServletResponse response) throws Exception; + + /** + * Show the login interface. + * + * @param request + * @param response + */ + public void showLogin(HttpServletRequest request, HttpServletResponse response) throws IOException; + + /** + * Return true if security checks should be bypassed for this request. + * + * Example: for FORM based authentication, the login and error pages should always be viewable without being + * authenticated, even if they would otherwise be blocked by a security constraint. + * + * @param request + * @return true if security should be bypassed, false otherwise + */ + public boolean bypassSecurityForThisRequest( + SecurityRequestWrapper request, + URLPatternMatcher patternMatcher + ) throws Exception; +} + +// ------------------------------------------------------------------------ +// EOF diff --git a/src/share/org/securityfilter/authenticator/AuthenticatorFactory.java b/src/share/org/securityfilter/authenticator/AuthenticatorFactory.java new file mode 100644 index 0000000..1c2f814 --- /dev/null +++ b/src/share/org/securityfilter/authenticator/AuthenticatorFactory.java @@ -0,0 +1,104 @@ +/* + * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/authenticator/AuthenticatorFactory.java,v 1.1 2003/07/07 13:12:56 maxcooper Exp $ + * $Revision: 1.1 $ + * $Date: 2003/07/07 13:12:56 $ + * + * ==================================================================== + * The SecurityFilter Software License, Version 1.1 + * + * (this license is derived and fully compatible with the Apache Software + * License - see http://www.apache.org/LICENSE.txt) + * + * Copyright (c) 2002 SecurityFilter.org. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * SecurityFilter.org (http://www.securityfilter.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The name "SecurityFilter" must not be used to endorse or promote + * products derived from this software without prior written permission. + * For written permission, please contact license@securityfilter.org . + * + * 5. Products derived from this software may not be called "SecurityFilter", + * nor may "SecurityFilter" appear in their name, without prior written + * permission of SecurityFilter.org. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE SECURITY FILTER PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.securityfilter.authenticator; + +import org.securityfilter.config.SecurityConfig; + +import javax.servlet.FilterConfig; +import javax.servlet.http.HttpServletRequest; + +/** + * AuthenticatorFactory - this class will create Authenticator instance. + * + * It is designed to be easy to extend to add more Authenticator implementations, or to allow custom Authenticators to + * be specified in the config file and created here in this factory class. + * + * @author Max Cooper (max@maxcooper.com) + * @version $Revision: 1.1 $ $Date: 2003/07/07 13:12:56 $ + */ +public class AuthenticatorFactory { + + /** + * Create an Authenticator based on the specified configuration information. + * + * @param filterConfig + * @param securityConfig + * @return + * @throws Exception + */ + public static Authenticator createAuthenticator( + FilterConfig filterConfig, + SecurityConfig securityConfig + ) throws Exception { + Authenticator authenticator = null; + + String authMethod = securityConfig.getAuthMethod(); + if (HttpServletRequest.FORM_AUTH.equals(authMethod)) { + // FORM + authenticator = new FormAuthenticator(); + } else if (HttpServletRequest.BASIC_AUTH.equals(authMethod)) { + // BASIC + authenticator = new BasicAuthenticator(); + } else { + throw new Exception("No Authenticator available for auth method: " + authMethod); + } + authenticator.init(filterConfig, securityConfig); + return authenticator; + } +} + +// ------------------------------------------------------------------------ +// EOF diff --git a/src/share/org/securityfilter/authenticator/BasicAuthenticator.java b/src/share/org/securityfilter/authenticator/BasicAuthenticator.java new file mode 100644 index 0000000..7eb43fe --- /dev/null +++ b/src/share/org/securityfilter/authenticator/BasicAuthenticator.java @@ -0,0 +1,258 @@ +/* + * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/authenticator/BasicAuthenticator.java,v 1.1 2003/07/07 13:12:56 maxcooper Exp $ + * $Revision: 1.1 $ + * $Date: 2003/07/07 13:12:56 $ + * + * ==================================================================== + * The SecurityFilter Software License, Version 1.1 + * + * (this license is derived and fully compatible with the Apache Software + * License - see http://www.apache.org/LICENSE.txt) + * + * Copyright (c) 2002 SecurityFilter.org. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * SecurityFilter.org (http://www.securityfilter.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The name "SecurityFilter" must not be used to endorse or promote + * products derived from this software without prior written permission. + * For written permission, please contact license@securityfilter.org . + * + * 5. Products derived from this software may not be called "SecurityFilter", + * nor may "SecurityFilter" appear in their name, without prior written + * permission of SecurityFilter.org. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE SECURITY FILTER PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.securityfilter.authenticator; + +import org.apache.commons.codec.binary.Base64; +import org.securityfilter.config.SecurityConfig; +import org.securityfilter.filter.*; +import org.securityfilter.realm.SecurityRealmInterface; + +import javax.servlet.FilterConfig; +import javax.servlet.http.*; +import java.io.IOException; +import java.security.Principal; + +/** + * BasicAuthenticator - authenticator implementation for the BASIC auth method. + * + * @author Daya Sharma (iamdaya@yahoo.com, billydaya@sbcglobal.net) + * @author Max Cooper (max@maxcooper.com) + * @version $Revision: 1.1 $ $Date: 2003/07/07 13:12:56 $ + */ +public class BasicAuthenticator implements Authenticator { + public static final String BASIC_WINDOW_SHOWN = "basic_window_shown"; + public static final String LOGIN_ATTEMPTS = "loginAttempts"; + protected static final String DUMMY_TOKEN = "dummyToken"; + + public static final Base64 base64Helper = new Base64(); + + protected String tooManyIncorrectLogins; + + protected SecurityRealmInterface realm; + + /** + * Initialize this Authenticator. + * + * @param filterConfig + * @param securityConfig + */ + public void init(FilterConfig filterConfig, SecurityConfig securityConfig) throws Exception { + realm = securityConfig.getRealm(); + tooManyIncorrectLogins = "Sorry you are having problems logging in, please try again"; + } + + /** + * Returns BASIC as the authentication method. + * + * @return BASIC + */ + public String getAuthMethod() { + return HttpServletRequest.BASIC_AUTH; + } + + /** + * Process any login information that was included in the request, if any. + * Returns true if SecurityFilter should abort further processing after the method completes (for example, if a + * redirect was sent as part of the login processing). + * + * @param request + * @param response + * @return true if the filter should return after this method ends, false otherwise + */ + public boolean processLogin(SecurityRequestWrapper request, HttpServletResponse response) throws Exception { + if (basicAuthentication(request)) { + request.getSession().removeAttribute(BASIC_WINDOW_SHOWN); + String username = parseUsername(request.getHeader("Authorization")); + String 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. + request.setUserPrincipal(principal); + String continueToURL = SecurityFilter.getContinueToURL(request); + request.getSession().setAttribute(DUMMY_TOKEN, DUMMY_TOKEN); + // This is the url that the user was initially accessing before being prompted for login. + response.sendRedirect(response.encodeRedirectURL(continueToURL)); + } else { + // login failed + // show the basic authentication window again. + showLogin(request.getCurrentRequest(), response); + } + } + return false; + } + + /** + * Returns true if this request includes BASIC auth info. + * + * @param request + * @return + */ + private boolean basicAuthentication(HttpServletRequest request) { + return ( + request.getSession().getAttribute(BASIC_WINDOW_SHOWN) != null + && request.getHeader("Authorization") != null + ); + } + + /** + * Parse the username out of the BASIC authorization header string. + * @param authorization + * @return + */ + private String parseUsername(String authorization) { + String unencoded = decodeBasicAuthorizationString(authorization); + if (unencoded == null) { + return null; + } else { + int colon = unencoded.indexOf(':'); + if (colon < 0) { + return null; + } else { + return unencoded.substring(0, colon).trim(); + } + } + } + + /** + * Parse the password out of the BASIC authorization header string. + * @param authorization + * @return + */ + private String parsePassword(String authorization) { + String unencoded = decodeBasicAuthorizationString(authorization); + if (unencoded == null) { + return null; + } else { + int colon = unencoded.indexOf(':'); + if (colon < 0) { + return (null); + } else { + return unencoded.substring(colon + 1).trim(); + } + } + } + + /** + * Decode the BASIC authorization string. + * + * @param authorization + * @return + */ + private String decodeBasicAuthorizationString(String authorization) { + if (authorization == null || !authorization.toLowerCase().startsWith("basic ")) { + return null; + } else { + authorization = authorization.substring(6).trim(); + // Decode and parse the authorization credentials + return new String(base64Helper.decodeBase64(authorization.getBytes())); + } + } + + /** + * Show the login page. + * + * @param request the current request + * @param response the current response + */ + public void showLogin(HttpServletRequest request, HttpServletResponse response) throws IOException { + // save this request + SecurityFilter.saveRequestInformation(request); + + // redirect to login page + 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. + String loginAttemptMessage = "Login attempt number " + loginAttempts; + String logo; + 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); + } + } + + /** + * All requests should be subject to security checking for BASIC authentication. + * + * @param request + * @return always false -- check all requests + */ + public boolean bypassSecurityForThisRequest(SecurityRequestWrapper request, URLPatternMatcher patternMatcher) { + return false; + } +} + +// ------------------------------------------------------------------------ +// EOF diff --git a/src/share/org/securityfilter/authenticator/FormAuthenticator.java b/src/share/org/securityfilter/authenticator/FormAuthenticator.java new file mode 100644 index 0000000..7b4204a --- /dev/null +++ b/src/share/org/securityfilter/authenticator/FormAuthenticator.java @@ -0,0 +1,215 @@ +/* + * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/authenticator/FormAuthenticator.java,v 1.1 2003/07/07 13:12:56 maxcooper Exp $ + * $Revision: 1.1 $ + * $Date: 2003/07/07 13:12:56 $ + * + * ==================================================================== + * The SecurityFilter Software License, Version 1.1 + * + * (this license is derived and fully compatible with the Apache Software + * License - see http://www.apache.org/LICENSE.txt) + * + * Copyright (c) 2002 SecurityFilter.org. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * SecurityFilter.org (http://www.securityfilter.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The name "SecurityFilter" must not be used to endorse or promote + * products derived from this software without prior written permission. + * For written permission, please contact license@securityfilter.org . + * + * 5. Products derived from this software may not be called "SecurityFilter", + * nor may "SecurityFilter" appear in their name, without prior written + * permission of SecurityFilter.org. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE SECURITY FILTER PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.securityfilter.authenticator; + +import org.securityfilter.config.SecurityConfig; +import org.securityfilter.filter.*; +import org.securityfilter.realm.SecurityRealmInterface; + +import javax.servlet.FilterConfig; +import javax.servlet.http.*; +import java.io.IOException; +import java.security.Principal; + +/** + * FormAuthenticator - authenticator implementation for the FORM auth method. + * + * @author Max Cooper (max@maxcooper.com) + * @version $Revision: 1.1 $ $Date: 2003/07/07 13:12:56 $ + */ +public class FormAuthenticator implements Authenticator { + + public static final String LOGIN_SUBMIT_PATTERN_KEY = "loginSubmitPattern"; + public static final String DEFAULT_LOGIN_SUBMIT_PATTERN = "/j_security_check"; + protected String loginSubmitPattern; + + protected static final String FORM_USERNAME = "j_username"; + protected static final String FORM_PASSWORD = "j_password"; + + protected String loginPage; + protected URLPattern loginPagePattern; + + protected String errorPage; + protected URLPattern errorPagePattern; + + protected String defaultPage; + + protected SecurityRealmInterface realm; + + /** + * Initilize this Authenticator. + * + * @param filterConfig + * @param securityConfig + */ + public void init(FilterConfig filterConfig, SecurityConfig securityConfig) throws Exception { + + realm = securityConfig.getRealm(); + + // login submit pattern + loginSubmitPattern = filterConfig.getInitParameter(LOGIN_SUBMIT_PATTERN_KEY); + if (loginSubmitPattern == null) { + loginSubmitPattern = DEFAULT_LOGIN_SUBMIT_PATTERN; + } + + // default page + defaultPage = securityConfig.getDefaultPage(); + + URLPatternFactory patternFactory = new URLPatternFactory(); + + // login page + loginPage = securityConfig.getLoginPage(); + loginPagePattern = patternFactory.createURLPattern(loginPage, null, null, 0); + + // error page + errorPage = securityConfig.getErrorPage(); + errorPagePattern = patternFactory.createURLPattern(errorPage, null, null, 0); + } + + /** + * Returns FORM as the authentication method. + * + * @return FORM + */ + public String getAuthMethod() { + return HttpServletRequest.FORM_AUTH; + } + + /** + * Process any login information that was included in the request, if any. + * Returns true if SecurityFilter should abort further processing after the method completes (for example, if a + * redirect was sent as part of the login processing). + * + * @param request + * @param response + * @return true if the filter should return after this method ends, false otherwise + */ + public boolean processLogin(SecurityRequestWrapper request, HttpServletResponse response) throws Exception { + if (request.getMatchableURL().endsWith(loginSubmitPattern)) { + String username = request.getParameter(FORM_USERNAME); + String password = request.getParameter(FORM_PASSWORD); + 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. + request.setUserPrincipal(principal); + String continueToURL = getContinueToURL(request); + // This is the url that the user was initially accessing before being prompted for login. + 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); + } + return true; + } + return false; + } + + /** + * Show the login page. + * + * @param request the current request + * @param response the current response + */ + public void showLogin( + HttpServletRequest request, + HttpServletResponse response + ) throws IOException { + // save this request + SecurityFilter.saveRequestInformation(request); + + // redirect to login page + response.sendRedirect(request.getContextPath() + loginPage); + return; + } + + /** + * FormAuthenticator has a special case where the user should be sent to a default page if the user + * spontaneously submits a login request. + * + * @param request + * @return a URL to send the user to after logging in + */ + private String getContinueToURL(HttpServletRequest request) { + String savedURL = SecurityFilter.getContinueToURL(request); + if (savedURL != null) { + return savedURL; + } else { + return request.getContextPath() + defaultPage; + } + } + + /** + * The login and error pages should be viewable, even if they would otherwise be blocked by a security constraint. + * + * @param request + * @return + */ + public boolean bypassSecurityForThisRequest( + SecurityRequestWrapper request, + URLPatternMatcher patternMatcher + ) throws Exception { + String requestURL = request.getMatchableURL(); + return patternMatcher.match(requestURL, loginPagePattern) || patternMatcher.match(requestURL, errorPagePattern); + } +} + +// ------------------------------------------------------------------------ +// EOF diff --git a/src/share/org/securityfilter/filter/SecurityFilter.java b/src/share/org/securityfilter/filter/SecurityFilter.java index 9e0ae99..11a80e8 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.20 2003/07/07 04:18:29 maxcooper Exp $ - * $Revision: 1.20 $ - * $Date: 2003/07/07 04:18:29 $ + * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityFilter.java,v 1.21 2003/07/07 13:12:57 maxcooper Exp $ + * $Revision: 1.21 $ + * $Date: 2003/07/07 13:12:57 $ * * ==================================================================== * The SecurityFilter Software License, Version 1.1 @@ -55,17 +55,12 @@ package org.securityfilter.filter; -import org.apache.commons.codec.binary.Base64; -import org.securityfilter.config.AuthConstraint; -import org.securityfilter.config.SecurityConfig; -import org.securityfilter.config.SecurityConstraint; -import org.securityfilter.config.WebResourceCollection; +import org.securityfilter.authenticator.*; +import org.securityfilter.config.*; import org.securityfilter.realm.SecurityRealmInterface; import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import javax.servlet.http.*; import java.io.IOException; import java.net.URL; import java.security.Principal; @@ -77,47 +72,25 @@ import java.util.*; * @author Max Cooper (max@maxcooper.com) * @author Daya Sharma (iamdaya@yahoo.com, billydaya@sbcglobal.net) * @author Torgeir Veimo (torgeir@pobox.com) - * @version $Revision: 1.20 $ $Date: 2003/07/07 04:18:29 $ + * @version $Revision: 1.21 $ $Date: 2003/07/07 13:12:57 $ */ public class SecurityFilter implements Filter { public static final String CONFIG_FILE_KEY = "config"; public static final String DEFAULT_CONFIG_FILE = "/WEB-INF/securityfilter-config.xml"; - public static final String VALIDATE_KEY = "validate"; - public static final String LOGIN_SUBMIT_PATTERN_KEY = "loginSubmitPattern"; - public static final String DEFAULT_LOGIN_SUBMIT_PATTERN = "/j_security_check"; - protected String loginSubmitPattern; - public static final String TRUE = "true"; - public static final String SAVED_REQUEST_URL = SecurityFilter.class.getName() + ".SAVED_REQUEST_URL"; - public static final String SAVED_REQUEST = SecurityFilter.class.getName() + ".SAVED_REQUEST"; public static final String ALREADY_PROCESSED = SecurityFilter.class.getName() + ".ALREADY_PROCESSED"; - public static final String BASIC_WINDOW_SHOWN = "basic_window_shown"; - public static final String LOGIN_ATTEMPTS = "loginAttempts"; - - protected static final String DUMMY_TOKEN = "dummyToken"; - - protected static final String FORM_USERNAME = "j_username"; - protected static final String FORM_PASSWORD = "j_password"; - - public static final Base64 base64Helper = new Base64(); + public static final String SAVED_REQUEST_URL = SecurityFilter.class.getName() + ".SAVED_REQUEST_URL"; + public static final String SAVED_REQUEST = SecurityFilter.class.getName() + ".SAVED_REQUEST"; protected FilterConfig config; protected SecurityRealmInterface realm; - protected String loginPage; - protected URLPattern loginPagePattern; - protected String errorPage; - protected URLPattern errorPagePattern; - protected String defaultPage; - protected URLPatternFactory patternFactory; protected List patternList; - - protected String authMethod; - protected boolean basic = false; - protected String tooManyIncorrectLogins; + protected URLPatternFactory patternFactory; + protected Authenticator authenticator; /** * Perform filtering operation, and optionally pass the request down the chain. @@ -146,37 +119,23 @@ public class SecurityFilter implements Filter { // get a URLPatternMatcher to use for this thread URLPatternMatcher patternMatcher = patternFactory.createURLPatternMatcher(); - // get the part of the URL to check for matches - String requestURL = getMatchableURL(hReq); - // get saved request, if any (returns null if not applicable) SavedRequest savedRequest = getSavedRequest(hReq); // wrap request - wrappedRequest = new SecurityRequestWrapper(hReq, realm, savedRequest); + wrappedRequest = new SecurityRequestWrapper(hReq, savedRequest, realm, authenticator.getAuthMethod()); URLPattern match = null; try { - // check if this is a login form submittal - if (basicAuthentication(hReq)) { - hReq.getSession().removeAttribute(BASIC_WINDOW_SHOWN); - processLogin(wrappedRequest, hRes); - return; - } else if (requestURL.endsWith(loginSubmitPattern)) { - processLogin(wrappedRequest, hRes); + // check if this request includes login info + if (authenticator.processLogin(wrappedRequest, hRes)) { return; } - // only check the request for a security constraint match if it doesn't - // match the login page or error page patterns -- this allows requests for the - // 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, errorPagePattern) - ) { + // match the url if the authenticator does not indicate that security should be bypassed + if (!authenticator.bypassSecurityForThisRequest(wrappedRequest, patternMatcher)) { // check if request matches security constraint - match = matchPattern(requestURL, wrappedRequest.getMethod(), patternMatcher); + match = matchPattern(wrappedRequest.getMatchableURL(), wrappedRequest.getMethod(), patternMatcher); } } catch (Exception e) { throw new ServletException("Error matching patterns", e); @@ -191,9 +150,10 @@ 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 && hReq.getSession().getAttribute(DUMMY_TOKEN) == null) { + // todo: do we still need this DUMMY_TOKEN check for BASIC auth? + if (!roles.isEmpty() && principal == null /* && hReq.getSession().getAttribute(DUMMY_TOKEN) == null */) { // user needs to be authenticated - showLogin(hReq, hRes); + authenticator.showLogin(hReq, hRes); return; } else { boolean authorized = false; @@ -222,14 +182,6 @@ 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. * @@ -238,41 +190,29 @@ public class SecurityFilter implements Filter { public void init(FilterConfig config) throws ServletException { this.config = config; try { - patternFactory = new URLPatternFactory(); - // parse config file + // config file name String configFile = config.getInitParameter(CONFIG_FILE_KEY); if (configFile == null) { configFile = DEFAULT_CONFIG_FILE; } URL configURL = config.getServletContext().getResource(configFile); + // validate config file? - String validate = config.getInitParameter(VALIDATE_KEY); - // login submit pattern - loginSubmitPattern = config.getInitParameter(LOGIN_SUBMIT_PATTERN_KEY); - if (loginSubmitPattern == null) { - loginSubmitPattern = DEFAULT_LOGIN_SUBMIT_PATTERN; - } - SecurityConfig securityConfig = new SecurityConfig(TRUE.equalsIgnoreCase(validate)); + boolean validate = TRUE.equalsIgnoreCase(config.getInitParameter(VALIDATE_KEY)); + + SecurityConfig securityConfig = new SecurityConfig(validate); securityConfig.loadConfig(configURL); - // get config values + // get the realm realm = securityConfig.getRealm(); - defaultPage = securityConfig.getDefaultPage(); - - // get login and error page patterns - loginPage = securityConfig.getLoginPage(); - 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 an Authenticator + authenticator = AuthenticatorFactory.createAuthenticator(config, securityConfig); // create pattern list + patternFactory = new URLPatternFactory(); patternList = new ArrayList(); int order = 1; List constraints = securityConfig.getSecurityConstraints(); @@ -309,22 +249,6 @@ public class SecurityFilter implements Filter { } /** - * Set the filter configuration, included for WebLogic 6 compatibility. - * - * @param config filter configuration object - */ - public void setFilterConfig(FilterConfig config) throws ServletException { - init(config); - } - - /** - * Get the filter config object, included for WebLogic 6 compatibility. - */ - public FilterConfig getFilterConfig() { - return config; - } - - /** * Find a match for the requested pattern & method, if any. * * @param pattern the pattern to match @@ -345,131 +269,6 @@ public class SecurityFilter implements Filter { } /** - * Show the login page. - * - * @param request the current request - * @param response the current response - * @exception IOException - * @exception ServletException - */ - protected void showLogin( - HttpServletRequest request, - HttpServletResponse response - ) throws IOException, ServletException { - // save this request - saveRequestInformation(request); - - // redirect to login page - if (basic) { - 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 (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; - } - } - - /** - * Process a login form submittal. - * - * @param request the current request - * @param response the current response - * @exception IOException - * @exception ServletException - */ - protected void processLogin( - SecurityRequestWrapper request, - HttpServletResponse response - ) throws IOException, ServletException { - String username; - String password; - if (basic) { - username = parseUsername(request.getHeader("Authorization")); - password = parsePassword(request.getHeader("Authorization")); - } else { - username = request.getParameter(FORM_USERNAME); - password = request.getParameter(FORM_PASSWORD); - } - 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. - request.setUserPrincipal(principal); - String continueToURL = getContinueToURL(request); - if (basic) { - // what does this do? - request.getSession().setAttribute(DUMMY_TOKEN, DUMMY_TOKEN); - } - // This is the url that the user was initially accessing before being prompted for login. - response.sendRedirect(response.encodeRedirectURL(continueToURL)); - } else { - // 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); - } - } - } - - /** - * 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 - * request was spontaneously submitted. - * - * @param request the current request - */ - protected String getContinueToURL(HttpServletRequest request) { - String savedURL = (String) request.getSession().getAttribute(SAVED_REQUEST_URL); - if (savedURL != null) { - return savedURL; - } else { - return request.getContextPath() + defaultPage; - } - } - - /** - * Save request information to re-use when the user is successfully authenticated. - * - * @param request the current request - */ - protected void saveRequestInformation(HttpServletRequest request) { - HttpSession session = request.getSession(); - session.setAttribute(SecurityFilter.SAVED_REQUEST_URL, getSaveableURL(request)); - session.setAttribute(SecurityFilter.SAVED_REQUEST, new SavedRequest(request)); - } - - /** * If this request matches the one we saved, return the SavedRequest and remove it from the session. * * @param request the current request @@ -493,23 +292,30 @@ public class SecurityFilter implements Filter { } } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // The following methods are provided as static utilities for use by SecurityFilter and other classes. // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** - * Return a URL that can be matched against security URL patterns.

+ * 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 + * request was spontaneously submitted. * - * This is the part after the contextPath, with the pathInfo, but without the query string.
- * http://server:8080/contextPath/someURL.jsp?param=value becomes /someURL.jsp + * @param request the current request + */ + public static String getContinueToURL(HttpServletRequest request) { + return (String) request.getSession().getAttribute(SAVED_REQUEST_URL); + } + + /** + * Save request information to re-use when the user is successfully authenticated. * - * @param request the request to construct a matchable URL for + * @param request the current request */ - private String getMatchableURL(HttpServletRequest request) { - // extract the servlet path portion that needs to be checked - String matchableURL = request.getServletPath(); - // add the pathInfo, as it needs to be part of the URL we check - String pathInfo = request.getPathInfo(); - if (pathInfo != null) { - matchableURL = matchableURL + pathInfo; - } - return matchableURL; + public static void saveRequestInformation(HttpServletRequest request) { + HttpSession session = request.getSession(); + session.setAttribute(SecurityFilter.SAVED_REQUEST_URL, getSaveableURL(request)); + session.setAttribute(SecurityFilter.SAVED_REQUEST, new SavedRequest(request)); } /** @@ -519,32 +325,15 @@ public class SecurityFilter implements Filter { * * @param request the request to construct a saveable URL for */ - private String getSaveableURL(HttpServletRequest request) { - String url = null; + private static String getSaveableURL(HttpServletRequest request) { 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. - if (request.isSecure()) { - protocol = "https://"; - } else { - protocol = "http://"; - } - } - 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. - } + saveableURL = getRequestURL(request); } + // fix the protocol + fixProtocol(saveableURL, request); // add the query string, if any String queryString = request.getQueryString(); if (queryString != null) { @@ -553,57 +342,72 @@ public class SecurityFilter implements Filter { return saveableURL.toString(); } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // The following methods are provided for compatibility with various app servers. // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** - * Parse the username out of the BASIC authorization header string. - * @param authorization - * @return + * Set the filter configuration, included for WebLogic 6 compatibility. + * + * @param config filter configuration object */ - private String parseUsername(String authorization) { - String unencoded = decodeBasicAuthorizationString(authorization); - if (unencoded == null) { - return null; - } else { - int colon = unencoded.indexOf(':'); - if (colon < 0) { - return null; - } else { - return unencoded.substring(0, colon).trim(); - } - } + public void setFilterConfig(FilterConfig config) throws ServletException { + init(config); } /** - * Parse the password out of the BASIC authorization header string. - * @param authorization - * @return + * Get the filter config object, included for WebLogic 6 compatibility. */ - private String parsePassword(String authorization) { - String unencoded = decodeBasicAuthorizationString(authorization); - if (unencoded == null) { - return null; - } else { - int colon = unencoded.indexOf(':'); - if (colon < 0) { - return (null); + public FilterConfig getFilterConfig() { + return config; + } + + /** + * Get the requestURL. + * This method is called when the app server fails to implement HttpServletRequest.getRequestURL(). + * Orion 1.5.2 is one such server. + */ + private static StringBuffer getRequestURL(HttpServletRequest request) { + String protocol = request.getProtocol(); + int port = request.getServerPort(); + String portString = ":" + port; + + // todo: this needs to be tested to see if it still an issue; remove it if it is not needed + // Set the portString to the empty string if the requrest came in on the default port. + // This will keep Netscape from dropping the session, which happens when the port is added where it wasn't before. + // This is not perfect, but most requests on the default ports will not be made with an explicit port number. + if (protocol.equals("HTTP/1.1")) { + if (!request.isSecure()) { + if (port == 80) { + portString = ""; + } } else { - return unencoded.substring(colon + 1).trim(); + if (port == 443) { + portString = ""; + } } } + + // construct the saveable URL string + return new StringBuffer(protocol + request.getServerName() + portString + request.getRequestURI()); } /** - * Decode the BASIC authorization string. + * Fix the protocol portion of an absolute url. Often, the protocol will be http: even for https: requests. + * + * todo: needs testing to make sure this is proper in all circumstances * - * @param authorization - * @return + * @param url + * @param request */ - private String decodeBasicAuthorizationString(String authorization) { - if (authorization == null || !authorization.toLowerCase().startsWith("basic ")) { - return null; - } else { - authorization = authorization.substring(6).trim(); - // Decode and parse the authorization credentials - return new String(base64Helper.decodeBase64(authorization.getBytes())); + private static void fixProtocol(StringBuffer url, HttpServletRequest request) { + // fix protocol, if needed (since HTTP is the same regardless of whether it runs on TCP or on SSL/TCP) + if ( + request.getProtocol().equals("HTTP/1.1") + && request.isSecure() + && url.toString().startsWith("http://") + ) { + url.replace(0, 4, "https"); } } } diff --git a/src/share/org/securityfilter/filter/SecurityRequestWrapper.java b/src/share/org/securityfilter/filter/SecurityRequestWrapper.java index 1803ed1..77077ef 100644 --- a/src/share/org/securityfilter/filter/SecurityRequestWrapper.java +++ b/src/share/org/securityfilter/filter/SecurityRequestWrapper.java @@ -1,7 +1,7 @@ /* - * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityRequestWrapper.java,v 1.8 2003/07/07 04:16:40 maxcooper Exp $ - * $Revision: 1.8 $ - * $Date: 2003/07/07 04:16:40 $ + * $Header: /cvsroot/securityfilter/securityfilter/src/share/org/securityfilter/filter/SecurityRequestWrapper.java,v 1.9 2003/07/07 13:12:57 maxcooper Exp $ + * $Revision: 1.9 $ + * $Date: 2003/07/07 13:12:57 $ * * ==================================================================== * The SecurityFilter Software License, Version 1.1 @@ -57,16 +57,11 @@ package org.securityfilter.filter; import org.securityfilter.realm.SecurityRealmInterface; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletRequest; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.*; +import javax.servlet.http.*; import java.io.IOException; import java.security.Principal; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * SecurityRequestWrapper @@ -74,7 +69,7 @@ import java.util.Map; * @author Max Cooper (max@maxcooper.com) * @author Daya Sharma (iamdaya@yahoo.com, billydaya@sbcglobal.net) * @author Torgeir Veimo (torgeir@pobox.com) - * @version $Revision: 1.8 $ $Date: 2003/07/07 04:16:40 $ + * @version $Revision: 1.9 $ $Date: 2003/07/07 13:12:57 $ */ public class SecurityRequestWrapper extends HttpServletRequestWrapper { public static final String PRINCIPAL_SESSION_KEY = SecurityRequestWrapper.class.getName() + ".PRINCIPAL"; @@ -82,6 +77,8 @@ public class SecurityRequestWrapper extends HttpServletRequestWrapper { private HttpServletRequest currentRequest; private SecurityRealmInterface realm; private SavedRequest savedRequest; + private String authType; + private String matchableURL; /** * Construct a new SecurityRequestWrapper. @@ -91,11 +88,18 @@ public class SecurityRequestWrapper extends HttpServletRequestWrapper { * @param savedRequest SavedRequest (usually null, unless this is the request * that invoked the authorization sequence) */ - public SecurityRequestWrapper(HttpServletRequest request, SecurityRealmInterface realm, SavedRequest savedRequest) { + public SecurityRequestWrapper( + HttpServletRequest request, + SavedRequest savedRequest, + SecurityRealmInterface realm, + String authType + ) { super(request); this.currentRequest = request; this.realm = realm; this.savedRequest = savedRequest; + this.authType = authType; + initMatchableURL(); } /** @@ -231,13 +235,11 @@ public class SecurityRequestWrapper extends HttpServletRequestWrapper { } /** - * Returns FORM_AUTH if the user has been authenticated, null otherwise. - * - * todo: add BASIC support + * Returns the auth type (e.g. FORM, BASIC, etc.). */ public String getAuthType() { if (getUserPrincipal() != null) { - return HttpServletRequest.FORM_AUTH; + return authType; } else { return null; } @@ -254,6 +256,29 @@ public class SecurityRequestWrapper extends HttpServletRequestWrapper { return super.getMethod(); } } + + /** + * Get a URL that can be matched against security URL patterns. + * + * This is the part after the contextPath, with the pathInfo, but without the query string. + * http://server:8080/contextPath/someURL.jsp?param=value becomes /someURL.jsp + */ + public String getMatchableURL() { + return matchableURL; + } + + /** + * Initilize the matchableURL. + */ + private void initMatchableURL() { + // extract the servlet path portion that needs to be checked + matchableURL = currentRequest.getServletPath(); + // add the pathInfo, as it needs to be part of the URL we check + String pathInfo = currentRequest.getPathInfo(); + if (pathInfo != null) { + matchableURL = matchableURL + pathInfo; + } + } } // ------------------------------------------------------------------------