Subject: [PATCH] boot4_userCache
---
Index: src/main/java/ru/javaops/bootjava/app/config/AppConfig.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/ru/javaops/bootjava/app/config/AppConfig.java b/src/main/java/ru/javaops/bootjava/app/config/AppConfig.java
--- a/src/main/java/ru/javaops/bootjava/app/config/AppConfig.java	(revision c5d1f7b9b7a24c28fed510a64ca4e332f31e2adf)
+++ b/src/main/java/ru/javaops/bootjava/app/config/AppConfig.java	(revision 8f36adf661e041a5462f9701aeb1d886974f6945)
@@ -4,7 +4,6 @@
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import lombok.extern.slf4j.Slf4j;
 import org.h2.tools.Server;
-import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Profile;
@@ -22,7 +21,6 @@
 
 @Configuration
 @Slf4j
-@EnableCaching
 public class AppConfig {
 
     @Profile("!test")
Index: src/main/java/ru/javaops/bootjava/app/config/SecurityConfig.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/ru/javaops/bootjava/app/config/SecurityConfig.java b/src/main/java/ru/javaops/bootjava/app/config/SecurityConfig.java
--- a/src/main/java/ru/javaops/bootjava/app/config/SecurityConfig.java	(revision c5d1f7b9b7a24c28fed510a64ca4e332f31e2adf)
+++ b/src/main/java/ru/javaops/bootjava/app/config/SecurityConfig.java	(revision 8f36adf661e041a5462f9701aeb1d886974f6945)
@@ -2,15 +2,22 @@
 
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.CachingUserDetailsService;
+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.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserCache;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.core.userdetails.cache.SpringCacheBasedUserCache;
 import org.springframework.security.crypto.factory.PasswordEncoderFactories;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.SecurityFilterChain;
@@ -25,26 +32,41 @@
 
 @Configuration
 @EnableWebSecurity
+@EnableCaching
 @Slf4j
 @AllArgsConstructor
 public class SecurityConfig {
     public static final PasswordEncoder PASSWORD_ENCODER = PasswordEncoderFactories.createDelegatingPasswordEncoder();
 
     private final UserRepository userRepository;
+    private final CacheManager cacheManager;
 
     @Bean
     PasswordEncoder passwordEncoder() {
         return PASSWORD_ENCODER;
     }
 
+    @Bean
+    UserCache userCache() {
+        return new SpringCacheBasedUserCache(cacheManager.getCache("users"));
+    }
+
+    //    https://www.phind.com/search/cmihyvg060000356u4nci6bie
     @Bean
     UserDetailsService userDetailsService() {
-        return email -> {
+        CachingUserDetailsService service = new CachingUserDetailsService(email -> {
             log.debug("Authenticating '{}'", email);
             Optional<User> optionalUser = userRepository.findByEmailIgnoreCase(email);
             return new AuthUser(optionalUser.orElseThrow(
                     () -> new UsernameNotFoundException("User '" + email + "' was not found")));
-        };
+        });
+        service.setUserCache(userCache());
+        return service;
+    }
+
+    @Autowired
+    public void configure(AuthenticationManagerBuilder builder) {
+        builder.eraseCredentials(false);
     }
 
     //https://stackoverflow.com/a/76538979/548473
Index: src/main/java/ru/javaops/bootjava/user/web/AbstractUserController.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/ru/javaops/bootjava/user/web/AbstractUserController.java b/src/main/java/ru/javaops/bootjava/user/web/AbstractUserController.java
--- a/src/main/java/ru/javaops/bootjava/user/web/AbstractUserController.java	(revision c5d1f7b9b7a24c28fed510a64ca4e332f31e2adf)
+++ b/src/main/java/ru/javaops/bootjava/user/web/AbstractUserController.java	(revision 8f36adf661e041a5462f9701aeb1d886974f6945)
@@ -2,6 +2,7 @@
 
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserCache;
 import org.springframework.web.bind.WebDataBinder;
 import org.springframework.web.bind.annotation.InitBinder;
 import ru.javaops.bootjava.user.model.User;
@@ -12,6 +13,9 @@
 public abstract class AbstractUserController {
     protected final Logger log = getLogger(getClass());
 
+    @Autowired
+    protected UserCache userCache;
+
     @Autowired
     protected UserRepository repository;
 
Index: src/main/java/ru/javaops/bootjava/user/web/AdminUserController.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/ru/javaops/bootjava/user/web/AdminUserController.java b/src/main/java/ru/javaops/bootjava/user/web/AdminUserController.java
--- a/src/main/java/ru/javaops/bootjava/user/web/AdminUserController.java	(revision c5d1f7b9b7a24c28fed510a64ca4e332f31e2adf)
+++ b/src/main/java/ru/javaops/bootjava/user/web/AdminUserController.java	(revision 8f36adf661e041a5462f9701aeb1d886974f6945)
@@ -18,7 +18,6 @@
 
 @RestController
 @RequestMapping(value = AdminUserController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE)
-// TODO: cache only most requested, seldom changed data!
 public class AdminUserController extends AbstractUserController {
 
     static final String REST_URL = "/api/admin/users";
@@ -33,7 +32,9 @@
     @DeleteMapping("/{id}")
     @ResponseStatus(HttpStatus.NO_CONTENT)
     public void delete(@PathVariable int id) {
+        User user = repository.getExisted(id);
         super.delete(id);
+        userCache.removeUserFromCache(user.getEmail());
     }
 
     @GetMapping
@@ -59,6 +60,7 @@
         log.info("update {} with id={}", user, id);
         assureIdConsistent(user, id);
         repository.prepareAndSave(user);
+        userCache.removeUserFromCache(user.getEmail());
     }
 
     @GetMapping("/by-email")
@@ -74,5 +76,6 @@
         log.info(enabled ? "enable {}" : "disable {}", id);
         User user = repository.getExisted(id);
         user.setEnabled(enabled);
+        userCache.removeUserFromCache(user.getEmail());
     }
 }
\ No newline at end of file
Index: src/main/java/ru/javaops/bootjava/user/web/ProfileController.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/ru/javaops/bootjava/user/web/ProfileController.java b/src/main/java/ru/javaops/bootjava/user/web/ProfileController.java
--- a/src/main/java/ru/javaops/bootjava/user/web/ProfileController.java	(revision c5d1f7b9b7a24c28fed510a64ca4e332f31e2adf)
+++ b/src/main/java/ru/javaops/bootjava/user/web/ProfileController.java	(revision 8f36adf661e041a5462f9701aeb1d886974f6945)
@@ -22,7 +22,6 @@
 @RestController
 @RequestMapping(value = ProfileController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE)
 @Slf4j
-// TODO: cache only most requested data!
 public class ProfileController extends AbstractUserController {
     static final String REST_URL = "/api/profile";
 
@@ -36,6 +35,7 @@
     @ResponseStatus(HttpStatus.NO_CONTENT)
     public void delete(@AuthenticationPrincipal AuthUser authUser) {
         super.delete(authUser.id());
+        userCache.removeUserFromCache(authUser.getUsername());
     }
 
     @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
@@ -57,5 +57,6 @@
         assureIdConsistent(userTo, authUser.id());
         User user = authUser.getUser();
         repository.prepareAndSave(UsersUtil.updateFromTo(user, userTo));
+        userCache.removeUserFromCache(authUser.getUsername());
     }
 }
\ No newline at end of file
Index: src/test/java/ru/javaops/bootjava/user/web/AdminUserControllerTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/test/java/ru/javaops/bootjava/user/web/AdminUserControllerTest.java b/src/test/java/ru/javaops/bootjava/user/web/AdminUserControllerTest.java
--- a/src/test/java/ru/javaops/bootjava/user/web/AdminUserControllerTest.java	(revision c5d1f7b9b7a24c28fed510a64ca4e332f31e2adf)
+++ b/src/test/java/ru/javaops/bootjava/user/web/AdminUserControllerTest.java	(revision 8f36adf661e041a5462f9701aeb1d886974f6945)
@@ -61,7 +61,7 @@
         perform(MockMvcRequestBuilders.delete(REST_URL_SLASH + USER_ID))
                 .andDo(print())
                 .andExpect(status().isNoContent());
-        assertFalse(repository.findById(USER_ID).isPresent());
+        assertFalse(repository.findByEmailIgnoreCase(USER_MAIL).isPresent());
     }
 
     @Test
