View Javadoc

    1   /* Copyright 2004, 2005 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 org.acegisecurity.Authentication;
    19  import org.acegisecurity.AuthenticationException;
    20  import org.acegisecurity.AuthenticationManager;
    21  import org.acegisecurity.context.SecurityContextHolder;
    22  import org.acegisecurity.intercept.web.AuthenticationEntryPoint;
    23  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
    24  import org.acegisecurity.ui.WebAuthenticationDetails;
    25
    26  import org.apache.commons.codec.binary.Base64;
    27  import org.apache.commons.logging.Log;
    28  import org.apache.commons.logging.LogFactory;
    29
    30  import org.springframework.beans.factory.InitializingBean;
    31
    32  import org.springframework.util.Assert;
    33
    34  import java.io.IOException;
    35
    36  import javax.servlet.Filter;
    37  import javax.servlet.FilterChain;
    38  import javax.servlet.FilterConfig;
    39  import javax.servlet.ServletException;
    40  import javax.servlet.ServletRequest;
    41  import javax.servlet.ServletResponse;
    42  import javax.servlet.http.HttpServletRequest;
    43  import javax.servlet.http.HttpServletResponse;
    44
    45
    46  /***
    47   * Processes a HTTP request's BASIC authorization headers, putting the result
    48   * into the <code>SecurityContextHolder</code>.
    49   *
    50   * <p>
    51   * For a detailed background on what this filter is designed to process, refer
    52   * to <A HREF="http://www.faqs.org/rfcs/rfc1945.html">RFC 1945, Section
    53   * 11.1</A>. Any realm name presented in the HTTP request is ignored.
    54   * </p>
    55   *
    56   * <p>
    57   * In summary, this filter is responsible for processing any request that has a
    58   * HTTP request header of <code>Authorization</code> with an authentication
    59   * scheme of <code>Basic</code> and a Base64-encoded
    60   * <code>username:password</code> token. For example, to authenticate user
    61   * "Aladdin" with password "open sesame" the following header would be
    62   * presented:
    63   * </p>
    64   *
    65   * <p>
    66   * <code>Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==</code>.
    67   * </p>
    68   *
    69   * <p>
    70   * This filter can be used to provide BASIC authentication services to both
    71   * remoting protocol clients (such as Hessian and SOAP) as well as standard
    72   * user agents (such as Internet Explorer and Netscape).
    73   * </p>
    74   *
    75   * <P>
    76   * If authentication is successful, the resulting {@link Authentication} object
    77   * will be placed into the <code>SecurityContextHolder</code>.
    78   * </p>
    79   *
    80   * <p>
    81   * If authentication fails, an {@link AuthenticationEntryPoint} implementation
    82   * is called. Usually this should be {@link BasicProcessingFilterEntryPoint},
    83   * which will prompt the user to authenticate again via BASIC authentication.
    84   * </p>
    85   *
    86   * <p>
    87   * Basic authentication is an attractive protocol because it is simple and
    88   * widely deployed. However, it still transmits a password in clear text and
    89   * as such is undesirable in many situations. Digest authentication is also
    90   * provided by Acegi Security and should be used instead of Basic
    91   * authentication wherever possible. See {@link
    92   * org.acegisecurity.ui.digestauth.DigestProcessingFilter}.
    93   * </p>
    94   *
    95   * <p>
    96   * <b>Do not use this class directly.</b> Instead configure
    97   * <code>web.xml</code> to use the {@link
    98   * org.acegisecurity.util.FilterToBeanProxy}.
    99   * </p>
    100  *
    101  * @author Ben Alex
    102  * @version $Id: BasicProcessingFilter.java,v 1.16 2005/11/17 00:56:48 benalex Exp $
    103  */
    104 public class BasicProcessingFilter implements Filter, InitializingBean {
    105     //~ Static fields/initializers =============================================
    106
    107     private static final Log logger = LogFactory.getLog(BasicProcessingFilter.class);
    108
    109     //~ Instance fields ========================================================
    110
    111     private AuthenticationEntryPoint authenticationEntryPoint;
    112     private AuthenticationManager authenticationManager;
    113
    114     //~ Methods ================================================================
    115
    116     public void setAuthenticationEntryPoint(
    117         AuthenticationEntryPoint authenticationEntryPoint) {
    118         this.authenticationEntryPoint = authenticationEntryPoint;
    119     }
    120
    121     public AuthenticationEntryPoint getAuthenticationEntryPoint() {
    122         return authenticationEntryPoint;
    123     }
    124
    125     public void setAuthenticationManager(
    126         AuthenticationManager authenticationManager) {
    127         this.authenticationManager = authenticationManager;
    128     }
    129
    130     public AuthenticationManager getAuthenticationManager() {
    131         return authenticationManager;
    132     }
    133
    134     public void afterPropertiesSet() throws Exception {
    135         Assert.notNull(this.authenticationManager,
    136             "An AuthenticationManager is required");
    137         Assert.notNull(this.authenticationEntryPoint,
    138             "An AuthenticationEntryPoint is required");
    139     }
    140
    141     public void destroy() {}
    142
    143     public void doFilter(ServletRequest request, ServletResponse response,
    144         FilterChain chain) throws IOException, ServletException {
    145         if (!(request instanceof HttpServletRequest)) {
    146             throw new ServletException("Can only process HttpServletRequest");
    147         }
    148
    149         if (!(response instanceof HttpServletResponse)) {
    150             throw new ServletException("Can only process HttpServletResponse");
    151         }
    152
    153         HttpServletRequest httpRequest = (HttpServletRequest) request;
    154
    155         String header = httpRequest.getHeader("Authorization");
    156
    157         if (logger.isDebugEnabled()) {
    158             logger.debug("Authorization header: " + header);
    159         }
    160
    161         if ((header != null) && header.startsWith("Basic ")) {
    162             String base64Token = header.substring(6);
    163             String token = new String(Base64.decodeBase64(
    164                         base64Token.getBytes()));
    165
    166             String username = "";
    167             String password = "";
    168             int delim = token.indexOf(":");
    169
    170             if (delim != -1) {
    171                 username = token.substring(0, delim);
    172                 password = token.substring(delim + 1);
    173             }
    174
    175             // Only reauthenticate if username doesn't match SecurityContextHolder and user isn't authenticated (see SEC-53)
    176             Authentication existingAuth = SecurityContextHolder.getContext()
    177                                                                .getAuthentication();
    178
    179             if ((existingAuth == null)
    180                 || !existingAuth.getName().equals(username)
    181                 || !existingAuth.isAuthenticated()) {
    182                 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,
    183                         password);
    184                 authRequest.setDetails(new WebAuthenticationDetails(
    185                         httpRequest, false));
    186
    187                 Authentication authResult;
    188
    189                 try {
    190                     authResult = authenticationManager.authenticate(authRequest);
    191                 } catch (AuthenticationException failed) {
    192                     // Authentication failed
    193                     if (logger.isDebugEnabled()) {
    194                         logger.debug("Authentication request for user: "
    195                             + username + " failed: " + failed.toString());
    196                     }
    197
    198                     SecurityContextHolder.getContext().setAuthentication(null);
    199                     authenticationEntryPoint.commence(request, response, failed);
    200
    201                     return;
    202                 }
    203
    204                 // Authentication success
    205                 if (logger.isDebugEnabled()) {
    206                     logger.debug("Authentication success: "
    207                         + authResult.toString());
    208                 }
    209
    210                 SecurityContextHolder.getContext().setAuthentication(authResult);
    211             }
    212         }
    213
    214         chain.doFilter(request, response);
    215     }
    216
    217     public void init(FilterConfig arg0) throws ServletException {}
    218 }