소스 검색

SEC-257: ExceptionTranslationFilter to use AccessDeniedHandler.

Ben Alex 19 년 전
부모
커밋
cc07f620df

+ 50 - 0
core/src/main/java/org/acegisecurity/ui/AccessDeniedHandler.java

@@ -0,0 +1,50 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.ui;
+
+import org.acegisecurity.AccessDeniedException;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+
+/**
+ * Used by {@link ExceptionTranslationFilter} to handle an
+ * <code>AccessDeniedException</code>.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface AccessDeniedHandler {
+    //~ Methods ================================================================
+
+    /**
+     * Handles an access denied failure.
+     *
+     * @param request that resulted in an <code>AccessDeniedException</code>
+     * @param response so that the user agent can be advised of the failure
+     * @param accessDeniedException that caused the invocation
+     *
+     * @throws IOException in the event of an IOException
+     * @throws ServletException in the event of a ServletException
+     */
+    public void handle(ServletRequest request, ServletResponse response,
+        AccessDeniedException accessDeniedException)
+        throws IOException, ServletException;
+}

+ 106 - 0
core/src/main/java/org/acegisecurity/ui/AccessDeniedHandlerImpl.java

@@ -0,0 +1,106 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.acegisecurity.ui;
+
+import org.acegisecurity.AccessDeniedException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.IOException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Base implementation of {@link AccessDeniedHandler}.
+ * 
+ * <p>
+ * This implementation sends a 403 (SC_FORBIDDEN) HTTP error code. In addition,
+ * if a {@link #errorPage} is defined, the implementation will perform a
+ * request dispatcher "forward" to the specified error page view. Being a
+ * "forward", the <code>SecurityContextHolder</code> will remain populated.
+ * This is of benefit if the view (or a tag library or macro) wishes to access
+ * the <code>SecurityContextHolder</code>. The request scope will also be
+ * populated with the exception itself, available from the key {@link
+ * #ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY}.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
+    //~ Static fields/initializers =============================================
+
+    public static final String ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY = "ACEGI_SECURITY_403_EXCEPTION";
+    protected final static Log logger = LogFactory.getLog(AccessDeniedHandlerImpl.class);
+
+    //~ Instance fields ========================================================
+
+    private String errorPage;
+
+    //~ Methods ================================================================
+
+    public void handle(ServletRequest request, ServletResponse response,
+        AccessDeniedException accessDeniedException)
+        throws IOException, ServletException {
+        if (errorPage != null) {
+            // Put exception into request scope (perhaps of use to a view)
+            ((HttpServletRequest) request).setAttribute(ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY,
+                accessDeniedException);
+
+            // Perform RequestDispatcher "forward"
+            RequestDispatcher rd = request.getRequestDispatcher(errorPage);
+
+            try {
+                rd.forward(request, response);
+                ((HttpServletResponse)response).setStatus(HttpServletResponse.SC_FORBIDDEN);
+                return;
+            } catch (Exception responseCommitted) {
+                if (logger.isErrorEnabled()) {
+                    logger.error("Error processing " + request.toString(),
+                        responseCommitted);
+                }
+            }
+        }
+
+        // Send 403 (we do this after response has been written)
+        ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN,
+            accessDeniedException.getMessage());
+    }
+
+    /**
+     * The error page to use. Must begin with a "/" and is interpreted relative
+     * to the current context root.
+     *
+     * @param errorPage the dispatcher path to display
+     *
+     * @throws IllegalArgumentException if the argument doesn't comply with the
+     *         above limitations
+     */
+    public void setErrorPage(String errorPage) {
+        if ((errorPage != null) && !errorPage.startsWith("/")) {
+            throw new IllegalArgumentException("ErrorPage must begin with '/'");
+        }
+
+        this.errorPage = errorPage;
+    }
+}

+ 11 - 24
core/src/main/java/org/acegisecurity/ui/ExceptionTranslationFilter.java

@@ -69,13 +69,9 @@ import javax.servlet.http.HttpServletResponse;
  * If an {@link AccessDeniedException} is detected, the filter will determine
  * whether or not the user is an anonymous user. If they are an anonymous
  * user, the <code>authenticationEntryPoint</code> will be launched. If they
- * are not an anonymous user, the filter will respond with a
- * <code>HttpServletResponse.SC_FORBIDDEN</code> (403 error).  In addition,
- * the <code>AccessDeniedException</code> itself will be placed in the
- * <code>HttpSession</code> attribute keyed against {@link
- * #ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY} (to allow access to the stack
- * trace etc). Again, this allows common access denied handling irrespective
- * of the originating security interceptor.
+ * are not an anonymous user, the filter will delegate to the {@link
+ * org.acegisecurity.ui.AccessDeniedHandler}. By default the filter will use
+ * {@link org.acegisecurity.ui.AccessDeniedHandlerImpl}.
  * </p>
  * 
  * <p>
