Spring Security 4 - Custom login from example

Posted on October 13, 2017


In this post, I will show how to create and configure a custom login from in Spring MVC application with Spring Security Java Configuration.

Tools and technologies used for this application are-

  • Spring Security 4.2.3.RELEASE
  • Spring MVC 4.3.10.RELEASE
  • Java SE 1.8
  • Maven 3.3.9
  • Eclipse Neon.3
  • Apache Tomcat 7.0.47 
     

Project structure

Review the final project structure build using Maven build tool.

spring-security-custom-login-from.png

You can refer this article to learn - How to create a web project using maven build tool in eclipse IDE.

Jar dependencies

Edit pom.xml file of your maven project and add the following dependencies in it.

<dependencies>
  <!-- Spring MVC Dependency -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.10.RELEASE</version>
  </dependency>
  <!-- Spring Security Dependency -->
  <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>4.2.3.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>4.2.3.RELEASE</version>
  </dependency>
  <!-- Servlet Dependency -->
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
  </dependency>
  <!-- JSP Dependency -->
  <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.1</version>
    <scope>provided</scope>
  </dependency>
  <!-- JSTL Dependency -->
  <dependency>
    <groupId>javax.servlet.jsp.jstl</groupId>
    <artifactId>javax.servlet.jsp.jstl-api</artifactId>
    <version>1.2.1</version>
  </dependency>
  <dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
  </dependency>
</dependencies>

 

Controller class

Create a simple @Controller class under com.boraji.tutorial.spring.controller package as follows. 

MyController.java

package com.boraji.tutorial.spring.controller;

import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MyController {

   @GetMapping("/")
   public String index(Model model) {

      // Get authenticated user name from SecurityContext
      SecurityContext context = SecurityContextHolder.getContext();
      
      model.addAttribute("message", "You are logged in as " 
                     + context.getAuthentication().getName());
      return "index";
   }
}

JSP views

Create a login.jsp file under src\main\webapp\WEB-INF\views folder and write the following code in it.

login.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
  pageEncoding="ISO-8859-1"%>
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html >
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>BORAJI.COM</title>
</head>
<body>
  <h2>Spring Security 4 - Custom login form example</h2>
  <hr />
  <h4>Login Form</h4>

  <form action='<spring:url value="/signin"/>' method="post">
    <table>
      <tr>
        <td>Username</td>
        <td><input type="text" name="userid"></td>
      </tr>
      <tr>
        <td>Password</td>
        <td><input type="password" name="passwd"></td>
      </tr>
      <tr>
        <td><button type="submit">Login</button></td>
      </tr>
    </table>
  </form>
  <br/>
  <c:if test="${not empty sessionScope.message}">
    <span style="color:green"><c:out value="${sessionScope.message}"/></span>
    <c:remove var="message" scope="session" />
  </c:if>
</body>
</html>

When user make a request to our web application, Spring Security checks whether the user is authenticated or not. If user is not authenticated, Spring Security will redirect the user to login page.

Now create an index.jsp file under src\main\webapp\WEB-INF\views folder as follows.

index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
  pageEncoding="ISO-8859-1"%>
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<!DOCTYPE html >
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>BORAJI.COM</title>
</head>
<body>
  <h2>Spring Security 4 - Custom login form example</h2>
  <hr />
  <h4>${message}</h4>
  <br>
  <a href='<spring:url value="/signout"/>'>Logout</a>
</body>
</html>

User will be redirected to the index.jsp (i.e. URL /) after authenticating successfully, otherwise login page with error message. 

 

Spring security configuration class

To configure the custom login form, create a @Configuration class by extending the WebSecurityConfigurerAdapter class as follows.

WebSecurityConfig.java

package com.boraji.tutorial.security.config;

import org.springframework.security.authentication.BadCredentialsException;
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.GrantedAuthority;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.inMemoryAuthentication()
      .withUser("admin").password("admin123").roles("USER");
   }

   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests().anyRequest().hasAnyRole("USER","ADMIN")
      .and()
      .authorizeRequests().antMatchers("/login**").permitAll()
      .and()
      .formLogin()
      .loginPage("/login") // Specifies the login page URL
      .loginProcessingUrl("/signin") // Specifies the URL,which is used 
                                     //in action attribute on the <from> tag
      .usernameParameter("userid")  // Username parameter, used in name attribute
                                    // of the <input> tag. Default is 'username'.
      .passwordParameter("passwd")  // Password parameter, used in name attribute
                                    // of the <input> tag. Default is 'password'.
      .successHandler((req,res,auth)->{    //Success handler invoked after successful authentication
         for (GrantedAuthority authority : auth.getAuthorities()) {
            System.out.println(authority.getAuthority());
         }
         System.out.println(auth.getName());
         res.sendRedirect("/"); // Redirect user to index/home page
      })
