Source code: net/sf/acegisecurity/ui/AbstractProcessingFilter.java
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 net.sf.acegisecurity.ui;
17
18 import net.sf.acegisecurity.Authentication;
19 import net.sf.acegisecurity.AuthenticationException;
20 import net.sf.acegisecurity.AuthenticationManager;
21 import net.sf.acegisecurity.context.ContextHolder;
22 import net.sf.acegisecurity.context.security.SecureContext;
23 import net.sf.acegisecurity.context.security.SecureContextUtils;
24 import net.sf.acegisecurity.ui.rememberme.NullRememberMeServices;
25 import net.sf.acegisecurity.ui.rememberme.RememberMeServices;
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.util.Assert;
33
34 import java.io.IOException;
35
36 import java.util.Properties;
37
38 import javax.servlet.*;
39 import javax.servlet.http.HttpServletRequest;
40 import javax.servlet.http.HttpServletResponse;
41
42
43 /**
44 * Abstract processor of browser-based HTTP-based authentication requests.
45 *
46 * <p>
47 * This filter is responsible for processing authentication requests. If
48 * authentication is successful, the resulting {@link Authentication} object
49 * will be placed into the <code>ContextHolder</code>, which is guaranteed to
50 * have already been created by an earlier filter.
51 * </p>
52 *
53 * <p>
54 * If authentication fails, the <code>AuthenticationException</code> will be
55 * placed into the <code>HttpSession</code> with the attribute defined by
56 * {@link #ACEGI_SECURITY_LAST_EXCEPTION_KEY}.
57 * </p>
58 *
59 * <p>
60 * To use this filter, it is necessary to specify the following properties:
61 * </p>
62 *
63 * <ul>
64 * <li>
65 * <code>defaultTargetUrl</code> indicates the URL that should be used for
66 * redirection if the <code>HttpSession</code> attribute named {@link
67 * #ACEGI_SECURITY_TARGET_URL_KEY} does not indicate the target URL once
68 * authentication is completed successfully. eg: <code>/</code>. This will be
69 * treated as relative to the web-app's context path, and should include the
70 * leading <code>/</code>.
71 * </li>
72 * <li>
73 * <code>authenticationFailureUrl</code> indicates the URL that should be used
74 * for redirection if the authentication request fails. eg:
75 * <code>/login.jsp?login_error=1</code>.
76 * </li>
77 * <li>
78 * <code>filterProcessesUrl</code> indicates the URL that this filter will
79 * respond to. This parameter varies by subclass.
80 * </li>
81 * <li>
82 * <code>alwaysUseDefaultTargetUrl</code> causes successful authentication to
83 * always redirect to the <code>defaultTargetUrl</code>, even if the
84 * <code>HttpSession</code> attribute named {@link
85 * #ACEGI_SECURITY_TARGET_URL_KEY} defines the intended target URL.
86 * </li>
87 * </ul>
88 *
89 * <p>
90 * To configure this filter to redirect to specific pages as the result of
91 * specific {@link AuthenticationException}s you can do the following.
92 * Configure the <code>exceptionMappings</code> property in your application
93 * xml. This property is a java.util.Properties object that maps a
94 * fully-qualified exception class name to a redirection url target.<br>
95 * For example:<br>
96 * <code> <property name="exceptionMappings"><br>
97 * <props><br>
98 * <prop> key="net.sf.acegisecurity.BadCredentialsException">/bad_credentials.jsp</prop><br>
99 * </props><br>
100 * </property><br>
101 * </code><br>
102 * The example above would redirect all {@link
103 * net.sf.acegisecurity.BadCredentialsException}s thrown, to a page in the
104 * web-application called /bad_credentials.jsp.
105 * </p>
106 *
107 * <p>
108 * Any {@link AuthenticationException} thrown that cannot be matched in the
109 * <code>exceptionMappings</code> will be redirected to the
110 * <code>authenticationFailureUrl</code>
111 * </p>
112 *
113 * @author Ben Alex
114 * @author colin sampaleanu
115 * @author Ray Krueger
116 * @version $Id: AbstractProcessingFilter.java,v 1.17 2005/04/18 16:24:33 luke_t Exp $
117 */
118 public abstract class AbstractProcessingFilter implements Filter,
119 InitializingBean {
120 //~ Static fields/initializers =============================================
121
122 public static final String ACEGI_SECURITY_TARGET_URL_KEY = "ACEGI_SECURITY_TARGET_URL";
123 public static final String ACEGI_SECURITY_LAST_EXCEPTION_KEY = "ACEGI_SECURITY_LAST_EXCEPTION";
124 protected static final Log logger = LogFactory.getLog(AbstractProcessingFilter.class);
125
126 //~ Instance fields ========================================================
127
128 private AuthenticationManager authenticationManager;
129 private Properties exceptionMappings = new Properties();
130 private RememberMeServices rememberMeServices = new NullRememberMeServices();
131
132 /** Where to redirect the browser to if authentication fails */
133 private String authenticationFailureUrl;
134
135 /**
136 * Where to redirect the browser to if authentication is successful but
137 * ACEGI_SECURITY_TARGET_URL_KEY is <code>null</code>
138 */
139 private String defaultTargetUrl;
140
141 /**
142 * The URL destination that this filter intercepts and processes (usually
143 * something like <code>/j_acegi_security_check</code>)
144 */
145 private String filterProcessesUrl = getDefaultFilterProcessesUrl();
146
147 /**
148 * If <code>true</code>, will always redirect to {@link #defaultTargetUrl}
149 * upon successful authentication, irrespective of the page that caused
150 * the authentication request (defaults to <code>false</code>).
151 */
152 private boolean alwaysUseDefaultTargetUrl = false;
153
154 /**
155 * Indicates if the filter chain should be continued prior to delegation to
156 * {@link #successfulAuthentication(HttpServletRequest,
157 * HttpServletResponse, Authentication)}, which may be useful in certain
158 * environment (eg Tapestry). Defaults to <code>false</code>.
159 */
160 private boolean continueChainBeforeSuccessfulAuthentication = false;
161
162 //~ Methods ================================================================
163
164 public void setAlwaysUseDefaultTargetUrl(boolean alwaysUseDefaultTargetUrl) {
165 this.alwaysUseDefaultTargetUrl = alwaysUseDefaultTargetUrl;
166 }
167
168 public boolean isAlwaysUseDefaultTargetUrl() {
169 return alwaysUseDefaultTargetUrl;
170 }
171
172 public void setContinueChainBeforeSuccessfulAuthentication(
173 boolean continueChainBeforeSuccessfulAuthentication) {
174 this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
175 }
176
177 public boolean isContinueChainBeforeSuccessfulAuthentication() {
178 return continueChainBeforeSuccessfulAuthentication;
179 }
180
181 /**
182 * Specifies the default <code>filterProcessesUrl</code> for the
183 * implementation.
184 *
185 * @return the default <code>filterProcessesUrl</code>
186 */
187 public abstract String getDefaultFilterProcessesUrl();
188
189 public void setExceptionMappings(Properties exceptionMappings) {
190 this.exceptionMappings = exceptionMappings;
191 }
192
193 public Properties getExceptionMappings() {
194 return new Properties(exceptionMappings);
195 }
196
197 public void setRememberMeServices(RememberMeServices rememberMeServices) {
198 this.rememberMeServices = rememberMeServices;
199 }
200
201 public RememberMeServices getRememberMeServices() {
202 return rememberMeServices;
203 }
204
205 /**
206 * Performs actual authentication.
207 *
208 * @param request from which to extract parameters and perform the
209 * authentication
210 *
211 * @return the authenticated user
212 *
213 * @throws AuthenticationException if authentication fails
214 */
215 public abstract Authentication attemptAuthentication(
216 HttpServletRequest request) throws AuthenticationException;
217
218 public void setAuthenticationFailureUrl(String authenticationFailureUrl) {
219 this.authenticationFailureUrl = authenticationFailureUrl;
220 }
221
222 public String getAuthenticationFailureUrl() {
223 return authenticationFailureUrl;
224 }
225
226 public void setAuthenticationManager(
227 AuthenticationManager authenticationManager) {
228 this.authenticationManager = authenticationManager;
229 }
230
231 public AuthenticationManager getAuthenticationManager() {
232 return authenticationManager;
233 }
234
235 public void setDefaultTargetUrl(String defaultTargetUrl) {
236 this.defaultTargetUrl = defaultTargetUrl;
237 }
238
239 public String getDefaultTargetUrl() {
240 return defaultTargetUrl;
241 }
242
243 public void setFilterProcessesUrl(String filterProcessesUrl) {
244 this.filterProcessesUrl = filterProcessesUrl;
245 }
246
247 public String getFilterProcessesUrl() {
248 return filterProcessesUrl;
249 }
250
251 public void afterPropertiesSet() throws Exception {
252 Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified");
253 Assert.hasLength(defaultTargetUrl, "defaultTargetUrl must be specified");
254 Assert.hasLength(authenticationFailureUrl, "authenticationFailureUrl must be specified");
255 Assert.notNull(authenticationManager, "authenticationManager must be specified");
256 Assert.notNull(this.rememberMeServices);
257 }
258
259 /**
260 * Does nothing. We use IoC container lifecycle services instead.
261 */
262 public void destroy() {}
263
264 public void doFilter(ServletRequest request, ServletResponse response,
265 FilterChain chain) throws IOException, ServletException {
266 if (!(request instanceof HttpServletRequest)) {
267 throw new ServletException("Can only process HttpServletRequest");
268 }
269
270 if (!(response instanceof HttpServletResponse)) {
271 throw new ServletException("Can only process HttpServletResponse");
272 }
273
274 HttpServletRequest httpRequest = (HttpServletRequest) request;
275 HttpServletResponse httpResponse = (HttpServletResponse) response;
276
277 if (requiresAuthentication(httpRequest, httpResponse)) {
278 if (logger.isDebugEnabled()) {
279 logger.debug("Request is to process authentication");
280 }
281
282 onPreAuthentication(httpRequest, httpResponse);
283
284 Authentication authResult;
285
286 try {
287 authResult = attemptAuthentication(httpRequest);
288 } catch (AuthenticationException failed) {
289 // Authentication failed
290 unsuccessfulAuthentication(httpRequest, httpResponse, failed);
291
292 return;
293 }
294
295 // Authentication success
296 if (continueChainBeforeSuccessfulAuthentication) {
297 chain.doFilter(request, response);
298 }
299
300 successfulAuthentication(httpRequest, httpResponse, authResult);
301
302 return;
303 }
304
305 chain.doFilter(request, response);
306 }
307
308 /**
309 * Does nothing. We use IoC container lifecycle services instead.
310 *
311 * @param arg0 ignored
312 *
313 * @throws ServletException ignored
314 */
315 public void init(FilterConfig arg0) throws ServletException {}
316
317 protected void onPreAuthentication(HttpServletRequest request,
318 HttpServletResponse response) throws IOException {}
319
320 protected void onSuccessfulAuthentication(HttpServletRequest request,
321 HttpServletResponse response, Authentication authResult)
322 throws IOException {}
323
324 protected void onUnsuccessfulAuthentication(HttpServletRequest request,
325 HttpServletResponse response) throws IOException {}
326
327 /**
328 * <p>
329 * Indicates whether this filter should attempt to process a login request
330 * for the current invocation.
331 * </p>
332 * <p>
333 * It strips any parameters from the "path" section of the request URL (such as the
334 * jsessionid parameter in <em>http://host/myapp/index.html;jsessionid=blah</em>)
335 * before matching against the <code>filterProcessesUrl</code> property.
336 *
337 * <p>
338 * Subclasses may override for special requirements, such as Tapestry
339 * integration.
340 * </p>
341 *
342 * @param request as received from the filter chain
343 * @param response as received from the filter chain
344 *
345 * @return <code>true</code> if the filter should attempt authentication,
346 * <code>false</code> otherwise
347 */
348 protected boolean requiresAuthentication(HttpServletRequest request,
349 HttpServletResponse response) {
350 String uri = request.getRequestURI();
351 int pathParamIndex = uri.indexOf(';');
352
353 if(pathParamIndex > 0) {
354 // strip everything after the first semi-colon
355 uri = uri.substring(0, pathParamIndex);
356 }
357
358 return uri.endsWith(request.getContextPath() + filterProcessesUrl);
359 }
360
361 protected void successfulAuthentication(HttpServletRequest request,
362 HttpServletResponse response, Authentication authResult)
363 throws IOException {
364 if (logger.isDebugEnabled()) {
365 logger.debug("Authentication success: " + authResult.toString());
366 }
367
368 SecureContext sc = SecureContextUtils.getSecureContext();
369 sc.setAuthentication(authResult);
370
371 if (logger.isDebugEnabled()) {
372 logger.debug(
373 "Updated ContextHolder to contain the following Authentication: '"
374 + authResult + "'");
375 }
376
377 String targetUrl = (String) request.getSession().getAttribute(ACEGI_SECURITY_TARGET_URL_KEY);
378 request.getSession().removeAttribute(ACEGI_SECURITY_TARGET_URL_KEY);
379
380 if (alwaysUseDefaultTargetUrl == true) {
381 targetUrl = null;
382 }
383
384 if (targetUrl == null) {
385 targetUrl = request.getContextPath() + defaultTargetUrl;
386 }
387
388 if (logger.isDebugEnabled()) {
389 logger.debug(
390 "Redirecting to target URL from HTTP Session (or default): "
391 + targetUrl);
392 }
393
394 onSuccessfulAuthentication(request, response, authResult);
395
396 rememberMeServices.loginSuccess(request, response, authResult);
397
398 response.sendRedirect(response.encodeRedirectURL(targetUrl));
399 }
400
401 protected void unsuccessfulAuthentication(HttpServletRequest request,
402 HttpServletResponse response, AuthenticationException failed)
403 throws IOException {
404 SecureContext sc = SecureContextUtils.getSecureContext();
405 sc.setAuthentication(null);
406 ContextHolder.setContext(sc);
407
408 if (logger.isDebugEnabled()) {
409 logger.debug("Updated ContextHolder to contain null Authentication");
410 }
411
412 String failureUrl = exceptionMappings.getProperty(failed.getClass()
413 .getName(),
414 authenticationFailureUrl);
415
416 if (logger.isDebugEnabled()) {
417 logger.debug("Authentication request failed: " + failed.toString());
418 }
419
420 request.getSession().setAttribute(ACEGI_SECURITY_LAST_EXCEPTION_KEY,
421 failed);
422
423 onUnsuccessfulAuthentication(request, response);
424
425 rememberMeServices.loginFail(request, response);
426
427 response.sendRedirect(response.encodeRedirectURL(request.getContextPath()
428 + failureUrl));
429 }
430 }