Spring Security 5 - Custom UserDetailsService example

Posted on January 6, 2018


The UserDetailsService is a core interface in Spring Security framework, which is used to retrieve the user’s authentication and authorization information. 

It has a single read-only method named as loadUserByUsername() which locate the user based on the username.

This post shows you how to create a custom UserDetailsService for authentication in Spring MVC web application.

Tools and technologies used for this application are -

  • Spring Security 5.0.0.RELEASE
  • Spring MVC 5.0.2.RELEASE
  • Servlet API 3.1.0
  • Java SE 9
  • Maven 3.5.2
  • Eclipse Oxygen.2 Release (4.7.2)
  • Jetty Maven plugin 9.4.8

Project structure

Final project structure of our application will look like as follows.

spring-security-userdetails.png

Related - How to create a web project using maven build tool in eclipse IDE.

Jar dependencies

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

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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.boraji.tutorial.springsecurity</groupId>
  <artifactId>spring-security-user-details-service-example</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Spring Security - Custom UserDetailsService example</name>
  <packaging>war</packaging>
  <properties>
    <maven.compiler.source>9</maven.compiler.source>
    <maven.compiler.target>9</maven.compiler.target>
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-web</artifactId>
      <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-config</artifactId>
      <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>javax.servlet.jsp-api</artifactId>
      <version>2.3.1</version>
      <scope>provided</scope>
    </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>

  <build>
    <plugins>
      <!-- Maven jetty plugin for testing war -->
      <plugin>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>9.4.8.v20171121</version>
      </plugin>
    </plugins>
  </build>
</project>

Creating Custom UserDetailsService

To create a custom user service, you need to implement the UserDetailsService interface and override the loadUserByUsername() method.

Create UserDetailsServiceImp class under com.boraji.tutorial.spring.service package as follows.

UserDetailsServiceImp.java

package com.boraji.tutorial.spring.service;

import org.springframework.security.core.userdetails.User.UserBuilder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import com.boraji.tutorial.spring.model.User;

public class UserDetailsServiceImp implements UserDetailsService {
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

    /*Here we are using dummy data, you need to load user data from
     database or other third party application*/
    User user = findUserbyUername(username);

    UserBuilder builder = null;
    if (user != null) {
      builder = org.springframework.security.core.userdetails.User.withUsername(username);
      builder.password(new BCryptPasswordEncoder().encode(user.getPassword()));
      builder.roles(user.getRoles());
    } else {
      throw new UsernameNotFoundException("User not found.");
    }

    return builder.build();
  }

  private User findUserbyUername(String username) {
    if(username.equalsIgnoreCase("admin")) {
      return new User(username, "admin123", "ADMIN");
    }
    return null;
  }
}

Next, create User model class under com.boraji.tutorial.spring.model package as follows.

User.java

package com.boraji.tutorial.spring.model;

public class User {
  private String username;
  private String password;
  private String[] roles;

  public User(String username, String password, String... roles) {
    this.username = username;
    this.password = password;
    this.roles = roles;
  }

  // Getter and Setter methods
}

Note :- You can use User model class to map the user data to a database table etc.

 

Spring Security configuration

To configure Spring Security in Spring MVC application you need to -

  • Create a springSecurityFilterChain Servlet Filter for protecting and validating all URLs  by create a @Configuration class.
  • Register the springSecurityFilterChain filter with war. 

Now, create a @Configuration class by extending the WebSecurityConfigurerAdapter class and annotate it with @EnableWebSecurity.

Define your custom UserDetailsService class as a @Bean in web security @Configuration class as follows.

WebSecurityConfig.java

package com.boraji.tutorial.spring.config;

import org.springframework.context.annotation.Bean;
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.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import com.boraji.tutorial.spring.service.UserDetailsServiceImp;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Bean
  public UserDetailsService userDetailsService() {
    return new UserDetailsServiceImp();
  };
  
  @Bean
  public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  };
  
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().hasAnyRole("ADMIN", "USER")
    .and()
    .formLogin()
    .and()
    .logout().permitAll().logoutSuccessUrl("/login")
    .and()
    .csrf().disable();
  }
  
}

Next, create SecurityWebApplicationInitializer class by extending the AbstractSecurityWebApplicationInitializer to register the springSecurityFilterChain filter.

SecurityWebApplicationInitializer.java

package com.boraji.tutorial.spring.config;

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

public class SecurityWebApplicationInitializer 
  extends AbstractSecurityWebApplicationInitializer {

}

Spring MVC configuration

To enable the Spring MVC in your application, you need to annotate your @Configuration  class with @EnableWebMvc annotation.

In this example, we are using the JSP views.  So create a @Configuration class and override the configureViewResolvers() method to register the JSP view resolver.

Also, you can override the addViewControllers() method to map and render the  default login page generated by Spring Security.

WebConfig.java

package com.boraji.tutorial.spring.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.WebMvcConfigurer;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.boraji.tutorial.spring.controller" })
public class WebConfig implements WebMvcConfigurer {
  @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");
  }
}

Servlet container Initialization and configuration 

In Spring MVC, The DispatcherServlet needs to be declared and mapped for processing all requests either using java or web.xmlconfiguration.

In a Servlet 3.0+ environment, you can use AbstractAnnotationConfigDispatcherServletInitializer class to register and initialize the DispatcherServlet programmatically as follows.

MvcWebApplicationInitializer.java

package com.boraji.tutorial.spring.config;

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

public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

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

}

Controller class

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

MyContoller.java

package com.boraji.tutorial.spring.controller;

import java.security.Principal;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MyContoller {

  @GetMapping("/")
  public String index(Model model, Principal principal) {
    model.addAttribute("message", "You are logged in as " + principal.getName());
    return "index";
  }
}

 

JSP views

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

index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
	pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Spring Security 5</title>
</head>
<body>
<h1>Spring Security - Custom UserDetailsService Example</h1>
<h2>${message}</h2>

<form action="/logout" method="post">
	<input value="Logout" type="submit">
</form>
</body>
</html>

Run application

Use the following maven command to run your application.

mvn jetty:run (This command deploy the webapp from its sources, instead of build war).

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

On entering the URL, you will see the login page asking for username and password as follows.

spring-security-userdetails_01.png

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

spring-security-userdetails_02.png