@@ -109,10 +105,10 @@ public class ExceptionTranslationFilter implements Filter, InitializingBean {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(ExceptionTranslationFilter.class);
-    public static final String ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY = "ACEGI_SECURITY_403_EXCEPTION";
 
     //~ Instance fields ========================================================
 
+    private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
     private AuthenticationEntryPoint authenticationEntryPoint;
     private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
     private PortResolver portResolver = new PortResolverImpl();
@@ -199,11 +195,11 @@ public class ExceptionTranslationFilter implements Filter, InitializingBean {
                         "Full authentication is required to access this resource"));
             } else {
                 if (logger.isDebugEnabled()) {
-                    logger.debug("Access is denied (user is not anonymous); sending back forbidden response",
+                    logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
                         exception);
                 }
 
-                sendAccessDeniedError(request, response, chain,
+                accessDeniedHandler.handle(request, response,
                     (AccessDeniedException) exception);
             }
         }
@@ -231,20 +227,6 @@ public class ExceptionTranslationFilter implements Filter, InitializingBean {
         return createSessionAllowed;
     }
 
-    protected void sendAccessDeniedError(ServletRequest request,
-        ServletResponse response, FilterChain chain,
-        AccessDeniedException accessDenied)
-        throws ServletException, IOException {
-        if (createSessionAllowed) {
-            ((HttpServletRequest) request).getSession()
-             .setAttribute(ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY,
-                accessDenied);
-        }
-
-        ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN,
-            accessDenied.getMessage()); // 403
-    }
-
     protected void sendStartAuthentication(ServletRequest request,
         ServletResponse response, FilterChain chain,
         AuthenticationException reason) throws ServletException, IOException {
@@ -274,6 +256,11 @@ public class ExceptionTranslationFilter implements Filter, InitializingBean {
             (HttpServletResponse) response, reason);
     }
 
+    public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
+        Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required");
+        this.accessDeniedHandler = accessDeniedHandler;
+    }
+
     public void setAuthenticationEntryPoint(
         AuthenticationEntryPoint authenticationEntryPoint) {
         this.authenticationEntryPoint = authenticationEntryPoint;

+ 7 - 2
core/src/test/java/org/acegisecurity/ui/ExceptionTranslationFilterTests.java

@@ -113,17 +113,22 @@ public class ExceptionTranslationFilterTests extends TestCase {
         // Setup SecurityContextHolder, as filter needs to check if user is anonymous
         SecurityContextHolder.getContext().setAuthentication(null);
 
+        // Setup a new AccessDeniedHandlerImpl that will do a "forward"
+        AccessDeniedHandlerImpl adh = new AccessDeniedHandlerImpl();
+        adh.setErrorPage("/error.jsp");
+
         // Test
         ExceptionTranslationFilter filter = new ExceptionTranslationFilter();
         filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
                 "/login.jsp"));
+        filter.setAccessDeniedHandler(adh);
 
         MockHttpServletResponse response = new MockHttpServletResponse();
         filter.doFilter(request, response, chain);
         assertEquals(403, response.getStatus());
         assertEquals(AccessDeniedException.class,
-            request.getSession()
-                   .getAttribute(ExceptionTranslationFilter.ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY)
+            request.getAttribute(
+                AccessDeniedHandlerImpl.ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY)
                    .getClass());
     }
 

+ 5 - 1
doc/xdocs/upgrade/upgrade-090-100.html

@@ -81,7 +81,11 @@ applications:
 	AbstractProcessingFilter.onUnsuccessfulAuthentication(HttpServletRequest, HttpServletResponse)
     has changed it signature (SEC-238). If subclassing, please override the new signature.
 	</li>
-
+	
+	<li>
+	ExceptionTranslationFilter no longer provides a sendAccessDenied() method. Use the
+	new AccessDeniedHandler instead if custom handling is required.
+	</li>
 	
     </ul>
 

+ 0 - 5
samples/contacts/src/main/webapp/filter/WEB-INF/web.xml

@@ -98,11 +98,6 @@
  	<welcome-file-list>
 		<welcome-file>index.jsp</welcome-file>
 	</welcome-file-list>
-	
-	<error-page>
-		<error-code>403</error-code>
-		<location>/error.html</location>
-	</error-page>
 
   	<taglib>
       <taglib-uri>/spring</taglib-uri>

+ 16 - 0
samples/contacts/src/main/webapp/filter/accessDenied.jsp

@@ -0,0 +1,16 @@
+<%@ page import="org.acegisecurity.context.SecurityContextHolder" %>
+<%@ page import="org.acegisecurity.Authentication" %>
+<%@ page import="org.acegisecurity.ui.AccessDeniedHandlerImpl" %>
+
+<h1>Sorry, access is denied</h1>
+
+
+<p>
+<%= request.getAttribute(AccessDeniedHandlerImpl.ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY)%>
+
+<p>
+
+<%		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+		if (auth != null) { %>
+			Authentication object as a String: <%= auth.toString() %><BR><BR>
+<%      } %>

+ 0 - 5
samples/contacts/src/main/webapp/filter/error.html

@@ -1,5 +0,0 @@
-<html>
-	<title>Access denied!</title>
-	<h1>Access Denied</h1>
-	We're sorry, but you are not authorized to perform the requested operation.
-</html>