View Javadoc

1   /* Copyright 2004, 2005, 2006 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;
17  
18  import org.acegisecurity.AccessDeniedException;
19  import org.acegisecurity.AcegiSecurityException;
20  import org.acegisecurity.AuthenticationException;
21  import org.acegisecurity.AuthenticationTrustResolver;
22  import org.acegisecurity.AuthenticationTrustResolverImpl;
23  import org.acegisecurity.InsufficientAuthenticationException;
24  
25  import org.acegisecurity.context.SecurityContextHolder;
26  
27  import org.acegisecurity.ui.savedrequest.SavedRequest;
28  
29  import org.acegisecurity.util.PortResolver;
30  import org.acegisecurity.util.PortResolverImpl;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  
35  import org.springframework.beans.factory.InitializingBean;
36  import org.springframework.context.ApplicationContext;
37  
38  import org.springframework.util.Assert;
39  
40  import java.io.IOException;
41  import java.util.Map;
42  
43  import javax.servlet.Filter;
44  import javax.servlet.FilterChain;
45  import javax.servlet.FilterConfig;
46  import javax.servlet.ServletException;
47  import javax.servlet.ServletRequest;
48  import javax.servlet.ServletResponse;
49  import javax.servlet.http.HttpServletRequest;
50  import javax.servlet.http.HttpServletResponse;
51  
52  /**
53   * Handles any <code>AccessDeniedException</code> and <code>AuthenticationException</code> thrown within the
54   * filter chain.
55   * <p>
56   * This filter is necessary because it provides the bridge between Java exceptions and HTTP responses.
57   * It is solely concerned with maintaining the user interface. This filter does not do any actual security enforcement.
58   * </p>
59   * <p>
60   * If an {@link AuthenticationException} is detected, the filter will launch the <code>authenticationEntryPoint</code>.
61   * This allows common handling of authentication failures originating from any subclass of
62   * {@link org.acegisecurity.intercept.AbstractSecurityInterceptor}.
63   * </p>
64   * <p>
65   * If an {@link AccessDeniedException} is detected, the filter will determine whether or not the user is an anonymous
66   * user. If they are an anonymous user, the <code>authenticationEntryPoint</code> will be launched. If they are not
67   * an anonymous user, the filter will delegate to the {@link org.acegisecurity.ui.AccessDeniedHandler}.
68   * By default the filter will use {@link org.acegisecurity.ui.AccessDeniedHandlerImpl}.
69   * </p>
70   * <p>
71   * To use this filter, it is necessary to specify the following properties:
72   * </p>
73   * <ul>
74   * <li><code>authenticationEntryPoint</code> indicates the handler that
75   * should commence the authentication process if an
76   * <code>AuthenticationException</code> is detected. Note that this may also
77   * switch the current protocol from http to https for an SSL login.</li>
78   * <li><code>portResolver</code> is used to determine the "real" port that a
79   * request was received on.</li>
80   * </ul>
81   * <P>
82   * <B>Do not use this class directly.</B> Instead configure
83   * <code>web.xml</code> to use the {@link
84   * org.acegisecurity.util.FilterToBeanProxy}.
85   * </p>
86   *
87   * @author Ben Alex
88   * @author colin sampaleanu
89   * @version $Id: ExceptionTranslationFilter.java 2134 2007-09-19 16:41:06Z luke_t $
90   */
91  public class ExceptionTranslationFilter implements Filter, InitializingBean {
92  
93      //~ Static fields/initializers =====================================================================================
94  
95  	private static final Log logger = LogFactory.getLog(ExceptionTranslationFilter.class);
96  
97  	//~ Instance fields ================================================================================================
98  
99      private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
100 	private AuthenticationEntryPoint authenticationEntryPoint;
101 	private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
102 	private PortResolver portResolver = new PortResolverImpl();
103 	private boolean createSessionAllowed = true;
104 
105 	//~ Methods ========================================================================================================
106 
107 	public void afterPropertiesSet() throws Exception {
108 		Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint must be specified");
109 		Assert.notNull(portResolver, "portResolver must be specified");
110 		Assert.notNull(authenticationTrustResolver, "authenticationTrustResolver must be specified");
111 	}
112 
113 	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
114 			ServletException {
115 		if (!(request instanceof HttpServletRequest)) {
116 			throw new ServletException("HttpServletRequest required");
117 		}
118 
119 		if (!(response instanceof HttpServletResponse)) {
120 			throw new ServletException("HttpServletResponse required");
121 		}
122 
123 		try {
124 			chain.doFilter(request, response);
125 
126 			if (logger.isDebugEnabled()) {
127 				logger.debug("Chain processed normally");
128 			}
129 		}
130 		catch (AuthenticationException ex) {
131 			handleException(request, response, chain, ex);
132 		}
133 		catch (AccessDeniedException ex) {
134 			handleException(request, response, chain, ex);
135 		}
136 		catch (ServletException ex) {
137 			if (ex.getRootCause() instanceof AuthenticationException
138 					|| ex.getRootCause() instanceof AccessDeniedException) {
139 				handleException(request, response, chain, (AcegiSecurityException) ex.getRootCause());
140 			}
141 			else {
142 				throw ex;
143 			}
144 		}
145 		catch (IOException ex) {
146 			throw ex;
147 		}
148 	}
149 
150 	public AuthenticationEntryPoint getAuthenticationEntryPoint() {
151 		return authenticationEntryPoint;
152 	}
153 
154 	public AuthenticationTrustResolver getAuthenticationTrustResolver() {
155 		return authenticationTrustResolver;
156 	}
157 
158 	public PortResolver getPortResolver() {
159 		return portResolver;
160 	}
161 
162 	private void handleException(ServletRequest request, ServletResponse response, FilterChain chain,
163 			AcegiSecurityException exception) throws IOException, ServletException {
164 		if (exception instanceof AuthenticationException) {
165 			if (logger.isDebugEnabled()) {
166 				logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
167 			}
168 
169 			sendStartAuthentication(request, response, chain, (AuthenticationException) exception);
170 		}
171 		else if (exception instanceof AccessDeniedException) {
172 			if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) {
173 				if (logger.isDebugEnabled()) {
174 					logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point",
175 							exception);
176 				}
177 
178 				sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(
179 						"Full authentication is required to access this resource"));
180 			}
181 			else {
182 				if (logger.isDebugEnabled()) {
183 					logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
184 							exception);
185 				}
186 
187 				accessDeniedHandler.handle(request, response, (AccessDeniedException) exception);
188 			}
189 		}
190 	}
191 
192 	/**
193 	 * If <code>true</code>, indicates that <code>SecurityEnforcementFilter</code> is permitted to store the target
194 	 * URL and exception information in the <code>HttpSession</code> (the default).
195      * In situations where you do not wish to unnecessarily create <code>HttpSession</code>s - because the user agent
196      * will know the failed URL, such as with BASIC or Digest authentication - you may wish to
197 	 * set this property to <code>false</code>. Remember to also set the
198 	 * {@link org.acegisecurity.context.HttpSessionContextIntegrationFilter#allowSessionCreation}
199 	 * to <code>false</code> if you set this property to <code>false</code>.
200 	 *
201 	 * @return <code>true</code> if the <code>HttpSession</code> will be
202 	 * used to store information about the failed request, <code>false</code>
203 	 * if the <code>HttpSession</code> will not be used
204 	 */
205 	public boolean isCreateSessionAllowed() {
206 		return createSessionAllowed;
207 	}
208 
209 	protected void sendStartAuthentication(ServletRequest request, ServletResponse response, FilterChain chain,
210 			AuthenticationException reason) throws ServletException, IOException {
211 		HttpServletRequest httpRequest = (HttpServletRequest) request;
212 
213 		SavedRequest savedRequest = new SavedRequest(httpRequest, portResolver);
214 
215 		if (logger.isDebugEnabled()) {
216 			logger.debug("Authentication entry point being called; SavedRequest added to Session: " + savedRequest);
217 		}
218 
219 		if (createSessionAllowed) {
220 			// Store the HTTP request itself. Used by AbstractProcessingFilter
221 			// for redirection after successful authentication (SEC-29)
222 			httpRequest.getSession().setAttribute(AbstractProcessingFilter.ACEGI_SAVED_REQUEST_KEY, savedRequest);
223 		}
224 
225 		// SEC-112: Clear the SecurityContextHolder's Authentication, as the
226 		// existing Authentication is no longer considered valid
227 		SecurityContextHolder.getContext().setAuthentication(null);
228 
229 		authenticationEntryPoint.commence(httpRequest, (HttpServletResponse) response, reason);
230 	}
231 
232 	public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
233 		Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required");
234 		this.accessDeniedHandler = accessDeniedHandler;
235 	}
236 
237 	public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
238 		this.authenticationEntryPoint = authenticationEntryPoint;
239 	}
240 
241 	public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) {
242 		this.authenticationTrustResolver = authenticationTrustResolver;
243 	}
244 
245 	public void setCreateSessionAllowed(boolean createSessionAllowed) {
246 		this.createSessionAllowed = createSessionAllowed;
247 	}
248 
249 	public void setPortResolver(PortResolver portResolver) {
250 		this.portResolver = portResolver;
251 	}
252 
253     public void init(FilterConfig filterConfig) throws ServletException {
254     }
255 
256     public void destroy() {
257     }    
258 }