×
☰ See All Chapters

Adding Updating and Deleting Users in Spring Security

If you need custom user management operations, implement the UserDetailsManager interface. This can involve creating, updating, deleting, and retrieving user details according to your application's requirements. If the app only needs to authenticate the users, then implementing the UserDetailsService contract is enough to cover the desired functionality.  When implementing the UserDetailsManager interface you have to implement five methods of its own and one which it inherits from the UserDetailsService which it extends. These methods are: createUser, updateUser, deleteUser, changePassword, userExists and the inherited method loadUserByUsername.

public interface UserDetailsManager extends UserDetailsService {
   
void createUser(UserDetails user);
   
void updateUser(UserDetails user);
   
void deleteUser(String username);
   
void changePassword(String oldPassword, String newPassword);
   
boolean userExists(String username);
}

In the Spring Security framework v2.0.4 there are two concrete implementations of UserDetailsManager: JdbcUserDetailsManager and LdapUserDetailsManager. Generally, we don’t implement this interface or even we don’t use JdbcUserDetailsManager and LdapUserDetailsManager. After authentication/login use project specific flows to create, update, delete user. This interface is included purely as a convenience interface which the user of the framework may or may not decide to use. 

Example

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
     
xsi:schemaLocation="https://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.6.0</version>
     <
relativePath/> <!-- lookup parent from repository, not local -->
 
</parent>
  <
groupId>com.java4coding</groupId>
  <
artifactId>AddingModifyingDeletingUser</artifactId>
  <
version>0.0.1-SNAPSHOT</version>
  <
name>AddingModifyingDeletingUser</name>
  <
description>Managing Users in Spring Security</description>
  <
properties>
     <
java.version>11</java.version>
     <
project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <
project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     <
spring-boot.version>2.6.0</spring-boot.version>
     <
lombok.version>1.18.28</lombok.version>
  </
properties>
  <
dependencies>
     <
dependency>
        <
groupId>org.springframework.boot</groupId>
        <
artifactId>spring-boot-starter</artifactId>
     </
dependency>
     <
dependency>
        <
groupId>org.springframework.boot</groupId>
        <
artifactId>spring-boot-starter-web</artifactId>
     </
dependency>
     <
dependency>
        <
groupId>org.springframework.boot</groupId>
        <
artifactId>spring-boot-starter-security</artifactId>
     </
dependency>
     <
dependency>
        <
groupId>org.projectlombok</groupId>
        <
artifactId>lombok</artifactId>
        <
version>${lombok.version}</version>
        <
scope>provided</scope>
     </
dependency>
     <
dependency>
        <
groupId>com.h2database</groupId>
        <
artifactId>h2</artifactId>
        <
scope>runtime</scope>
     </
dependency>
     <
dependency>
        <
groupId>org.springframework.boot</groupId>
        <
artifactId>spring-boot-starter-data-jpa</artifactId>
     </
dependency>
  </
dependencies>
  <
dependencyManagement>
     <
dependencies>
        <
dependency>
           <
groupId>org.springframework.boot</groupId>
           <
artifactId>spring-boot-dependencies</artifactId>
           <
version>${spring-boot.version}</version>
           <
type>pom</type>
           <
scope>import</scope>
        </
dependency>
     </
dependencies>
  </
dependencyManagement>

  <
build>
     <
plugins>
        <
plugin>
           <
groupId>org.springframework.boot</groupId>
           <
artifactId>spring-boot-maven-plugin</artifactId>
           <
version>${spring-boot.version}</version>
           <
executions>
              <
execution>
                 <
id>build-info</id>
                 <
goals>
                    <
goal>build-info</goal>
                    <
goal>repackage</goal>
                 </
goals>
              </
execution>
           </
executions>
        </
plugin>
     </
plugins>
  </
build>

</
project>

 

 

SpringBootDemo.java

package com.java4coding;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@EntityScan
@EnableJpaRepositories
public class SpringBootDemo {
   
public static void main(String[] args) {
       
SpringApplication.run(SpringBootDemo.class, args);
   }
}

 

 

ApplicationConfig.java

package com.java4coding.config;

import com.java4coding.repository.UserRepository;
import com.java4coding.security.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class ApplicationConfig {

   
@Bean
   
public UserDetailsService userDetailsService(UserRepository userRepository) {
       
return new UserDetailsServiceImpl(userRepository);
   }

   
@Bean
   
public PasswordEncoder passwordEncoder() {
       
return NoOpPasswordEncoder.getInstance();
   }
}

 

 

ApplicationWebSecurityConfigurerAdapter.java

package com.java4coding.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class ApplicationWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

   
/*
   Since H2 has its own authentication provider, you can skip the Spring Security for the
   path of h2 console entirely in the same way that you do for your static content.
   In order to do that, in your Spring security config, you have to override the configuration
    method which takes an instance of org.springframework.security.config.annotation.web.builders.WebSecurity
    as a parameter instead of the one which takes an instance of org.springframework.security.config.annotation.web.builders.HttpSecurity
    */
   
@Override
   
public void configure(WebSecurity web) throws Exception {
       web.ignoring().antMatchers(
"/h2-console/**");
   }

   
/*
    Spring Security enables Cross-Site Request Forgery (CSRF) protection by default.
    CSRF is an attack that tricks the victim into submitting a malicious request and
    uses the identity of the victim to perform an undesired function on their behalf.
    If the CSRF token, which is used to protect against this type of attack, is missing
    or incorrect, the server may also respond with error 403.
    */
   
