diff --git a/core/src/main/java/org/springframework/security/authentication/CachingUserDetailsService.java b/core/src/main/java/org/springframework/security/authentication/CachingUserDetailsService.java index da6e7de77d..ba8f6d3695 100644 --- a/core/src/main/java/org/springframework/security/authentication/CachingUserDetailsService.java +++ b/core/src/main/java/org/springframework/security/authentication/CachingUserDetailsService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,51 +26,29 @@ import org.springframework.util.Assert; * Implementation of {@link UserDetailsService} that utilizes caching through a * {@link UserCache} *

- * If a null {@link UserDetails} instance is got from calling + * If a null {@link UserDetails} instance is returned from * {@link UserCache#getUserFromCache(String)} to the {@link UserCache} got from * {@link #getUserCache()}, the user load is deferred to the {@link UserDetailsService} - * provided during construction. Otherwise, the instance got from cache is returned. + * provided during construction. Otherwise, the instance retrieved from the cache is + * returned. *

* It is initialized with a {@link NullUserCache} by default, so it's strongly recommended * setting your own {@link UserCache} using {@link #setUserCache(UserCache)}, otherwise, * the delegate will be called every time. *

- * Utilize this class by defining {@link org.springframework.context.annotation.Bean} that - * encapsulates an actual implementation of {@link UserDetailsService} and set an - * {@link UserCache}. + * Utilize this class by defining a {@link org.springframework.context.annotation.Bean} + * that encapsulates an actual implementation of {@link UserDetailsService} and providing + * a {@link UserCache} implementation. *

- * For example:
{@code
+ * For example: 
  * @Bean
- * public CachingUserDetailsService cachingUserDetailsService(UserDetailsService delegate,
- *                                                            UserCache userCache) {
+ * public CachingUserDetailsService cachingUserDetailsService(UserCache userCache) {
+ *     UserDetailsService delegate = ...;
  *     CachingUserDetailsService service = new CachingUserDetailsService(delegate);
  *     service.setUserCache(userCache);
  *     return service;
  * }
- * }
- * - *

- * However, a preferable approach would be to use - * {@link org.springframework.cache.annotation.Cacheable} in your - * {@link UserDetailsService#loadUserByUsername(String)} implementation to cache - * {@link UserDetails} by username, reducing boilerplate and setup, specially - * if you are already using cache in your application. - *

- * - * For example: - * - *
{@code
- * @Service
- * public class MyCustomUserDetailsImplementation implements UserDetailsService {
-
- *     @Override
- *     @Cacheable
- *     public UserDetails loadUserByUsername(String username) {
- *         //some logic here to get the actual user details
- *         return userDetails;
- *     }
- * }
- * }
+ *
* * @author Luke Taylor * @since 2.0 diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/cached.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/cached.adoc deleted file mode 100644 index 8d58252b6e..0000000000 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/cached.adoc +++ /dev/null @@ -1,38 +0,0 @@ -[[servlet-authentication-cached]] -= CachingUserDetailsService - -Spring Security's `CachingUserDetailsService` implements xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[UserDetailsService] offering support for caching authentication. - -`CachingUserDetailsService` provides caching support for `UserDetails` by delegating the authentication process to the provided `UserDetailsService`. The result is then stored in a `UserCache` to reduce computation in subsequent calls. - -Utilize this class by defining a `@Bean` of it that encapsulates a concrete implementation of `UserDetailsService` and set a `UserCache` to cache authenticated `UserDetails`. - -For example: - -[source,java] ----- -@Bean -public CachingUserDetailsService cachingUserDetailsService(UserDetailsService delegate, UserCache userCache) { - CachingUserDetailsService service = new CachingUserDetailsService(delegate); - service.setUserCache(userCache); - return service; -} ----- - -However, a preferable approach would be to use `@Cacheable` in your `UserDetailsService.loadUserByUsername(String)` implementation to cache `UserDetails` by `username`, reducing boilerplate and setup, especially if you are already using cache in your application. - -For example: - -[source,java] ----- -@Service -public class MyCustomUserDetailsImplementation implements UserDetailsService { - - @Override - @Cacheable - public UserDetails loadUserByUsername(String username) { - // some logic here to get the actual user details - return userDetails; - } -} ----- diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/caching.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/caching.adoc new file mode 100644 index 0000000000..9c0d37771e --- /dev/null +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/caching.adoc @@ -0,0 +1,156 @@ +[[servlet-authentication-caching-user-details]] += Caching `UserDetails` + +Spring Security provides support for caching `UserDetails` with <>. +Alternatively, you can use Spring Framework's <> annotation. +In either case, you will need to <> in order to validate passwords retrieved from the cache. + +[[servlet-authentication-caching-user-details-service]] +== `CachingUserDetailsService` + +Spring Security's `CachingUserDetailsService` implements xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[UserDetailsService] to provide support for caching `UserDetails`. +`CachingUserDetailsService` provides caching support for `UserDetails` by delegating to the provided `UserDetailsService`. +The result is then stored in a `UserCache` to reduce computation in subsequent calls. + +The following example simply defines a `@Bean` that encapsulates a concrete implementation of `UserDetailsService` and a `UserCache` for caching the `UserDetails`: + +.Provide a `CachingUserDetailsService` `@Bean` +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +public CachingUserDetailsService cachingUserDetailsService(UserCache userCache) { + UserDetailsService delegate = ...; + CachingUserDetailsService service = new CachingUserDetailsService(delegate); + service.setUserCache(userCache); + return service; +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun cachingUserDetailsService(userCache: UserCache): CachingUserDetailsService { + val delegate: UserDetailsService = ... + val service = CachingUserDetailsService(delegate) + service.userCache = userCache + return service +} +---- +====== + +[[servlet-authentication-caching-user-details-cacheable]] +== `@Cacheable` + +An alternative approach would be to use Spring Framework's {spring-framework-reference-url}integration.html#cache-annotations-cacheable[`@Cacheable`] in your `UserDetailsService` implementation to cache `UserDetails` by `username`. +The benefit to this approach is simpler configuration, especially if you are already using caching elsewhere in your application. + +The following example assumes caching is already configured, and annotates the `loadUserByUsername` with `@Cacheable`: + +.`UserDetailsService` annotated with `@Cacheable` +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Service +public class MyCustomUserDetailsImplementation implements UserDetailsService { + + @Override + @Cacheable + public UserDetails loadUserByUsername(String username) { + // some logic here to get the actual user details + return userDetails; + } +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Service +class MyCustomUserDetailsImplementation : UserDetailsService { + + @Cacheable + override fun loadUserByUsername(username: String): UserDetails { + // some logic here to get the actual user details + return userDetails + } +} +---- +====== + +[[servlet-authentication-caching-user-details-credential-erasure]] +== Disable Credential Erasure + +Whether you use <> or <>, you will need to disable xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager-erasing-credentials[credential erasure] so that the `UserDetails` will contain a `password` to be validated when retrieved from the cache. +The following example disables credential erasure for the global `AuthenticationManager` by configuring the `AuthenticationManagerBuilder` provided by Spring Security: + +.Disable credential erasure for the global `AuthenticationManager` +[tabs] +===== +Java:: ++ +[source,java,role="primary"] +---- +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // ... + return http.build(); + } + + @Bean + public UserDetailsService userDetailsService() { + // Return a UserDetailsService that caches users + // ... + } + + @Autowired + public void configure(AuthenticationManagerBuilder builder) { + builder.eraseCredentials(false); + } + +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +import org.springframework.security.config.annotation.web.invoke + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + // ... + return http.build() + } + + @Bean + fun userDetailsService(): UserDetailsService { + // Return a UserDetailsService that caches users + // ... + } + + @Autowired + fun configure(builder: AuthenticationManagerBuilder) { + builder.eraseCredentials(false) + } + +} +---- +===== diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc index d3b3ab914f..af6ed15daa 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc @@ -2,7 +2,7 @@ = UserDetailsService {security-api-url}org/springframework/security/core/userdetails/UserDetailsService.html[`UserDetailsService`] is used by xref:servlet/authentication/passwords/dao-authentication-provider.adoc#servlet-authentication-daoauthenticationprovider[`DaoAuthenticationProvider`] for retrieving a username, a password, and other attributes for authenticating with a username and password. -Spring Security provides xref:servlet/authentication/passwords/in-memory.adoc#servlet-authentication-inmemory[in-memory], xref:servlet/authentication/passwords/jdbc.adoc#servlet-authentication-jdbc[JDBC], and xref:servlet/authentication/passwords/cached.adoc#servlet-authentication-cached[in-cache] implementations of `UserDetailsService`. +Spring Security provides xref:servlet/authentication/passwords/in-memory.adoc#servlet-authentication-inmemory[in-memory], xref:servlet/authentication/passwords/jdbc.adoc#servlet-authentication-jdbc[JDBC], and xref:servlet/authentication/passwords/caching.adoc#servlet-authentication-caching-user-details[caching] implementations of `UserDetailsService`. You can define custom authentication by exposing a custom `UserDetailsService` as a bean. For example, the following listing customizes authentication, assuming that `CustomUserDetailsService` implements `UserDetailsService`: