Spring MVC + Spring Security (6.x) — In memory User Details Configuration (Security Part — 1)

Syed Hasan
6 min readFeb 11, 2020

Spring MVC + Spring Security (Java Configuration)

This article and its corresponding codes have been updated to support Spring 6.0.9 and Spring Security 6.1.0

In this post I will discuss few security concepts and demonstrate how to integrate Spring Security into Spring MVC Application. In this section we will work with In memory user details and integrate custom login form.

  • Few Spring Security concepts

Spring Security has 2 concepts.

a) Authentication: Authentication is the part of security which only identifies a user. You provide your username and your password, which only you know. If these 2 matches with the stored username and password, then the system will mark you as authenticated.

b) Authorization: Authorization comes in action when some functionality requires some role to perform an action and your access to some functionality requires depends on having that specific role or not.

  1. Required dependencies
<!-- Spring Security Config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>6.1.0</version>
</dependency>

<!-- Spring Security Web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>6.1.0</version>
</dependency>

2. Writing Configuration

a) SecurityInitializer: We need to write a SecurityInitializer class that will extend AbstractSecurityWebApplicationInitializer. This class is required for initializing Spring Security. The code is as follows.

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}

b) WebSecurityConfiguration: For configuring our Security we will write a Security Configuration class. We named it as SecurityConfig. We no longer need to extend WebSecurityConfigurerAdapter, what we used to do till Spring Security 5.x. We will annotate this class with @EnableWebSecurity and @Configuration annotations. In this SecurityConfig class we will declare several methods with @Bean annotation.

Let’s take a look at the full code of this class at a glance first.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception {
// We are disabling CSRF so that our forms don't complain about a CSRF token.
// Beware that it can create a security vulnerability
return http.csrf(AbstractHttpConfigurer::disable)
// Here we are configuring our login form
.formLogin(Customizer.withDefaults())
.authorizeHttpRequests(authorize ->
authorize
// We are permitting all static resources to be accessed publicly
.requestMatchers("/images/**", "/css/**", "/js/**", "/WEB-INF/views/**").permitAll()
// We are restricting endpoints for individual roles.
// Only users with allowed roles will be able to access individual endpoints.
.requestMatchers("/course/add").hasRole("ADMIN")
.requestMatchers("/course/show-all").hasAnyRole("ADMIN", "USER")
.requestMatchers("/course/edit").hasAnyRole("USER")
// Following line denotes that all requests must be authenticated.
// Hence, once a request comes to our application, we will check if the user is authenticated or not.
.anyRequest().authenticated()
)

.logout(Customizer.withDefaults())
.build();

}
@Bean
public UserDetailsService userDetailsService() {

UserDetails user = User
.withUsername("user")
.password("{noop}password")
.roles("USER")
.build();
UserDetails admin = User
.withUsername("admin")
.password("{noop}password")
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}

@Bean
public HandlerMappingIntrospector mvcHandlerMappingIntrospector() {
return new HandlerMappingIntrospector();
}
}

i. SecurityFilterChain Bean will help us with configuring which URLs to be secured, which URLs will be public, should there be CSRF, CORS prevention or not. Which will be Login URL, Logout URL, Login Success URL, Logout Failure URL, Logout Success URL etc. Currently we have disabled the CSRF token, and only allowing static resources to be accessible publicly, /course/add to be accessed by ADMIN only, /course/show-all can be accessed by anyone with ADMIN and USER roles. /course/edit is only accessible by anyone with USER role. And any other URL must be accessible by authenticated users. We didn’t define any login URL yet, rather we are using the default login and logout configuration provided by Spring Security.

The default login url will be /login and the logout url will be /logout

@Bean
public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception {
// We are disabling CSRF so that our forms don't complain about a CSRF token.
// Beware that it can create a security vulnerability
return http.csrf(AbstractHttpConfigurer::disable)
// Here we are configuring our login form
.formLogin(Customizer.withDefaults())
.authorizeHttpRequests(authorize ->
authorize
// We are permitting all static resources to be accessed publicly
.requestMatchers("/images/**", "/css/**", "/js/**", "/WEB-INF/views/**").permitAll()
// We are restricting endpoints for individual roles.
// Only users with allowed roles will be able to access individual endpoints.
.requestMatchers("/course/add").hasRole("ADMIN")
.requestMatchers("/course/show-all").hasAnyRole("ADMIN", "USER")
.requestMatchers("/course/edit").hasAnyRole("USER")
// Following line denotes that all requests must be authenticated.
// Hence, once a request comes to our application, we will check if the user is authenticated or not.
.anyRequest().authenticated()
)

.logout(Customizer.withDefaults())
.build();

}

ii. UserDetailsService Bean — We need an implementation or Bean of UserDetailsService to provide login credentials. Currently we are willing to use in memory user details, so we will not implement this interface, rather we will create a bean.

