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 }