SEC-257: ExceptionTranslationFilter to use AccessDeniedHandler.

This commit is contained in:
Ben Alex 2006-04-28 06:52:50 +00:00
parent 9a90e4e1aa
commit cc07f620df
8 changed files with 195 additions and 37 deletions

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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());
}

View File

@ -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>

View File

@ -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>

View File

@ -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>
<% } %>

View File

@ -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>