Last Modified 2016.10.2

Spring Security - Handling access denial

To forward to a /WEB-INF/views/403.jsp page when an unauthorized user requests http://localhost:8080/admin, add the following:

security.xml
<http>
	<access-denied-handler error-page="/403" />
	<intercept-url pattern="/users/bye_confirm" access="permitAll"/>
	<intercept-url pattern="/users/welcome" access="permitAll"/>
	<intercept-url pattern="/users/signUp" access="permitAll"/>
	<intercept-url pattern="/users/login" access="permitAll"/>
	<intercept-url pattern="/images/**" access="permitAll"/>
	<intercept-url pattern="/css/**" access="permitAll"/>
	<intercept-url pattern="/js/**" access="permitAll"/>
	<intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
	<intercept-url pattern="/users/**" access="hasAnyRole('ROLE_ADMIN','ROLE_USER')"/>
	<intercept-url pattern="/bbs/**" access="hasAnyRole('ROLE_ADMIN','ROLE_USER')"/>
	
	<!-- omit -->
	  

Unauthorized users are not sent to the /WEB-INF/views/403.jsp page only with <access-denied-handler error-page=/403 />.
If you do not declare a handler for "/403" in the controller, you will see a 404 error page.
Create a WEB-INF/views/403.jsp file and add the following method to the HomeController:

/403.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ page import="net.java_school.user.User" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>403</title>
<link rel="stylesheet" href="/css/screen.css" type="text/css" />
<script type="text/javascript" src="/js/jquery-3.2.1.min.js"></script>
</head>
<body>
<div id="wrap">

    <div id="header">
        <%@ include file="inc/header.jsp" %>
    </div>
    
    <div id="main-menu">
        <%@ include file="inc/main-menu.jsp" %>
    </div>
    
    <div id="container">
        <div id="content" style="min-height: 800px;">
            <div id="content-categories">Error</div>
            <h1>403</h1>
            Access is Denied.
        </div>
    </div>
    
    <div id="sidebar">
        <h1>Error</h1>
    </div>
    
    <div id="extra">
        <%@ include file="inc/extra.jsp" %>    
    </div>
    
    <div id="footer">
        <%@ include file="inc/footer.jsp" %>
    </div>
        
</div>

</body>
</html>
HomeController.java
@RequestMapping(value="/403", method={RequestMethod.GET,RequestMethod.POST})
public String error403() {
	return "403";
}

For reference, here is the error page setting in web.xml.

web.xml
<error-page>
	<error-code>404</error-code>
	<location>/WEB-INF/views/404.jsp</location>
</error-page>
<error-page>
	<error-code>500</error-code>
	<location>/WEB-INF/views/500.jsp</location>
</error-page>

Run mvn clean compile war:inplace, rerun Tomcat, and visit http://localhost:8080/admin.
If a user has only the ROLE_USER privilege, /WEB-INF/views/403.jsp will be displayed to him.

Implementing an AccessDeniedHandler

If you do not have the privilege required by the requested page, you will see an error page.
If you have business logic that needs to be done before you view the error page, you must implement org.springframework.security.web.access.AccessDeniedHandler.
Modify the security.xml file as follows:

security.xml
<access-denied-handler ref="my403" />

Add the following to security.xml.

security.xml
<beans:bean id="my403" class="net.java_school.spring.MyAccessDeniedHandler">
	<beans:property name="errorPage" value="403" />
</beans:bean>

Create a MyAccessDeniedHandler that implements AccessDeniedHandler as you added it to security.xml.

MyAccessDeniedHandler.java
package net.java_school.spring;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

public class MyAccessDeniedHandler implements AccessDeniedHandler {

	private String errorPage;

	public void setErrorPage(String errorPage) {
		this.errorPage = errorPage;
	}

	@Override
	public void handle(HttpServletRequest req, HttpServletResponse resp, AccessDeniedException e)
			throws IOException, ServletException {
		//TODO: business logic
		req.getRequestDispatcher(errorPage).forward(req, resp);
	}

}

The error page in web.xml does not work with the Spring Security tag.
Because the request is forwarded to these error pages before the view-level security filter works.

Method Security

The simplest way to map an exception to an error page in Spring MVC is to use the SimpleMappingExceptionResolver.
The following configuration maps to error-403 when the org.springframework.security.access.AccessDeniedException exception occurs, and to error when any other exception occurs.
The view resolver interprets error-403 and error as /WEB-INF/views/error-403.jsp and /WEB-INF/views/error.jsp respectively.
Here's what the controller does not have to do.

spring-bbs-servlet.xml
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<property name="defaultErrorView" value="error" />
	<property name="exceptionMappings">
		<props>
			<prop key="AccessDeniedException">
			error-403
			</prop>
		</props>
	</property>
</bean>

After logging in as janedoe@gmail.org/1111, try to unsubscribe at johndoe@gmail.org/1111 from the Bye menu.

UserService.java
@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_USER') and #user.email == principal.username")
	public void bye(User user);

The highlighted section above will work and an org.springframework.security.access.AccessDeniedException exception will occur and you will see /WEB-INF/views/error-403.jsp.
On the Bye page, enter the wrong password for janedoe@gmail.org.

UserServiceImpl.java
@Override
public void bye(User user) {
	String encodedPassword = this.getUser(user.getEmail()).getPasswd();
	boolean check = this.bcryptPasswordEncoder.matches(user.getPasswd(), encodedPassword);
	
	if (check == false) {
		throw new AccessDeniedException("Wrong password!");
	}
	
	userMapper.deleteAuthority(user.getEmail());
	userMapper.delete(user);
}

The highlighted section above will work and an org.springframework.security.access.AccessDeniedException exception will occur and you will see /WEB-INF/views/error-403.jsp.