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 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