Create Custom Access Denied Page in Spring Security Application
In the Spring Security application, when the user enters the wrong login credentials then spring returns an auto-generated message "Bad credentials" to the browser and if the user tries to access any resource which is not accessible to him then Spring returns an exception like access denied or HTTP Status 403 - Forbidden. This is Spring Security's internal error handling mechanism. We cannot do much more with it except provide a more meaningful or little fancy message to the user so the user can understand the error.
Here, we will add our own custom error page to the project so when the user tries to access any non-authorized resource then get a custom error message.
Time for an Example
Now, let's understand by example. We created a maven-based Spring Security project that contains the following files.
// AppConfig.java
This is our application configuration file that implements WebMvcConfugurer
interface to make this MVC application and created a method viewResolver to map our views files(JSP).
The @EnableWebMvc annotation is used to make our application a web application with an MVC pattern.
The @Configuration annotation is used to declare this class as a configuration class and the @ComponentScan annotation is used to scan the component class of our application.
package com.studytonight;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@EnableWebMvc
@Configuration
@ComponentScan("com.studytonight.controller")
public class AppConfig implements WebMvcConfigurer{
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver irvr = new InternalResourceViewResolver();
irvr.setPrefix("WEB-INF/views/");
irvr.setSuffix(".jsp");
irvr.setOrder(0);
return irvr;
}
}
// MainApp.java
This class initialize our web application and creates ServletContext
by using that we register our AppConfig class(above file).
package com.studytonight;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
public class MainApp implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("started");
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
context.setServletContext(servletContext);
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
servlet.setLoadOnStartup(1);
servlet.addMapping("/");
context.close();
}
}
// SecurityAppInitializer.java
This is the Security initializer class that extends AbstractSecurityWebApplicationInitializer
and we passed our SecurityConfig
class so that it can read security configurations.
package com.studytonight;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityAppInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityAppInitializer() {
super(SecurityConfig.class);
}
}
// SecurityConfig.java
This is our security configuration file that extends WebSecurityConfigurerAdapter
class and provides several methods such as configure()
to configure the security. Spring Security provides AuthenticationManagerBuilder
class that works as an Authentication Manager and provides several methods to authenticate the user. Here, we are using inMemoryAuthentication
concept that allows mapping hard-coded user values.
We set a user role by using the roles()
method. Here, we set a user GUEST role and another is REGESTERED role.
We used HttpSecurity
class to configure the login page. The loginPage()
method is used to specify our login.jsp page. We can also use any other name for the login form such as login-form.jsp or user-login.jsp and then specify the mapping to this method. The "/login" value passed here will map to the controller's action and then render the JSP page.
Here, we used accessDeniedPage()
method to configure our custom error handling page.
package com.studytonight;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.User.UserBuilder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
UserBuilder users = User.withDefaultPasswordEncoder();
auth.inMemoryAuthentication()
.withUser(users.username("studytonight").password("abc123").roles("GUEST"))
.withUser(users.username("pro-studytonight").password("abc123").roles("REGISTERED"));
}
@Autowired
protected void configure(HttpSecurity hs) throws Exception {
hs.authorizeRequests()
.antMatchers("/").hasAnyRole("GUEST","REGISTERED")
.antMatchers("/java-course").hasRole("GUEST")
.antMatchers("/premium-courses").hasRole("REGISTERED")
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authenticateTheUser")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.exceptionHandling().accessDeniedPage("/unauthorized");
}
}
// UserController.java
This is our controller class that works as a user request handler and maps user requests with the resources and returns responses accordingly. We created the login() method to render the login page and the home() method to show the index.jsp page and course() method to display course.jsp page.
package com.studytonight.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class UserController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/")
public String home() {
return "index";
}
@GetMapping("/java-course")
public String course() {
return "course";
}
@GetMapping("/premium-courses")
public String premiumCourse() {
return "premium-courses";
}
@GetMapping("/unauthorized")
public String unauthorized() {
return "unauthorized-user";
}
}
View Files
These are views files of our project that displayed to the browser. See the code.
// course.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Course Page</title>
</head>
<body>
<h2>List of Courses</h2>
<ul>
<li>Java</li>
<li>Python</li>
<li>C++</li>
<li>Linux</li>
</ul>
</body>
</html>
// index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Home Page</title>
</head>
<body>
<h2>Welcome to Studytonight!</h2>
<h3>
<a href="java-course">Study Java</a>
</h3>
<h2>
<a href="premium-courses">Study Premium Courses</a>
</h2>
<br><br>
<form:form
action="${pageContext.request.contextPath}/logout"
method="post">
<input type="submit" value="logout">
</form:form>
</body>
</html>
// login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login Page</title>
</head>
<body>
<form:form
action="${pageContext.request.contextPath}/authenticateTheUser"
method="post">
<c:if test="${param.error!=null}">
<p style="color: red">You entered wrong credentials!</p>
</c:if>
<c:if test="${param.logout!=null}">
<p style="color: green">You have successfully logged out.!</p>
</c:if>
<label for="name">Enter User Name</label>
<input type="text" name="username">
<br>
<br>
<label for="password">Enter Password</label>
<input type="password" name="password">
<br>
<br>
<input type="submit" value="Login">
</form:form>
</body>
</html>
// premium-courses.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Course Page</title>
</head>
<body>
<h2>List of Premium Courses</h2>
<ul>
<li>Spring Framework</li>
<li>Pandas</li>
<li>Spring Security</li>
</ul>
</body>
</html>
// unauthorize-user.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Unauthorized Page</title>
</head>
<body>
<h2>Error: You are not Authorized to access this Page</h2>
</body>
</html>
// pom.xml
This file contains all the dependencies of this project such as spring jars, servlet jars, etc. Put these dependencies into your project to run the application.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.studytonight</groupId>
<artifactId>springwithsecurity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<spring.version>5.2.8.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/jstl/jstl -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl-api -->
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.4.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.4.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-taglibs -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.4.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Project Structure
After creating these files our project will look like the below. You can refer to this to understand the directory structure of the project.
Run the Application
After successfully completing the project and adding the dependencies run the application and you will get the output as below.
This is our own login page that will be submitted to /authenticeTheUser URL and match the username and password with the credentials provided in the SecurityConfig.java file.
Provide Correct Username and Password
Home page
Now, you are successfully logged in to the application. This is our index.jsp file renders as a home page to the browser.
Click on the First link "Study Java" and the below web page will be open.
Access Denied
If the user guest user tries to access the premium course then he gets an access denied message. this is the page that we created and configured using the accessDeniedPage() method in the configuration file.