I still don’t get why you had to implement your own access handler… I have currently faced same task:
<security:access-denied-handler error-page="/accessDenied"/> - works like charm.
Don’t forget to specify handler in your Controller:
@RequestMapping(value = "/accessDenied")
public String accessDenied() {
return "accessDenied"; // logical view name
}
Update for Spring Boot(2014 Oct):
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedHandler(customHandler) OR .accessDeniedPage("/somePage.html").and
.formLogin()
.failureHandler(ajaxAuthenticationFailureHandler)}
Nowadays we don’t really return views for such task since angular js kicks in so you can use your failure/success handler and return tailored JSON responses. For us it was sufficient to use failure handler but you get to choose where you want your control to kick in. We generally don’t use view resolvers as there are UI tiles frameworks(such as angular partials) able to construct pieces into single page for you. Html pieces are stored on the server and served simply as static resources.
Lets play with Embedded Tomcat to achieve similar behavior to web.xml !
@Configuration
@EnableAutoConfiguration
public class ApplicationWebXml extends SpringBootServletInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.profiles(addDefaultProfile())
.showBanner(false)
.sources(Application.class);
}
//required for container customizer to work, the numerous tutorials didn't work for me, so I simply tried overriding the default one
@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
return tomcat;
}
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer(
) {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory containerFactory = (TomcatEmbeddedServletContainerFactory) container;
containerFactory.setSessionTimeout(1); // just for your interest, remove as necessary
containerFactory.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN,"/views/accessDenied.html"),
new ErrorPage(HttpStatus.NOT_FOUND,"/views/notFound.html"));
containerFactory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
connector.setPort(8082);// just for your interest, remove as necessary
}
});
}
};
}
}
In this tutorial we will discuss about the using custom access denied page or 403 page(Customized Access Denied Page in Spring Security) for access control for a specific resource or url.
Access denied page appears when an unauthorized user which has not privileged for viewing a page/section , try to view it using their login & password. For example, when an unprivileged user tries to view an admin section pages , an error page will appear showing the error code 403 and a message “Access is denied”. In this section, we will customize the access denied page.
In the below example, we will ensure secure URL access by providing auto generated Login form using Spring Security. User needs to provide correct login credential to view the page. For accessing admin section, you need to provide admin login and password. While for user section, both admin and user login are permitted. If you access with non admin privileges then it redirect to the custom access denied page as follows.
There are two ways for using custom denied page.
1. access-denied-handler
<security:http auto-config="true"> <security:intercept-url pattern="/admin*" access="ROLE_ADMIN" /> <security:logout logout-success-url="/index" /> <security:intercept-url pattern="/index*" access="ROLE_USER,ROLE_ADMIN" /> <security:logout logout-success-url="/index" /> <security:access-denied-handler error-page="/403"/> </security:http>
Popular Tutorials
- Spring Tutorial
- Spring MVC Web Tutorial
- Spring Boot Tutorial
- Spring Security Tutorial
- Spring AOP Tutorial
- Spring JDBC Tutorial
- Spring HATEOAS
- Microservices with Spring Boot
- REST Webservice
- Core Java
- Hibernate Tutorial
- Spring Batch
Tag- access-denied-handler
Defines the access-denied strategy that should be used. An access denied page can be defined or a reference to an AccessDeniedHandler instance.
Attribute- error-page
The access denied page that an authenticated user will be redirected to if they request a page which they don’t have the authority to access.
It means the user with authority as ROLE_ADMIN can have access to URL /admin . Also, the URL /index is open for both type of users having authority ROLE_USER or ROLE_ADMIN . If non authorized user try to access /admin, a “http 403 access denied custom page(403.jsp)” will be displayed.
2. AccessDeniedHandler:
In second way, create a class and implements Spring’s AccessDeniedHandler, override handle() method and put your access denied logic inside.
package com.dineshonjava.error.handler; 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 accessDeniedUrl; public MyAccessDeniedHandler() { } public MyAccessDeniedHandler(String accessDeniedUrl) { this.accessDeniedUrl = accessDeniedUrl; } @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.sendRedirect(accessDeniedUrl); request.getSession().setAttribute("message", " Sorry user_dineshonjava You don't have privileges to view this page!!!"); } public String getAccessDeniedUrl() { return accessDeniedUrl; } public void setAccessDeniedUrl(String accessDeniedUrl) { this.accessDeniedUrl = accessDeniedUrl; } }
Declares above Spring bean.
<bean id="accessDeniedHandler" class="com.dineshonjava.error.handler.MyAccessDeniedHandler"> <property name="accessDeniedUrl" value="403" /> </bean> <security:http auto-config="true"> <security:intercept-url pattern="/admin*" access="ROLE_ADMIN" /> <security:logout logout-success-url="/index" /> <security:intercept-url pattern="/index*" access="ROLE_USER,ROLE_ADMIN" /> <security:logout logout-success-url="/index" /> <security:access-denied-handler ref="accessDeniedHandler"/> </security:http>
Some helpful example related to this section is given below :
Example related to Spring Security Authorized Access Using Auto generated Login Form, Click Here .
Example related to Spring Security Authorized Access Using Custom Login Form, Click Here .
Example related to Spring Security Authorized Access with Customized Login from Database Click Here .
In last Spring Security Authorized Access Control Example , if non authorized user try to access a protected page, default “http 403 access denied” will be display :
Project Directory structure-
Creating welcome page (welcome.jsp)
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>WELCOME TO SECURE AREA</title> </head> <body> <h1>Message : ${message}</h1> <h1>Author : ${author}</h1> <a href='<c:url value="/j_spring_security_logout" />' > Logout</a> </body> </html>
Assume below is your customized 403 page:
403.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>HTTP Status 403 - Access is denied</title> </head> <body> <h1>Message : ${message}</h1> </body> </html>
Creating AdminController class (AdminController.java)
AdminController.java
package com.dineshonjava.admin.controller; import java.security.Principal; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * @author Dinesh Rajput * */ @Controller public class AdminController { @RequestMapping(value = "/admin", method = RequestMethod.GET) public String welcomeAdmin(ModelMap model, Principal principal) { String username = principal.getName(); model.addAttribute("author", username); model.addAttribute("message", "Hello Spring Security - ADMIN PAGE"); return "welcome"; } @RequestMapping(value = "/index", method = RequestMethod.GET) public String printMessage(ModelMap model, Principal principal) { String username = principal.getName(); model.addAttribute("author", username); model.addAttribute("message", "Hello Spring Security - USER LOGIN"); return "welcome"; } @RequestMapping(value = "/403", method = RequestMethod.GET) public String accessDenied(ModelMap model, Principal principal) { String username = principal.getName(); model.addAttribute("message", "Sorry "+username+" You don't have privileges to view this page!!!"); return "403"; } }
Spring Securing Configuration file (sdnext-security.xml)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <security:http auto-config="true"> <security:intercept-url pattern="/admin*" access="ROLE_ADMIN" /> <security:logout logout-success-url="/index" /> <security:intercept-url pattern="/index*" access="ROLE_USER,ROLE_ADMIN" /> <security:logout logout-success-url="/index" /> <security:access-denied-handler error-page="/403"/> </security:http> <security:authentication-manager> <security:authentication-provider> <security:user-service> <security:user name="user_dineshonjava" password="sweetu" authorities="ROLE_USER" /> <security:user name="admin_dineshonjava" password="sweetu" authorities="ROLE_ADMIN" /> </security:user-service> </security:authentication-provider> </security:authentication-manager> </beans>
Spring Configuration File (sdnext-servlet.xml)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <context:component-scan base-package="com.dineshonjava.admin" /> <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> </beans>
Running the example
Now, if non authorized user is access the protected page, your customize 403 page will be displayed :
When you try to access the admin using below URL :
http://localhost:8080/sdnext/spring_security_login;jsessionid=A576B0C8EFB29231141D4FD3DE15A751
You will get the below page :
When you try to access the admin page/section using a normal user login(Username : user_dineshonjava, Password: sweetu), you will get the below customized access denied page :
Download Source Code + Libs
SpringSecurityCustomAccessDeniedPage.zip
References-
https://www.dineshonjava.com/spring-security-form-based-login-example/
Spring Security
Spring Security documentation
Spring Security Related Posts
- Spring Security Interview Questions and Answers
- Spring Security Java Based Configuration with Example
- Spring Security XML Namespace Configuration Example
- Spring Security XML Based Hello World Example
- Spring Security form-based login example
- Spring Security Login Form Based Example Using Database
- Spring Security Authentication Example Using HTTP Basic
- Spring Security Authorized Access Control Example
- Spring Security Customized Access Denied Page
- Spring Security Custom Error Message
- Spring Security Logout Example
- Spring Security Fetch Logged in Username
- Spring Security Password Hashing
Last Modified 2021.11.29
Add the following to redirect access denied user requests to the /WEB-INF/views/403.jsp page.
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 -->
You need to declare a handler for «/403» in the controller with the above configuration. Otherwise, you will get a 404 error.
Create a 403.jsp file and add the following method to your 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"; }
Exclude 403 error from error page configuration 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 the user has only the ROLE_USER privilege, /WEB-INF/views/403.jsp will be displayed.
Implementing AccessDeniedHandler
If you have business logic to perform before showing the user an error page, you should configure your access denied handler by implementing the org.springframework.security.web.access.AccessDeniedHandler.
Create a MyAccessDeniedHandler that implements AccessDeniedHandler.
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); } }
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>
Modify the security.xml file as follows:
security.xml
<access-denied-handler ref="my403" />
The Spring Security tag does not work for error pages set in web.xml 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 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.
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 signing up and logging in with janedoe@gmail.org/1111, try to withdraw membership with 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 part above will work and occur an org.springframework.security.access.AccessDeniedException, and you will see /WEB-INF/views/error-403.jsp.
On the Bye page, try to withdraw membership with janedoe@gmail.org and the wrong password.
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 part above will occur an org.springframework.security.access.AccessDeniedException, and you will see /WEB-INF/views/error-403.jsp.