@Override
   
protected void configure(HttpSecurity http) throws Exception {
       http.authorizeRequests()
               .anyRequest()
               .authenticated()
               .and()
               .httpBasic();
       http.csrf().disable();
   }

}

 

 

UserController.java

package com.java4coding.controller;

import com.java4coding.dto.ResponseDto;
import com.java4coding.dto.UserDto;
import com.java4coding.entity.User;
import com.java4coding.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class UserController {

   
@Autowired
   
private UserRepository userRepository;

   
@GetMapping(value = "/testAccess")
   
public String testAccess() {
       
return "Hurray! You are Authorized.";
   }

   
@PostMapping(value = "/addUser")
   
public ResponseEntity<ResponseDto> addUser(@RequestBody UserDto userDto) {
       
User user = new User();
       
user.setPassword(userDto.getPassword());
       
user.setUsername(userDto.getUsername());
       
user.setAuthority(userDto.getAuthority());
       
userRepository.save(user);
       
return ResponseEntity.ok(ResponseDto.builder().result("Success").code("200").build());
   }

   
@PostMapping(value = "/updateUser")
   
public ResponseEntity<ResponseDto> updateUser(@RequestBody UserDto userDto) {
       
List<User> users = userRepository.findByUsername(userDto.getUsername());
       
if (users == null || users.isEmpty()) {
           
return ResponseEntity.ok(ResponseDto.builder().result("No User Exists").code("400").build());
       }
       
User existingUser = users.get(0);
       
existingUser.setPassword(userDto.getPassword());
       
existingUser.setUsername(userDto.getNewUsername());
       
existingUser.setAuthority(userDto.getAuthority());
       
userRepository.save(existingUser);
       
return ResponseEntity.ok(ResponseDto.builder().result("Success").code("200").build());
   }

   
@GetMapping(value = "/deleteUser/{userName}")
   
public ResponseEntity<ResponseDto> deleteUser(@PathVariable("userName") String userName) {
       
List<User> users = userRepository.findByUsername(userName);
       
if (users == null || users.isEmpty()) {
           
return ResponseEntity.ok(ResponseDto.builder().result("No User Exists").code("400").build());
       }
       
User existingUser = users.get(0);
       
userRepository.delete(existingUser);
       
return ResponseEntity.ok(ResponseDto.builder().result("Success").code("200").build());
   }
}

 

 

ResponseDto.java

package com.java4coding.dto;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
@Builder
public class ResponseDto {
   
private String result;
   
private String code;
}

 

 

UserDto.java

package com.java4coding.dto;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class UserDto {
   
private String username;
   
private String newUsername;
   
private String password;
   
private String authority;
}

 

 

User.java

package com.java4coding.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Setter
@Getter
public class User {
   
@Id
   @GeneratedValue
(strategy = GenerationType.IDENTITY)
   
private Long id;
   
private String username;
   
private String password;
   
private String authority;
}

 

 

UserRepository.java

package com.java4coding.repository;

import com.java4coding.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface UserRepository extends JpaRepository<User, Long> {
   
List<User> findByUsername(String userName);
}

 

 

SecurityUser.java

package com.java4coding.security;

import com.java4coding.entity.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

public class SecurityUser implements UserDetails {
   
private final User user;

   
public SecurityUser(User user) {
       
this.user = user;
   }

   
@Override
   
public String getUsername() {
       
return user.getUsername();
   }

   
@Override
   
public String getPassword() {
       
return user.getPassword();
   }

   
@Override
   
public Collection<? extends GrantedAuthority> getAuthorities() {
       
return List.of(() -> user.getAuthority());
   }

   
@Override
   
public boolean isAccountNonExpired() {
       
return true;
   }

   
@Override
   
public boolean isAccountNonLocked() {
       
return true;
   }

   
@Override
   
public boolean isCredentialsNonExpired() {
       
return true;
   }

   
@Override
   public boolean isEnabled() {
       
return true;
   }
}

 

 

UserDetailsServiceImpl.java

package com.java4coding.security;

import com.java4coding.entity.User;
import com.java4coding.repository.UserRepository;
import lombok.AllArgsConstructor;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.List;

@AllArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
   
private UserRepository userRepository;

   
@Override
   
public SecurityUser loadUserByUsername(String username) throws UsernameNotFoundException {
       
List<User> usersByUserName = userRepository.findByUsername(username);
       
return new SecurityUser(usersByUserName.get(0));
   }
}

 

 

application.properties

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console/

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=pass
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
#spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.hibernate.ddl-auto=none

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.format_sql=true

 

 

schema.sql

create table user (
  id bigint generated
by default as identity,
   authority
varchar(255),
   password
varchar(255),
   username
varchar(255),
   
primary key (id)
);

 

 

data.sql

INSERT INTO User (id, username, password, authority) VALUES (1, 'manu', 'pass', 'read');
INSERT INTO User (id, username, password, authority) VALUES (2, 'advith', 'xyz123', 'read');
INSERT INTO User (id, username, password, authority) VALUES (3, 'aashvith', 'xyz123', 'read');

 

Project Structure

adding-updating-and-deleting-users-in-spring-security-0
 

Output

adding-updating-and-deleting-users-in-spring-security-1
 

All Chapters
Author