From cc07f620df5d703cf07b80ebcdc7da255a6278c0 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 28 Apr 2006 06:52:50 +0000 Subject: [PATCH] SEC-257: ExceptionTranslationFilter to use AccessDeniedHandler. --- .../acegisecurity/ui/AccessDeniedHandler.java | 50 +++++++++ .../ui/AccessDeniedHandlerImpl.java | 106 ++++++++++++++++++ .../ui/ExceptionTranslationFilter.java | 35 ++---- .../ui/ExceptionTranslationFilterTests.java | 9 +- doc/xdocs/upgrade/upgrade-090-100.html | 6 +- .../src/main/webapp/filter/WEB-INF/web.xml | 5 - .../src/main/webapp/filter/accessDenied.jsp | 16 +++ .../src/main/webapp/filter/error.html | 5 - 8 files changed, 195 insertions(+), 37 deletions(-) create mode 100644 core/src/main/java/org/acegisecurity/ui/AccessDeniedHandler.java create mode 100644 core/src/main/java/org/acegisecurity/ui/AccessDeniedHandlerImpl.java create mode 100644 samples/contacts/src/main/webapp/filter/accessDenied.jsp delete mode 100644 samples/contacts/src/main/webapp/filter/error.html diff --git a/core/src/main/java/org/acegisecurity/ui/AccessDeniedHandler.java b/core/src/main/java/org/acegisecurity/ui/AccessDeniedHandler.java new file mode 100644 index 0000000000..00cb2a8a82 --- /dev/null +++ b/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 + * AccessDeniedException. + * + * @author Ben Alex + * @version $Id$ + */ +public interface AccessDeniedHandler { + //~ Methods ================================================================ + + /** + * Handles an access denied failure. + * + * @param request that resulted in an AccessDeniedException + * @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; +} diff --git a/core/src/main/java/org/acegisecurity/ui/AccessDeniedHandlerImpl.java b/core/src/main/java/org/acegisecurity/ui/AccessDeniedHandlerImpl.java new file mode 100644 index 0000000000..5ba873556e --- /dev/null +++ b/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}. + * + *

+ * 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 SecurityContextHolder will remain populated. + * This is of benefit if the view (or a tag library or macro) wishes to access + * the SecurityContextHolder. The request scope will also be + * populated with the exception itself, available from the key {@link + * #ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY}. + *

+ * + * @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; + } +} diff --git a/core/src/main/java/org/acegisecurity/ui/ExceptionTranslationFilter.java b/core/src/main/java/org/acegisecurity/ui/ExceptionTranslationFilter.java index b4b47ac057..1c7a127c39 100644 --- a/core/src/main/java/org/acegisecurity/ui/ExceptionTranslationFilter.java +++ b/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 authenticationEntryPoint will be launched. If they - * are not an anonymous user, the filter will respond with a - * HttpServletResponse.SC_FORBIDDEN (403 error). In addition, - * the AccessDeniedException itself will be placed in the - * HttpSession 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}. *

* *

@@ -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; diff --git a/core/src/test/java/org/acegisecurity/ui/ExceptionTranslationFilterTests.java b/core/src/test/java/org/acegisecurity/ui/ExceptionTranslationFilterTests.java index ca5a6adada..ab60766b9b 100644 --- a/core/src/test/java/org/acegisecurity/ui/ExceptionTranslationFilterTests.java +++ b/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()); } diff --git a/doc/xdocs/upgrade/upgrade-090-100.html b/doc/xdocs/upgrade/upgrade-090-100.html index 012a04e802..99af9438ac 100644 --- a/doc/xdocs/upgrade/upgrade-090-100.html +++ b/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. - + +

  • + ExceptionTranslationFilter no longer provides a sendAccessDenied() method. Use the + new AccessDeniedHandler instead if custom handling is required. +
  • diff --git a/samples/contacts/src/main/webapp/filter/WEB-INF/web.xml b/samples/contacts/src/main/webapp/filter/WEB-INF/web.xml index 5da5284a29..a0363c0d36 100644 --- a/samples/contacts/src/main/webapp/filter/WEB-INF/web.xml +++ b/samples/contacts/src/main/webapp/filter/WEB-INF/web.xml @@ -98,11 +98,6 @@ index.jsp - - - 403 - /error.html - /spring diff --git a/samples/contacts/src/main/webapp/filter/accessDenied.jsp b/samples/contacts/src/main/webapp/filter/accessDenied.jsp new file mode 100644 index 0000000000..816d4526f8 --- /dev/null +++ b/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" %> + +

    Sorry, access is denied

    + + +

    +<%= request.getAttribute(AccessDeniedHandlerImpl.ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY)%> + +

    + +<% Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { %> + Authentication object as a String: <%= auth.toString() %>

    +<% } %> diff --git a/samples/contacts/src/main/webapp/filter/error.html b/samples/contacts/src/main/webapp/filter/error.html deleted file mode 100644 index 5d461b5a25..0000000000 --- a/samples/contacts/src/main/webapp/filter/error.html +++ /dev/null @@ -1,5 +0,0 @@ - - Access denied! -

    Access Denied

    - We're sorry, but you are not authorized to perform the requested operation. - \ No newline at end of file