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