@Bean
public UserDetailsService userDetailsService() {

UserDetails user = User
.withUsername("user")
.password("{noop}password")
.roles("USER")
.build();
UserDetails admin = User
.withUsername("admin")
.password("{noop}password")
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}

Since spring 5 Security, if there is no encryption mechanism added, then they suggests us to add {noop} before plain text password.

iii. HandlerMappingIntrospector Bean — Additionally, with Spring Security 6.x, if you ever encounter the following error, you have to add a HandlerMappingIntrospector Bean.

Caused by: org.springframework.beans.BeanInstantiationException: 
Failed to instantiate [org.springframework.security.web.SecurityFilterChain]:
Factory method 'securityFilterChain' threw exception with message:
No bean named 'A Bean named mvcHandlerMappingIntrospector of type
org.springframework.web.servlet.handler.HandlerMappingIntrospector
is required to use MvcRequestMatcher. Please ensure Spring Security &
Spring MVC are configured in a shared ApplicationContext.' available

The Bean definition will look like below.

    @Bean
public HandlerMappingIntrospector mvcHandlerMappingIntrospector() {
return new HandlerMappingIntrospector();
}

3. Login controller and login.jsp pages

Alternatively, if you want to configure a login page and a login controller, your security configuration class may contain form login configuration like below. In this case, you have to customize your login and logout handler configuration for the SecurityFilterChain bean in the following way.

@Bean
public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception {
// We are disabling CSRF so that our forms don't complain about a CSRF token.
// Beware that it can create a security vulnerability
return http.csrf(AbstractHttpConfigurer::disable)
// Here we are configuring our login form
// .formLogin(Customizer.withDefaults())
.formLogin(formLogin -> {
formLogin
.loginPage("/login") // Login page will be accessed through this endpoint. We will create a controller method for this.
.loginProcessingUrl("/login-processing") // This endpoint will be mapped internally. This URL will be our Login form post action.
.usernameParameter("username")
.passwordParameter("password")
.permitAll() // We re permitting all for login page
.defaultSuccessUrl("/") // If the login is successful, user will be redirected to this URL.
.failureUrl("/login?error=true"); // If the user fails to login, application will redirect the user to this endpoint
}
)
.authorizeHttpRequests(authorize -> {
// Your Authorization configuration goes here
})
.logout(logout ->
logout
.logoutUrl("/logout")
.logoutSuccessUrl("/")
)
.build();
}

In the above configuration, you can see, our login endpoint is “/login”, login processing url is “/login-processing”, username and password parameters are respectively “username” and “password”. Default Success URL is “/” and login failure URL is “/login?error=true”.

Additionally, for the custom logout configuration, our logout URL is “/logout” and logout success url is “/”.

Note that, your loginProcessingUrl(…), usernameParameter(…) and passwordParameter(…) parameters will map to the form submit url, username and password input fields of your login form.

Now we need to create our /login endpoint and login.jsp page.

i. login() controller method:

 @GetMapping("/login")
public String login(Model model, @RequestParam(name="error", required = false) String error) {
model.addAttribute("error", error);
return "auth/login";
}

ii. login.jsp page:

In our views directory, we will create a new directory auth. We will create login.jsp page in auth directory. Following is our login.jsp code.

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath }/css/bootstrap.min.css">

<link rel="stylesheet"
href="${pageContext.request.contextPath }/css/login.css">

<script type="text/javascript"
src="${pageContext.request.contextPath }/js/bootstrap.min.js">
</script>
<script type="text/javascript"
src="${pageContext.request.contextPath }/js/jquery.js">
</script>
</head>
<body>
<c:if test="${error == 'true'}">
<div class="alert alert-danger" role="alert">Wrong username or
password
</div>
</c:if>
<div class="sidenav">
<div class="login-main-text">
<h2>
Application<br> Login Page
</h2>
<p>Login or register from here to access.</p>
</div>
</div>
<div class="main">
<div class="col-md-6 col-sm-12">
<div class="login-form">
<form action="${pageContext.request.contextPath }/login-processing"
method="POST">

<div class="form-group">
<label>User Name</label> <input type="text" name="username"
class="form-control" placeholder="User Name">

</div>
<div class="form-group">
<label>Password</label> <input type="password"
class="form-control" name="password" placeholder="Password">

</div>
<button type="submit" class="btn btn-black">Login</button>
<button type="submit" class="btn btn-secondary">Register</button>
</form>
</div>
</div>
</div>
</body>
</html>

4. Running application

Now let’s run our application. If everything is okay, our application will run successfully.

Here is the source code for this application.

Sign up to discover human stories that deepen your understanding of the world.

Syed Hasan
Syed Hasan

Written by Syed Hasan

Software Engineer | Back-End Developer | Spring Developer | Cloud Enthusiast

No responses yet

Write a response