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  package org.acegisecurity.intercept.web;
16  
17  import org.acegisecurity.AccessDeniedException;
18  import org.acegisecurity.AuthenticationException;
19  import org.acegisecurity.AuthenticationTrustResolver;
20  import org.acegisecurity.AuthenticationTrustResolverImpl;
21  import org.acegisecurity.InsufficientAuthenticationException;
22  import org.acegisecurity.context.SecurityContextHolder;
23  import org.acegisecurity.ui.AbstractProcessingFilter;
24  import org.acegisecurity.util.PortResolver;
25  import org.acegisecurity.util.PortResolverImpl;
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 javax.servlet.Filter;
37  import javax.servlet.FilterChain;
38  import javax.servlet.FilterConfig;
39  import javax.servlet.ServletException;
40  import javax.servlet.ServletRequest;
41  import javax.servlet.ServletResponse;
42  import javax.servlet.http.HttpServletRequest;
43  import javax.servlet.http.HttpServletResponse;
44  
45  
46  /***
47   * Wraps requests to the {@link FilterSecurityInterceptor}.
48   *
49   * <p>
50   * This filter is necessary because it provides the bridge between incoming
51   * requests and the <code>FilterSecurityInterceptor</code> instance.
52   * </p>
53   *
54   * <p>
55   * If an {@link AuthenticationException} is detected, the filter will launch
56   * the <code>authenticationEntryPoint</code>. This allows common handling of
57   * authentication failures originating from any subclass of {@link
58   * org.acegisecurity.intercept.AbstractSecurityInterceptor}.
59   * </p>
60   *
61   * <p>
62   * If an {@link AccessDeniedException} is detected, the filter will determine
63   * whether or not the user is an anonymous user. If they are an anonymous
64   * user, the <code>authenticationEntryPoint</code> will be launched. If they
65   * are not an anonymous user, the filter will respond with a
66   * <code>HttpServletResponse.SC_FORBIDDEN</code> (403 error).  In addition,
67   * the <code>AccessDeniedException</code> itself will be placed in the
68   * <code>HttpSession</code> attribute keyed against {@link
69   * #ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY} (to allow access to the stack
70   * trace etc). Again, this allows common access denied handling irrespective
71   * of the originating security interceptor.
72   * </p>
73   *
74   * <p>
75   * To use this filter, it is necessary to specify the following properties:
76   * </p>
77   *
78   * <ul>
79   * <li>
80   * <code>filterSecurityInterceptor</code> indicates the
81   * <code>FilterSecurityInterceptor</code> to delegate HTTP security decisions
82   * to.
83   * </li>
84   * <li>
85   * <code>authenticationEntryPoint</code> indicates the handler that should
86   * commence the authentication process if an
87   * <code>AuthenticationException</code> is detected. Note that this may also
88   * switch the current protocol from http to https for an SSL login.
89   * </li>
90   * <li>
91   * <code>portResolver</code> is used to determine the "real" port that a
92   * request was received on.
93   * </li>
94   * </ul>
95   *
96   * <P>
97   * <B>Do not use this class directly.</B> Instead configure
98   * <code>web.xml</code> to use the {@link
99   * org.acegisecurity.util.FilterToBeanProxy}.
100  * </p>
101  *
102  * @author Ben Alex
103  * @author colin sampaleanu
104  * @version $Id: SecurityEnforcementFilter.java,v 1.22 2005/11/25 04:38:18 benalex Exp $
105  */
106 public class SecurityEnforcementFilter implements Filter, InitializingBean {
107     private static final Log logger = LogFactory.getLog(SecurityEnforcementFilter.class);
108     public static final String ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY = "ACEGI_SECURITY_403_EXCEPTION";
109     private AuthenticationEntryPoint authenticationEntryPoint;
110     private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
111     private FilterSecurityInterceptor filterSecurityInterceptor;
112     private PortResolver portResolver = new PortResolverImpl();
113     private boolean createSessionAllowed = true;
114 
115     public void setAuthenticationEntryPoint(
116         AuthenticationEntryPoint authenticationEntryPoint) {
117         this.authenticationEntryPoint = authenticationEntryPoint;
118     }
119 
120     public AuthenticationEntryPoint getAuthenticationEntryPoint() {
121         return authenticationEntryPoint;
122     }
123 
124     public void setAuthenticationTrustResolver(
125         AuthenticationTrustResolver authenticationTrustResolver) {
126         this.authenticationTrustResolver = authenticationTrustResolver;
127     }
128 
129     /***
130      * If <code>true</code>, indicates that <code>SecurityEnforcementFilter</code> is permitted
131      * to store the target URL and exception information in the <code>HttpSession</code> (the
132      * default). In situations where you do not wish to unnecessarily create <code>HttpSession</code>s
133      * - because the user agent will know the failed URL, such as with BASIC or Digest authentication
134      * - you may wish to set this property to <code>false</code>. Remember to also set the
135      * {@link org.acegisecurity.context.HttpSessionContextIntegrationFilter#allowSessionCreation}
136      * to <code>false</code> if you set this property to <code>false</code>.
137      *
138      * @return <code>true</code> if the <code>HttpSession</code> will be used to store information
139      * about the failed request, <code>false</code> if the <code>HttpSession</code> will not be
140      * used
141      */
142     public boolean isCreateSessionAllowed() {
143         return createSessionAllowed;
144     }
145 
146     public void setCreateSessionAllowed(boolean createSessionAllowed) {
147         this.createSessionAllowed = createSessionAllowed;
148     }
149 
150     public AuthenticationTrustResolver getAuthenticationTrustResolver() {
151         return authenticationTrustResolver;
152     }
153 
154     public void setFilterSecurityInterceptor(
155         FilterSecurityInterceptor filterSecurityInterceptor) {
156         this.filterSecurityInterceptor = filterSecurityInterceptor;
157     }
158 
159     public FilterSecurityInterceptor getFilterSecurityInterceptor() {
160         return filterSecurityInterceptor;
161     }
162 
163     public void setPortResolver(PortResolver portResolver) {
164         this.portResolver = portResolver;
165     }
166 
167     public PortResolver getPortResolver() {
168         return portResolver;
169     }
170 
171     public void afterPropertiesSet() throws Exception {
172         Assert.notNull(authenticationEntryPoint,
173             "authenticationEntryPoint must be specified");
174         Assert.notNull(filterSecurityInterceptor,
175             "filterSecurityInterceptor must be specified");
176         Assert.notNull(portResolver, "portResolver must be specified");
177         Assert.notNull(authenticationTrustResolver,
178             "authenticationTrustResolver must be specified");
179     }
180 
181     public void destroy() {
182     }
183 
184     public void doFilter(ServletRequest request, ServletResponse response,
185         FilterChain chain) throws IOException, ServletException {
186         if (!(request instanceof HttpServletRequest)) {
187             throw new ServletException("HttpServletRequest required");
188         }
189 
190         if (!(response instanceof HttpServletResponse)) {
191             throw new ServletException("HttpServletResponse required");
192         }
193 
194         FilterInvocation fi = new FilterInvocation(request, response, chain);
195 
196         try {
197             filterSecurityInterceptor.invoke(fi);
198 
199             if (logger.isDebugEnabled()) {
200                 logger.debug("Chain processed normally");
201             }
202         } catch (AuthenticationException authentication) {
203             if (logger.isDebugEnabled()) {
204                 logger.debug("Authentication exception occurred; redirecting to authentication entry point",
205                     authentication);
206             }
207 
208             sendStartAuthentication(fi, authentication);
209         } catch (AccessDeniedException accessDenied) {
210             if (authenticationTrustResolver.isAnonymous(
211                         SecurityContextHolder.getContext().getAuthentication())) {
212                 if (logger.isDebugEnabled()) {
213                     logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point",
214                         accessDenied);
215                 }
216 
217                 sendStartAuthentication(fi,
218                     new InsufficientAuthenticationException(
219                         "Full authentication is required to access this resource"));
220             } else {
221                 if (logger.isDebugEnabled()) {
222                     logger.debug("Access is denied (user is not anonymous); sending back forbidden response",
223                         accessDenied);
224                 }
225 
226                 sendAccessDeniedError(fi, accessDenied);
227             }
228         } catch (ServletException e) {
229             throw e;
230         } catch (IOException e) {
231             throw e;
232         } catch (Throwable otherException) {
233             throw new ServletException(otherException);
234         }
235     }
236 
237     public void init(FilterConfig filterConfig) throws ServletException {
238     }
239 
240     protected void sendAccessDeniedError(FilterInvocation fi,
241         AccessDeniedException accessDenied)
242         throws ServletException, IOException {
243         if (createSessionAllowed) {
244             ((HttpServletRequest) fi.getRequest()).getSession().setAttribute(ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY,
245                 accessDenied);
246         }
247 
248         ((HttpServletResponse) fi.getResponse()).sendError(HttpServletResponse.SC_FORBIDDEN,
249             accessDenied.getMessage()); // 403
250     }
251 
252     protected void sendStartAuthentication(FilterInvocation fi,
253         AuthenticationException reason) throws ServletException, IOException {
254         HttpServletRequest request = (HttpServletRequest) fi.getRequest();
255 
256         int port = portResolver.getServerPort(request);
257         boolean includePort = true;
258 
259         if ("http".equals(request.getScheme().toLowerCase()) && (port == 80)) {
260             includePort = false;
261         }
262 
263         if ("https".equals(request.getScheme().toLowerCase()) && (port == 443)) {
264             includePort = false;
265         }
266 
267         String targetUrl = request.getScheme() + "://" +
268             request.getServerName() + ((includePort) ? (":" + port) : "") +
269             request.getContextPath() + fi.getRequestUrl();
270 
271         if (logger.isDebugEnabled()) {
272             logger.debug(
273                 "Authentication entry point being called; target URL added to Session: " +
274                 targetUrl);
275         }
276 
277         if (createSessionAllowed) {
278             ((HttpServletRequest) request).getSession().setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY,
279                 targetUrl);
280         }
281         
282         // SEC-112: Clear the SecurityContextHolder's Authentication, as the
283         // existing Authentication is no longer considered valid
284         SecurityContextHolder.getContext().setAuthentication(null);
285 
286         authenticationEntryPoint.commence(request,
287             (HttpServletResponse) fi.getResponse(), reason);
288     }
289 }