In this Spring Security Custom Login Form Example, we will learn how to build a Spring MVC application which will be protected by Spring Security. We will be building our own login form rather than using the default form provided by Spring Security
In our application the home page which will be accessible to everyone, and the the user page will be accessible to only to the user with user or admin rights and the admin page which will be accessible to only to the user with admin rights.
When the user tries to access the restricted page the user will be redirected to the custom login form which we have build and asked to log in, if the credentials entered matches the privilege then only the user will be allowed in.
Folder Structure:
- Create a simple Maven Project “SpringSecurityCustomLogin” and create a package for our source files “com.javainterviewpoint” under src/main/java
- Now add the following dependency in the POM.xml
<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 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javainterviewpoint</groupId> <artifactId>SpringSecurityCustomLogin</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>SpringSecurityCustomLogin Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>${security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${security.version}</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> <properties> <spring.version>5.1.1.RELEASE</spring.version> <security.version>5.0.7.RELEASE</security.version> <jdk.version>1.8</jdk.version> </properties> <build> <finalName>SpringSecurityCustomLogin</finalName> </build> </project>
- Create the Java class HelloController.java, SpringMvcConfig.java, SpringMvcInitializer.java, SpringSecurityConfig.java and SpringSecurityInitializer.java under com.javainterviewpoint folder.
- Place the view files index.jsp, user.jsp,login.jsp and admin.jsp are put under the sub directory under WEB-INF/Jsp
Spring Security Custom Login Form – Java Configuration
Spring Security Configuration
package com.javainterviewpoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; 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.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; @Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/user").hasAnyRole("USER,ADMIN") .antMatchers("/admin").hasRole("ADMIN") .and() .formLogin().loginPage("/login").failureUrl("/error") .usernameParameter("username").passwordParameter("password") .and() .csrf().disable(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user").password(passwordEncoder().encode("password")).roles("USER") .and() .withUser("admin").password(passwordEncoder().encode("password")).roles("ADMIN"); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
- @EnableWebSecurity annotation enables spring security configuration which is defined in WebSecurityConfigurerAdapter
- We have extended WebSecurityConfigurerAdapter, which allows us to override spring’s security default feature. In our example we want all the requests to be authenticated using the custom authentication.
- configure() method configures the HttpSecurity class which authorizes each HTTP request which has been made. In our example ‘/user’ should be allowed for the user with USER/ADMIN role and ‘/admin’ should be allowed for the user with ADMIN role.
- authorizeRequests() .antMatchers(“/user”).hasAnyRole(“USER, ADMIN”) .antMatchers(“/admin”).hasRole(“ADMIN”) –> All requests to must be authorized or else they should be rejected.
- formLogin() –> Configures custom login page at URL /login and when the login credentials are invalid then the user will be redirected to /error
- .csrf().disable() –> Disables CSRF protection
@Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/user").hasAnyRole("USER,ADMIN") .antMatchers("/admin").hasRole("ADMIN") .and() .formLogin().loginPage("/login").failureUrl("/error") .usernameParameter("username").passwordParameter("password") .and() .csrf().disable(); }
- configureGlobal() method configures the AuthenticationManagerBuilder class with the valid credentials and the allowed roles. The AuthenticationManagerBuilder class creates the AuthenticationManger which is responsible for authenticating the credentials. In our example we have used the inMemoryAuthentication, you can choose other authentication types such JDBC, LDAP.
Equivalent XML Configuration
<http> <intercept-url pattern="/user" access="hasAnyRole('ROLE_ADMIN','ROLE_USER')" /> <intercept-url pattern="/admin" access="hasRole('ROLE_ADMIN')" /> <form-login login-page="/login" authentication-failure-url="/error" username-parameter="username" password-parameter="password" /> <csrf/> <logout logout-success-url="/logout" /> </http> <authentication-manager> <authentication-provider> <user-service> <user name="user" password="password" authorities="ROLE_USER" /> <user name="admin" password="password" authorities="ROLE_ADMIN" /> </user-service> </authentication-provider> </authentication-manager>
Registering springSecurityFilter
Spring Security will be implemented using DelegatingFilterProxy, in order to register it with the Spring container we will be extending AbstractSecurityWebApplicationInitializer. This will enable Spring to register DelegatingFilterProxy and use the springSecurityFilterChain Filter
package com.javainterviewpoint; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer { }
Equivalent XML Configuration
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Spring MVC Configuration
We will be registering the InternalResourceViewResolver as our ViewResolver
package com.javainterviewpoint; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; @Configuration @EnableWebMvc @ComponentScan(basePackages = { "com.javainterviewpoint"}) public class SpringMvcConfig { @Bean public InternalResourceViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setViewClass(JstlView.class); viewResolver.setPrefix("/WEB-INF/Jsp/"); viewResolver.setSuffix(".jsp"); return viewResolver; } }
Equivalent XML Configuration
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix"> <value>/WEB-INF/Jsp/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean>
SpringMvcInitializer.java
We have extended AbstractAnnotationConfigDispatcherServletInitializer class which in turn implements WebApplicationInitializer, the WebApplicationInitializer configures the servletContext programatically and hence web.xml is not required [From Servlet 3.0 onwards]
package com.javainterviewpoint; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { SpringSecurityConfig.class, SpringMvcConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
HelloController.java
package com.javainterviewpoint; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class HelloController { @RequestMapping("/admin") public String goAdmin() { return "admin"; } @RequestMapping("/user") public String goUser() { return "user"; } @RequestMapping("/error") public String error(ModelMap model) { model.addAttribute("error", "true"); return "login"; } @RequestMapping("/login") public String login() { return "login"; } @RequestMapping("/logout") public String logout(ModelMap model) { Authentication authentication = SecurityContextHolder.getContext() .getAuthentication(); authentication.setAuthenticated(false); return "login"; } }
- Our HelloController has two main methods
- user() – when the user hits on the URL “/user” this method gets called and and the user will be redirected to the login page, only when the user keys in a valid credentials he will be allowed see the “user.jsp”.
- admin() – when the user hits on the URL “/admin” this method gets called and the user will be redirected to the login page, only when the user keys in a valid credentials he will be allowed see the “admin.jsp”.
- Additionally we have added login(), logout() and error() methods for redirecting to the respective pages.
index.jsp
<html> <body> <h2>Welcome to Spring Security Custom Login Form Example</h2> <h4><a href="admin">Access Admin Page</a> || <a href="user">Access User Page</a></h4> </body> </html>
user.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Spring Security Custom Login Form</title> </head> <body> <h2>Spring Security Custom Login Form - User Page</h2> <h3>Welcome User!!!</h3> <br> <form action="logout" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> <input type="submit" value="Logout"> </form> </body> </html>
admin.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Spring Security Custom Login Form</title> </head> <body> <h2>Spring Security Custom Login Form - Admin Page</h2> <h3>Welcome Admin!!!</h3> <br> <form action="logout" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> <input type="submit" value="Logout"> </form> </body> </html>
login.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <title>Custom Login Page</title> </head> <body> <h3>Custom Login Page</h3> <% String error = (String) request.getAttribute("error"); if (error != null && error.equals("true")) { out.println("<h4 style=\"color:red\">Invalid login credentials. Please try again!!</h4>"); } %> <form action="<c:url value='login' />" method='POST'> <table> <tr> <td>User:</td> <td><input type='text' name='username' value=''></td> </tr> <tr> <td>Password:</td> <td><input type='password' name='password' /></td> </tr> <tr> <td><input name="submit" type="submit" value="Login" /></td> <td><input name="reset" type="reset" /> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /></td> </tr> </table> </form> </body> </html>
we have added CSRF parameters in our login page to prevent the CSRF attacks.
<input name="reset" type="reset" /> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
Output
Hit on the URL : http://localhost:8080/SpringSecurityCustomLogin/
Hit on the URL : http://localhost:8080/SpringSecurityCustomLogin/admin
You will be asked to login, if invalid password is entered you will get the below error and again redirected to the login page.
Only when you enter the valid credentials you will be allowed to see the admin page.
Leave a Reply