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.x509;
    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.event.authentication.InteractiveAuthenticationSuccessEvent;
    23  import org.acegisecurity.providers.x509.X509AuthenticationToken;
    24  import org.acegisecurity.ui.AbstractProcessingFilter;
    25  import org.acegisecurity.ui.WebAuthenticationDetails;
    26
    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.context.ApplicationEventPublisherAware;
    33  import org.springframework.context.ApplicationEventPublisher;
    34
    35  import org.springframework.util.Assert;
    36
    37  import java.io.IOException;
    38
    39  import java.security.cert.X509Certificate;
    40
    41  import javax.servlet.*;
    42  import javax.servlet.http.HttpServletRequest;
    43  import javax.servlet.http.HttpServletResponse;
    44
    45
    46  /***
    47   * Processes the X.509 certificate submitted by a client browser when HTTPS is
    48   * used with client-authentication enabled.
    49   *
    50   * <p>
    51   * An {@link X509AuthenticationToken} is created with the certificate as the
    52   * credentials.
    53   * </p>
    54   *
    55   * <p>
    56   * The configured authentication manager is expected to supply a provider which
    57   * can handle this token (usually an instance of {@link
    58   * org.acegisecurity.providers.x509.X509AuthenticationProvider}).
    59   * </p>
    60   *
    61   * <p>
    62   * If authentication is successful, an {@link
    63   * org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent} will be
    64   * published to the application context. No events will be published if
    65   * authentication was unsuccessful, because this would generally be recorded
    66   * via an <code>AuthenticationManager</code>-specific application event.
    67   * </p>
    68   *
    69   * <p>
    70   * <b>Do not use this class directly.</b> Instead configure
    71   * <code>web.xml</code> to use the {@link
    72   * org.acegisecurity.util.FilterToBeanProxy}.
    73   * </p>
    74   *
    75   * @author Luke Taylor
    76   * @version $Id: X509ProcessingFilter.java,v 1.13 2005/11/17 00:56:28 benalex Exp $
    77   */
    78  public class X509ProcessingFilter implements Filter, InitializingBean,
    79          ApplicationEventPublisherAware {
    80      //~ Static fields/initializers =============================================
    81
    82      private static final Log logger = LogFactory.getLog(X509ProcessingFilter.class);
    83
    84      //~ Instance fields ========================================================
    85
    86      private ApplicationEventPublisher eventPublisher;
    87      private AuthenticationManager authenticationManager;
    88
    89      //~ Methods ================================================================
    90
    91      public void setApplicationEventPublisher(ApplicationEventPublisher context) {
    92          this.eventPublisher = context;
    93      }
    94
    95      public void setAuthenticationManager(
    96          AuthenticationManager authenticationManager) {
    97          this.authenticationManager = authenticationManager;
    98      }
    99
    100     public void afterPropertiesSet() throws Exception {
    101         Assert.notNull(authenticationManager,
    102             "An AuthenticationManager must be set");
    103     }
    104
    105     public void destroy() {}
    106
    107     /***
    108      * This method first checks for an existing, non-null authentication in the
    109      * secure context. If one is found it does nothing.
    110      *
    111      * <p>
    112      * If no authentication object exists, it attempts to obtain the client
    113      * authentication certificate from the request. If there is no certificate
    114      * present then authentication is skipped. Otherwise a new authentication
    115      * request containing the certificate will be passed to the configured
    116      * {@link AuthenticationManager}.
    117      * </p>
    118      *
    119      * <p>
    120      * If authentication is successful the returned token will be stored in the
    121      * secure context. Otherwise it will be set to null. In either case, the
    122      * request proceeds through the filter chain.
    123      * </p>
    124      *
    125      * @param request DOCUMENT ME!
    126      * @param response DOCUMENT ME!
    127      * @param filterChain DOCUMENT ME!
    128      *
    129      * @throws IOException DOCUMENT ME!
    130      * @throws ServletException DOCUMENT ME!
    131      */
    132     public void doFilter(ServletRequest request, ServletResponse response,
    133         FilterChain filterChain) throws IOException, ServletException {
    134         if (!(request instanceof HttpServletRequest)) {
    135             throw new ServletException("Can only process HttpServletRequest");
    136         }
    137
    138         if (!(response instanceof HttpServletResponse)) {
    139             throw new ServletException("Can only process HttpServletResponse");
    140         }
    141
    142         HttpServletRequest httpRequest = (HttpServletRequest) request;
    143         HttpServletResponse httpResponse = (HttpServletResponse) response;
    144
    145         if (logger.isDebugEnabled()) {
    146             logger.debug("Checking secure context token: "
    147                 + SecurityContextHolder.getContext().getAuthentication());
    148         }
    149
    150         if (SecurityContextHolder.getContext().getAuthentication() == null) {
    151             Authentication authResult = null;
    152             X509Certificate clientCertificate = extractClientCertificate(httpRequest);
    153
    154             try {
    155                 X509AuthenticationToken authRequest = new X509AuthenticationToken(clientCertificate);
    156
    157                 authRequest.setDetails(new WebAuthenticationDetails(httpRequest));
    158                 authResult = authenticationManager.authenticate(authRequest);
    159                 successfulAuthentication(httpRequest, httpResponse, authResult);
    160             } catch (AuthenticationException failed) {
    161                 unsuccessfulAuthentication(httpRequest, httpResponse, failed);
    162             }
    163         }
    164
    165         filterChain.doFilter(request, response);
    166     }
    167
    168     public void init(FilterConfig ignored) throws ServletException {}
    169
    170     /***
    171      * Puts the <code>Authentication</code> instance returned by the
    172      * authentication manager into the secure context.
    173      *
    174      * @param request DOCUMENT ME!
    175      * @param response DOCUMENT ME!
    176      * @param authResult DOCUMENT ME!
    177      *
    178      * @throws IOException DOCUMENT ME!
    179      */
    180     protected void successfulAuthentication(HttpServletRequest request,
    181         HttpServletResponse response, Authentication authResult)
    182         throws IOException {
    183         if (logger.isDebugEnabled()) {
    184             logger.debug("Authentication success: " + authResult);
    185         }
    186
    187         SecurityContextHolder.getContext().setAuthentication(authResult);
    188
    189         // Fire event
    190         if (this.eventPublisher != null) {
    191             eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
    192                     authResult, this.getClass()));
    193         }
    194     }
    195
    196     /***
    197      * Ensures the authentication object in the secure context is set to null
    198      * when authentication fails.
    199      *
    200      * @param request DOCUMENT ME!
    201      * @param response DOCUMENT ME!
    202      * @param failed DOCUMENT ME!
    203      */
    204     protected void unsuccessfulAuthentication(HttpServletRequest request,
    205         HttpServletResponse response, AuthenticationException failed) {
    206         SecurityContextHolder.getContext().setAuthentication(null);
    207
    208         if (logger.isDebugEnabled()) {
    209             logger.debug("Updated SecurityContextHolder to contain null Authentication");
    210         }
    211
    212         request.getSession().setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY,
    213             failed);
    214     }
    215
    216     private X509Certificate extractClientCertificate(HttpServletRequest request) {
    217         X509Certificate[] certs = (X509Certificate[]) request.getAttribute(
    218                 "javax.servlet.request.X509Certificate");
    219
    220         if ((certs != null) && (certs.length > 0)) {
    221             return certs[0];
    222         }
    223
    224         if (logger.isDebugEnabled()) {
    225             logger.debug("No client certificate found in request.");
    226         }
    227
    228         return null;
    229     }
    230 }