Coverage Report - org.acegisecurity.ui.basicauth.BasicProcessingFilter
 
Classes in this File Line Coverage Branch Coverage Complexity
BasicProcessingFilter
80% 
93% 
2.615
 
 1  
 /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 2  
  *
 3  
  * Licensed under the Apache License, Version 2.0 (the "License");
 4  
  * you may not use this file except in compliance with the License.
 5  
  * You may obtain a copy of the License at
 6  
  *
 7  
  *     http://www.apache.org/licenses/LICENSE-2.0
 8  
  *
 9  
  * Unless required by applicable law or agreed to in writing, software
 10  
  * distributed under the License is distributed on an "AS IS" BASIS,
 11  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
  * See the License for the specific language governing permissions and
 13  
  * limitations under the License.
 14  
  */
 15  
 
 16  
 package org.acegisecurity.ui.basicauth;
 17  
 
 18  
 import java.io.IOException;
 19  
 
 20  
 import javax.servlet.Filter;
 21  
 import javax.servlet.FilterChain;
 22  
 import javax.servlet.FilterConfig;
 23  
 import javax.servlet.ServletException;
 24  
 import javax.servlet.ServletRequest;
 25  
 import javax.servlet.ServletResponse;
 26  
 import javax.servlet.http.HttpServletRequest;
 27  
 import javax.servlet.http.HttpServletResponse;
 28  
 
 29  
 import org.acegisecurity.Authentication;
 30  
 import org.acegisecurity.AuthenticationException;
 31  
 import org.acegisecurity.AuthenticationManager;
 32  
 import org.acegisecurity.context.SecurityContextHolder;
 33  
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 34  
 import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
 35  
 import org.acegisecurity.ui.AuthenticationDetailsSource;
 36  
 import org.acegisecurity.ui.AuthenticationDetailsSourceImpl;
 37  
 import org.acegisecurity.ui.AuthenticationEntryPoint;
 38  
 import org.acegisecurity.ui.rememberme.RememberMeServices;
 39  
 import org.apache.commons.codec.binary.Base64;
 40  
 import org.apache.commons.logging.Log;
 41  
 import org.apache.commons.logging.LogFactory;
 42  
 import org.springframework.beans.factory.InitializingBean;
 43  
 import org.springframework.util.Assert;
 44  
 
 45  
 
 46  
 /**
 47  
  * Processes a HTTP request's BASIC authorization headers, putting the result into the
 48  
  * <code>SecurityContextHolder</code>.<p>For a detailed background on what this filter is designed to process,
 49  
  * refer to <A HREF="http://www.faqs.org/rfcs/rfc1945.html">RFC 1945, Section 11.1</A>. Any realm name presented in
 50  
  * the HTTP request is ignored.</p>
 51  
  *  <p>In summary, this filter is responsible for processing any request that has a HTTP request header of
 52  
  * <code>Authorization</code> with an authentication scheme of <code>Basic</code> and a Base64-encoded
 53  
  * <code>username:password</code> token. For example, to authenticate user "Aladdin" with password "open sesame" the
 54  
  * following header would be presented:</p>
 55  
  *  <p><code>Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==</code>.</p>
 56  
  *  <p>This filter can be used to provide BASIC authentication services to both remoting protocol clients (such as
 57  
  * Hessian and SOAP) as well as standard user agents (such as Internet Explorer and Netscape).</p>
 58  
  *  <P>If authentication is successful, the resulting {@link Authentication} object will be placed into the
 59  
  * <code>SecurityContextHolder</code>.</p>
 60  
  *  <p>If authentication fails and <code>ignoreFailure</code> is <code>false</code> (the default), an {@link
 61  
  * AuthenticationEntryPoint} implementation is called. Usually this should be {@link BasicProcessingFilterEntryPoint},
 62  
  * which will prompt the user to authenticate again via BASIC authentication.</p>
 63  
  *  <p>Basic authentication is an attractive protocol because it is simple and widely deployed. However, it still
 64  
  * transmits a password in clear text and as such is undesirable in many situations. Digest authentication is also
 65  
  * provided by Acegi Security and should be used instead of Basic authentication wherever possible. See {@link
 66  
  * org.acegisecurity.ui.digestauth.DigestProcessingFilter}.</p>
 67  
  *  <p>Note that if a {@link #rememberMeServices} is set, this filter will automatically send back remember-me
 68  
  * details to the client. Therefore, subsequent requests will not need to present a BASIC authentication header as
 69  
  * they will be authenticated using the remember-me mechanism.</p>
 70  
  *  <p><b>Do not use this class directly.</b> Instead configure <code>web.xml</code> to use the {@link
 71  
  * org.acegisecurity.util.FilterToBeanProxy}.</p>
 72  
  *
 73  
  * @author Ben Alex
 74  
  * @version $Id: BasicProcessingFilter.java 2277 2007-12-02 02:15:18Z benalex $
 75  
  */
 76  17
 public class BasicProcessingFilter implements Filter, InitializingBean {
 77  
     //~ Static fields/initializers =====================================================================================
 78  
 
 79  2
         private static final Log logger = LogFactory.getLog(BasicProcessingFilter.class);
 80  
 
 81  
     //~ Instance fields ================================================================================================
 82  
 
 83  17
     private AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl();
 84  
     private AuthenticationEntryPoint authenticationEntryPoint;
 85  
     private AuthenticationManager authenticationManager;
 86  
     private RememberMeServices rememberMeServices;
 87  17
     private boolean ignoreFailure = false;
 88  
 
 89  
     //~ Methods ========================================================================================================
 90  
 
 91  
     public void afterPropertiesSet() throws Exception {
 92  2
         Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
 93  1
         Assert.notNull(this.authenticationEntryPoint, "An AuthenticationEntryPoint is required");
 94  0
     }
 95  
 
 96  8
     public void destroy() {}
 97  
 
 98  
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
 99  
             throws IOException, ServletException {
 100  
 
 101  10
         if (!(request instanceof HttpServletRequest)) {
 102  1
             throw new ServletException("Can only process HttpServletRequest");
 103  
         }
 104  
 
 105  9
         if (!(response instanceof HttpServletResponse)) {
 106  1
             throw new ServletException("Can only process HttpServletResponse");
 107  
         }
 108  
 
 109  8
         HttpServletRequest httpRequest = (HttpServletRequest) request;
 110  8
         HttpServletResponse httpResponse = (HttpServletResponse) response;
 111  
 
 112  8
         String header = httpRequest.getHeader("Authorization");
 113  
 
 114  8
         if (logger.isDebugEnabled()) {
 115  0
             logger.debug("Authorization header: " + header);
 116  
         }
 117  
 
 118  8
         if ((header != null) && header.startsWith("Basic ")) {
 119  6
             String base64Token = header.substring(6);
 120  6
             String token = new String(Base64.decodeBase64(base64Token.getBytes()));
 121  
 
 122  6
             String username = "";
 123  6
             String password = "";
 124  6
             int delim = token.indexOf(":");
 125  
 
 126  6
             if (delim != -1) {
 127  5
                 username = token.substring(0, delim);
 128  5
                 password = token.substring(delim + 1);
 129  
             }
 130  
 
 131  6
             if (authenticationIsRequired(username)) {
 132  6
                 UsernamePasswordAuthenticationToken authRequest =
 133  
                         new UsernamePasswordAuthenticationToken(username, password);
 134  6
                 authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));
 135  
 
 136  
                 Authentication authResult;
 137  
 
 138  
                 try {
 139  6
                     authResult = authenticationManager.authenticate(authRequest);
 140  4
                 } catch (AuthenticationException failed) {
 141  
                     // Authentication failed
 142  4
                     if (logger.isDebugEnabled()) {
 143  0
                         logger.debug("Authentication request for user: " + username + " failed: " + failed.toString());
 144  
                     }
 145  
 
 146  4
                     SecurityContextHolder.getContext().setAuthentication(null);
 147  
 
 148  4
                     if (rememberMeServices != null) {
 149  0
                         rememberMeServices.loginFail(httpRequest, httpResponse);
 150  
                     }
 151  
 
 152  4
                     if (ignoreFailure) {
 153  1
                         chain.doFilter(request, response);
 154  
                     } else {
 155  3
                         authenticationEntryPoint.commence(request, response, failed);
 156  
                     }
 157  
 
 158  4
                     return;
 159  2
                 }
 160  
 
 161  
                 // Authentication success
 162  2
                 if (logger.isDebugEnabled()) {
 163  0
                     logger.debug("Authentication success: " + authResult.toString());
 164  
                 }
 165  
 
 166  2
                 SecurityContextHolder.getContext().setAuthentication(authResult);
 167  
 
 168  2
                 if (rememberMeServices != null) {
 169  0
                     rememberMeServices.loginSuccess(httpRequest, httpResponse, authResult);
 170  
                 }
 171  
             }
 172  
         }
 173  
 
 174  4
         chain.doFilter(request, response);
 175  4
     }
 176  
 
 177  
     private boolean authenticationIsRequired(String username) {
 178  
         // Only reauthenticate if username doesn't match SecurityContextHolder and user isn't authenticated
 179  
         // (see SEC-53)
 180  6
         Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
 181  
 
 182  6
         if(existingAuth == null || !existingAuth.isAuthenticated()) {
 183  5
             return true;
 184  
         }
 185  
 
 186  
         // Limit username comparison to providers which use usernames (ie UsernamePasswordAuthenticationToken)
 187  
         // (see SEC-348)
 188  
 
 189  1
         if (existingAuth instanceof UsernamePasswordAuthenticationToken && !existingAuth.getName().equals(username)) {
 190  1
             return true;
 191  
         }
 192  
         
 193  
         // Handle unusual condition where an AnonymousAuthenticationToken is already present
 194  
         // This shouldn't happen very often, as BasicProcessingFitler is meant to be earlier in the filter
 195  
         // chain than AnonymousProcessingFilter. Nevertheless, presence of both an AnonymousAuthenticationToken
 196  
         // together with a BASIC authentication request header should indicate reauthentication using the
 197  
         // BASIC protocol is desirable. This behaviour is also consistent with that provided by form and digest,
 198  
         // both of which force re-authentication if the respective header is detected (and in doing so replace
 199  
         // any existing AnonymousAuthenticationToken). See SEC-610.
 200  0
         if (existingAuth instanceof AnonymousAuthenticationToken) {
 201  0
                 return true;
 202  
         }
 203  
 
 204  0
         return false;
 205  
     }
 206  
 
 207  
     public AuthenticationEntryPoint getAuthenticationEntryPoint() {
 208  1
         return authenticationEntryPoint;
 209  
     }
 210  
 
 211  
     public AuthenticationManager getAuthenticationManager() {
 212  1
         return authenticationManager;
 213  
     }
 214  
 
 215  8
     public void init(FilterConfig arg0) throws ServletException {}
 216  
 
 217  
     public boolean isIgnoreFailure() {
 218  2
         return ignoreFailure;
 219  
     }
 220  
 
 221  
     public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
 222  0
         Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
 223  0
         this.authenticationDetailsSource = authenticationDetailsSource;
 224  0
     }
 225  
 
 226  
     public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
 227  14
         this.authenticationEntryPoint = authenticationEntryPoint;
 228  14
     }
 229  
 
 230  
     public void setAuthenticationManager(AuthenticationManager authenticationManager) {
 231  14
         this.authenticationManager = authenticationManager;
 232  14
     }
 233  
 
 234  
     public void setIgnoreFailure(boolean ignoreFailure) {
 235  1
         this.ignoreFailure = ignoreFailure;
 236  1
     }
 237  
 
 238  
     public void setRememberMeServices(RememberMeServices rememberMeServices) {
 239  0
         this.rememberMeServices = rememberMeServices;
 240  0
     }
 241  
 
 242  
 }