1
2
3
4
5
6
7
8
9
10
11
12
13
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
81
82 private static final Log logger = LogFactory.getLog(X509ProcessingFilter.class);
83
84
85
86 private ApplicationEventPublisher eventPublisher;
87 private AuthenticationManager authenticationManager;
88
89
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
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 }