We configured a static user in the application.properties file in the previous Spring Security tutorial. In reality, however, we must configure a web application so that it can be accessed by multiple users. In this section, we'll look at how to set up multiple users in a Spring Boot web application using the Spring Security Framework.
So, first, we'll create a simple Spring Boot MVC Web application, and then we'll add Spring Security to it.
POM.XML
The Spring Boot project's pom.xml is shown below:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.raven</groupId>
<artifactId>spring-boot-security-user-management</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>spring-boot-security-user-management</name>
<description>Spring Boot project to manage user in Spring Security</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Spring Boot version 2.7.6 is what we use. This version of Spring Boot, Spring Framework, and Spring Security is 5.3.24 and 5.7.5, respectively. To implement Spring Security in this application, we have added the spring-boot-starter-security dependency.
Controller
Create a package of name controller within the root package. Create two controller classes within this package: WelcomeController and UserController.
The following is a code snippet from the WelcomeController class:
package com.raven.springbootsecurityusermanagement.controller;
// imports are omitted
@RestController
public class WelcomeController {
@GetMapping("/welcome")
public String welcome() {
return "Available courses:" +
"<ul>" +
"<li>Learn JAVA : Beginner to Master</li>" +
"<li>Full Stack JAVA Developer</li>" +
"<li>Microservices with Spring Boot</li>" +
"<li>Complete Web Development</li>" +
"<li>Wordpress for Beginner</li>" +
"<li>Complete Python Development</li>" +
"<li>Docker guide : Beginner to Master</li>" +
"<li>Node.js : Ultimate guide</li>" +
"</ul>";
}
}
This class is annotated with @RestController, which tells the Spring Container that it will be used for a REST-based service. The Spring Container is informed by the @GetMapping annotation that the HTTP endpoint /welcome is exposed as a REST service. As a result, when we call this HTTP endpoint from another application or a browser, the welcome() method is invoked.
Here is a code snippet for the UserController class:
package com.raven.springbootsecurityusermanagement.controller;
// imports are omitted
@RestController
public class UserController {
@GetMapping("/myCourses")
public String myCourses() {
return "Enrolled courses:" +
"<ul>" +
"<li>Full Stack JAVA Developer (85% done)</li>" +
"<li>Microservices with Spring Boot (55 % done)</li>" +
"<li>Docker guide : Beginner to Master (65% done)</li>" +
"</ul>";
}
}
This class is also annotated with @RestController and has exposed another HTTP endpoint - /myCourses.
To manage users, Spring Security Framework provides classes such as InMemoryUserDetailsManager, JdbcUserDetailsManager, and LdapUserDetailsManager. These classes can be used to manage users in a variety of situations, such as in memory while the Spring Boot application is running, in the database, or when retrieving user information from LDAP servers.
User Management In Memory
Create another package of name configuration package under the root package. Create a class called SecurityConfiguration inside the configuration package and update it with the following code:
package com.raven.springbootsecurityusermanagement.configuration;
// imports are omitted
@Configuration
public class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests()
.antMatchers("/myCourses").authenticated()
.antMatchers("/welcome").permitAll()
.and().formLogin()
.and().httpBasic();
return httpSecurity.build();
}
@Bean
public InMemoryUserDetailsManager configureUsers() {
UserDetails adminUser = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin@321")
.authorities("admin")
.build();
UserDetails normalUser = User.withDefaultPasswordEncoder()
.username("normal")
.password("normal@321")
.authorities("read")
.build();
return new InMemoryUserDetailsManager(adminUser, normalUser);
}
}
We configured the endpoints that need to be secured in the securityFilterChain() method, while others will remain open to all. In the configureUsers() method, we've set up a number of users who can access the secured endpoints.
UserDetails is an interface in the Spring Security Framework. This interface includes abstract methods such as getUsername() and getPassword(). Spring Security also includes a sample implementation class for this interface called User. We can create new users by using the User class. After creating a new object of the class User, we can use the getUsername() method to retrieve the username or the getAuthorities() method to retrieve the authority (role). The UserDetails interface and User class are used in InMemoryUserDetailsManager, JdbcUserDetailsManager, UserDetailsService, and so on.
To create in-memory users, we define a bean of type InMemoryUserDetailsManager. We created two different users by passing credentials and authorities (roles) to the User class. Because we are using plain text as the password, there will be no encoding or hashing, so we have used withDefaultPasswordEncoder(). In the return statement, we passed the user details objects to the constructor of the InMemoryUserDetailsManager class. Then, using the User class, the InMemoryUserDetailsManager will create those users.
Start the application and try to connect to the endpoints. You can see that the /welcome endpoint does not require authentication, and you can access the /myCourses endpoint using the credentials specified above.
User Management with Database
First, we must create two tables in the database to store and retrieve user details for the Jdbc type of authentication. The SQL script is as follows:
CREATE DATABASE IF NOT EXISTS `spring_security_db`;
USE `spring_security_db`;
CREATE TABLE
`users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(45) NOT NULL,
`password` varchar(45) NOT NULL,
`enabled` int(11) NOT NULL,
PRIMARY KEY (`id`));
INSERT INTO users(username,password,enabled) VALUES ('john', 'john123', '1');
CREATE TABLE
`authorities` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(45) NOT NULL,
`authority` varchar(45) NOT NULL,
PRIMARY KEY (`id`));
INSERT INTO authorities(username,authority) VALUES ('john', 'write');
Update our application's SecurityConfiguration class with the following code snippet to enable JDBC authentication:
package com.raven.springbootsecurityusermanagement.configuration;
// imports are omitted
@Configuration
public class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests()
.antMatchers("/myCourses").authenticated()
.antMatchers("/welcome").permitAll()
.and().formLogin()
.and().httpBasic();
return httpSecurity.build();
}
@Bean
public JdbcUserDetailsManager userDetailsManager(DataSource dataSource) {
return new JdbcUserDetailsManager(dataSource);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
For Jdbc authentication, we've created a bean of type JdbcUserDetailsManager, just like we did for in-memory user management. The data source object is passed to the userDetailsManager() method. We've added a MySQL-related dependency to our classpath, and there are database-related properties in the application.properties file. Spring Boot will automatically configure the data source object within this application based on the application.properties file. The JdbcUserDetailsManager will now use this data source object.
You may have noticed that we define a bean of the type PasswordEncoder. It is best practice to inform Spring Security about how we store passwords in the database. For the sake of simplicity, we keep the passwords in the database as plain text, which is why we used NoOpPasswordEncoder. Spring Security will be notified that our passwords are in plain text format.
Restart the application and try to connect to the endpoints again. You can use the above-configured database credentials to access the /myCourses endpoint.
Because UserDetailsService and JdbcUserDetailsManager have a parent-child relationship, you can also create a bean of the type UserDetailsService instead of JdbcUserDetailsManager.
The source code is available for download here.
Happy coding!!! 😊
No comments:
Post a Comment