Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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> &lt;property name="exceptionMappings"&gt;<br>
97   * &nbsp;&nbsp;&lt;props&gt;<br>
98   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;prop&gt; key="net.sf.acegisecurity.BadCredentialsException"&gt;/bad_credentials.jsp&lt;/prop&gt;<br>
99   * &nbsp;&nbsp;&lt;/props&gt;<br>
100  * &lt;/property&gt;<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 }