Spring Security is a powerful and highly customizable authentication and access-control framework. This technical post will review how to integrate security into your Spring Boot project. NOTE: If you need to know what tools you need to have installed on your computer to create a Spring Boot basic project, please refer to my previous post: Spring Boot. Then, let’s create a new Spring Boot project with web, security and Lombok as dependencies:
spring init --dependencies=web,security,thymeleaf --build=gradle --type=gradle-project --language=java spring-boot-security
Here is the complete build.gradle
file generated:
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.3'
id 'io.spring.dependency-management' version '1.1.3'
}
group = 'com.josdem.springboot.security'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}
tasks.named('test') {
useJUnitPlatform()
}
In the latest versions of Spring Security you will need to specify your view template names; here we have an example of how to do it.
package com.josdem.springboot.security.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
registry.addViewController("/logout").setViewName("logout");
}
}
Now we need to create the login page as: ${PROJECT_HOME}/src/main/resources/templates/login.html
:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<form th:action="@{/login}" method="post">
<div><label> Username : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
This is our HomeController
.
package com.josdem.springboot.security.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Slf4j
@Controller
public class HomeController {
@GetMapping("/")
public String index() {
log.info("Calling home");
return "home";
}
}
And home view:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<title>Home Page</title>
</head>
<body>
<h1>Welcome!</h1>
<p>Click <a th:href="@{/message}">here</a> to see a private message.</p>
</body>
</html>
This home view includes a link to /message
page, defined in the following Thymeleaf template.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>Hello Page</title></head>
<body><h1>Hello World!</h1></body>
</html>
The expected behavior is when a user clicks on the /message
link it will ask for credentials, in order to do that we need to define our securityFilterChain
and userDetailsService
package com.josdem.springboot.security.config;
import com.josdem.springboot.security.model.Role;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {
private final CredentialsProperties credentialsProperties;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
(requests) ->
requests.requestMatchers("/", "/home").permitAll().anyRequest().authenticated())
.formLogin((form) -> form.loginPage("/login").permitAll())
.logout((logout) -> logout.permitAll());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username(credentialsProperties.getUsername())
.password(credentialsProperties.getPassword())
.roles(Role.USER.name())
.build();
return new InMemoryUserDetailsManager(user);
}
}
Finally this is our MessageController
:
package com.josdem.springboot.security.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Slf4j
@Controller
public class MessageController {
@GetMapping("/message")
public String message() {
log.info("Calling private message");
return "message";
}
}
Now run the project:
gradle bootRun
To browse the project go here, to download the project:
git clone git@github.com:josdem/spring-boot-security.git
git fetch
git checkout feature/in-memory