//    .defaultSuccessUrl("/")   // URL, where user will go after authenticating successfully.
                                // Skipped if successHandler() is used.
      .failureHandler((req,res,exp)->{  // Failure handler invoked after authentication failure
         String errMsg="";
         if(exp.getClass().isAssignableFrom(BadCredentialsException.class)){
            errMsg="Invalid username or password.";
         }else{
            errMsg="Unknown error - "+exp.getMessage();
         }
         req.getSession().setAttribute("message", errMsg);
         res.sendRedirect("/login"); // Redirect user to login page with error message.
      })
//    .failureUrl("/login?error")   // URL, where user will go after authentication failure.
                                    //  Skipped if failureHandler() is used.
      .permitAll() // Allow access to any URL associate to formLogin()
      .and()
      .logout()
      .logoutUrl("/signout")   // Specifies the logout URL, default URL is '/logout'
      .logoutSuccessHandler((req,res,auth)->{   // Logout handler called after successful logout 
         req.getSession().setAttribute("message", "You are logged out successfully.");
         res.sendRedirect("/login"); // Redirect user to login page with message.
      })
//    .logoutSuccessUrl("/login") // URL, where user will be redirect after successful
                                  //  logout. Ignored if logoutSuccessHandler() is used.
      .permitAll() // Allow access to any URL associate to logout()
      .and()
      .csrf().disable(); // Disable CSRF support
   }
}

Registering springSecurityFilterChain Filter

In Java based configuration, you need to register the spring springSecurityFilterChain which is responsible for all security (protecting the application URLs, validating submitted username and passwords, redirecting to the log in form, etc) within your application.

You can register the spring springSecurityFilterChain using the base class AbstractSecurityWebApplicationInitializer as follows.

package com.boraji.tutorial.security.config;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityWebApplicationInitializer 
      extends AbstractSecurityWebApplicationInitializer {

}

Spring web configuration class

Create a web @Configuration class annotated with @EnableWebMvc and @ComponentScan as follows.

WebConfig.java

package com.boraji.tutorial.security.config;

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.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.boraji.tutorial.spring.controller" })
public class WebConfig extends WebMvcConfigurerAdapter {
   @Override
   public void configureViewResolvers(ViewResolverRegistry registry) {
      registry.jsp().prefix("/WEB-INF/views/").suffix(".jsp");
   }

   @Override
   public void addViewControllers(ViewControllerRegistry registry) {
      registry.addViewController("/login").setViewName("login");
   }
}

The ViewControllerRegistry can be used to forward the request straight through to a view (such as a JSP) without writing a controller class. 

Application initializer class

Create a MvcWebApplicationInitializer class, which will replace our traditional web.xml, to initialize the Servlet container.

Load the WebSecurityConfig and WebConfig classes using the getRootConfigClasses() and getServletConfigClasses() methods as follows.

MvcWebApplicationInitializer.java

package com.boraji.tutorial.security.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MvcWebApplicationInitializer 
         extends AbstractAnnotationConfigDispatcherServletInitializer {

   @Override
   protected Class<?>[] getRootConfigClasses() {
      return new Class[] { WebSecurityConfig.class };
   }

   @Override
   protected Class<?>[] getServletConfigClasses() {
      return new Class[] { WebConfig.class };
   }

   @Override
   protected String[] getServletMappings() {
      return new String[] { "/" };
   }
}

 

Build + Deploy + Run application

Use the following maven commands to build, deploy and run Tomcat server.

mvn clean install  (This command triggers war packaging)

mvn tomcat7:run (This command run embedded tomcat and deploy war file automatically)

You can refer this link to learn how to run the above commands in Eclipse IDE.

 

Enter the http://localhost:8080/ URL in browser's address bar to test our application.

On entering the URL, Spring Security will redirect the user to custom login page (http://localhost:8080/login) asking for username and password as follow.

spring-security-custom-login-from-01.png

On successful login, you will see the index page as follows.

spring-security-custom-login-from-02.png

On clicking logout link, user will be redirected to login page with a message as follows.

spring-security-custom-login-from-03.png

In case of login failure, user will be redirected to login page with an error message as follows.

spring-security-custom-login-from-04.png