User
and Role
entities with a many-to-many relationship, configure Spring Security for database authentication, and insert a few records during application startup.What is Database Authentication?
Database authentication is a method of validating users against credentials stored in a database. This is more flexible and scalable compared to in-memory authentication, as it allows dynamic management of users and roles. User details and roles are stored in database tables, and Spring Security uses these tables to authenticate users and authorize their access to various parts of the application.
Prerequisites
Before we start, ensure you have the following:
- Java Development Kit (JDK) installed
- Apache Maven installed
- An IDE (Integrated Development Environment) like IntelliJ IDEA or Eclipse
Step 1: Setting Up the Project
Create a Spring Boot Project
- Open your IDE and create a new Spring Boot project using Spring Initializr.
- Add the following dependencies:
- Spring Web
- Spring Security
- Spring Data JPA
- H2 Database (for simplicity, but you can use any database of your choice)
- Spring Boot Starter Thymeleaf (for creating simple views)
Project Structure
Your project structure should look like this:
Step 2: Adding Dependencies
Add the necessary dependencies for Spring Security, Spring Data JPA, H2 database, and Thymeleaf in the pom.xml
file.
pom.xml
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Spring Boot Starter Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Step 3: Configuring the Application Properties
Configure the application properties for the H2 database and other settings.
application.properties
# H2 Database configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# JPA settings
spring.jpa.hibernate.ddl-auto=update
Step 4: Creating the User and Role Entities
Create User
and Role
entities with a many-to-many relationship in the com.example.security.model
package.
Role.java
package com.example.security.model;
import jakarta.persistence.*;
import java.util.Set;
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "roles")
private Set<User> users;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
User.java
package com.example.security.model;
import jakarta.persistence.*;
import java.util.Set;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
Step 5: Creating the Repositories
Create UserRepository
and RoleRepository
interfaces in the com.example.security.repository
package.
UserRepository.java
package com.example.security.repository;
import com.example.security.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
RoleRepository.java
package com.example.security.repository;
import com.example.security.model.Role;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RoleRepository extends JpaRepository<Role, Long> {
Role findByName(String name);
}
Step 6: Creating the User Service
Create a UserDetailsServiceImpl
class to load user-specific data and a UserService
class to manage users.
UserDetailsServiceImpl.java
package com.example.security.service;
import com.example.security.model.User;
import com.example.security.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles(user.getRoles().stream().map(role -> role.getName()).toArray(String[]::new))
.build();
}
}
UserService.java
package com.example.security.service;
import com.example.security.model
.Role;
import com.example.security.model.User;
import com.example.security.repository.RoleRepository;
import com.example.security.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.HashSet;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@PostConstruct
public void init() {
// Create roles
Role adminRole = new Role();
adminRole.setName("ADMIN");
roleRepository.save(adminRole);
Role userRole = new Role();
userRole.setName("USER");
roleRepository.save(userRole);
// Create users
User admin = new User();
admin.setUsername("admin");
admin.setPassword(passwordEncoder.encode("admin"));
admin.setRoles(new HashSet<>(Set.of(adminRole)));
userRepository.save(admin);
User user = new User();
user.setUsername("user");
user.setPassword(passwordEncoder.encode("password"));
user.setRoles(new HashSet<>(Set.of(userRole)));
userRepository.save(user);
}
}
Step 7: Configuring Spring Security
Create a SecurityConfig
class in the com.example.security
package to configure Spring Security for database authentication.
SecurityConfig.java
package com.example.security;
import com.example.security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.requestMatchers("/h2-console/**").permitAll() // Allow access to H2 console
.anyRequest().authenticated()
)
.httpBasic(withDefaults());
// Disable CSRF and frame options for H2 console
http.csrf().disable();
http.headers().frameOptions().disable();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
Step 8: Creating the Hello Controller
Create a HelloController
class in the com.example.security.controller
package to expose a secured endpoint.
HelloController.java
package com.example.security.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, World!";
}
}
Step 9: Creating the Main Application Class
Create the main application class to run your Spring Boot application.
SecurityApplication.java
package com.example.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}
Step 10: Running the Application
To run the application, execute the SecurityApplication
class. This will start the Spring Boot application with Spring Security configured for database authentication.
Accessing the Application
- Open your browser and navigate to
http://localhost:8080/h2-console
to access the H2 database console. Use the JDBC URLjdbc:h2:mem:testdb
, usernamesa
, and an empty password to log in. - Navigate to
http://localhost:8080/api/hello
. You will be prompted to enter a username and password. Use the credentials defined in yourUserService
:username: admin
password: admin
username: user
password: password
Using curl to Access the API
You can also use curl
to access the secured API:
curl -u user:password http://localhost:8080/api/hello
This command sends a request to the API with HTTP Basic authentication using the user
credentials.
Conclusion
In this tutorial, we have walked through setting up a basic Spring Boot application and integrating it with Spring Security for database authentication. We created User
and Role
entities with a many-to-many relationship, configured Spring Security to authenticate users from the database, and inserted initial records during application startup. By following this tutorial, you should now have a good understanding of how to integrate Spring Security with Spring Boot and secure your applications using database authentication.
Comments
Post a Comment