我正在尝试在我的应用程序中实现UserCache
,以避免在我使用基本身份验证的情况下多次调用User表。我根据此topic的可接受答案创建了CacheConfig
,其中CachingUserDetailsService
用于管理用户缓存。波纹管是UserService
,CacheConfig
和SecurityConfig
的代码:
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
public UserService(UserRepository repository) {
this.userRepository = repository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AddInUser user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("O usuário " + username + " não pode ser encontrado!"));
UserDetails userDetails = User
.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles("USER")
.build();
return userDetails;
}
@Transactional
public AddInUser save(AddInUser user) {
return userRepository.save(user);
}
}
@EnableCaching
@Configuration
public class CacheConfig {
public static final String USER_CACHE = "userCache";
/**
* Define cache strategy
*
* @return CacheManager
*/
@Bean
public CacheManager cacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
List<Cache> caches = new ArrayList<>();
//Failure after 5 minutes of caching
caches.add(new GuavaCache(CacheConfig.USER_CACHE,
CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build()));
simpleCacheManager.setCaches(caches);
return simpleCacheManager;
}
@Bean
public UserCache userCache() throws Exception {
Cache cache = cacheManager().getCache("userCache");
return new SpringCacheBasedUserCache(cache);
}
}
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
};
@Autowired
private UserRepository userRepository;
@Autowired
private UserCache userCache;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues())
.and()
.authorizeRequests()
.antMatchers("/api/**")
.authenticated()
.and()
.httpBasic();
}
@Override
@Bean
public UserDetailsService userDetailsService() {
UserService userService = new UserService(userRepository);
CachingUserDetailsService cachingUserService = new CachingUserDetailsService(userService);
cachingUserService.setUserCache(this.userCache);
return cachingUserService;
}
}
第一个调用工作良好,因为它可以调用UserRepository
。但是第二,它没有调用存储库(如预期的那样),但是我从BCryptPasswordEncoder
得到以下警告:
2020-09-24 08:43:51.327 WARN 24624 --- [nio-8081-exec-4] o.s.s.c.bcrypt.BCryptPasswordEncoder : Empty encoded password
该警告的含义很清楚,并且由于null
密码而无法验证用户身份。但是我不明白为什么从缓存中重试的用户如果密码正确存储,密码为空。我不确定如何使用缓存解决问题。有什么想法吗?
非常感谢您的帮助!
答案 0 :(得分:2)
@ M.Deinum评论是绝对正确的。您可以参考文档here。
请注意,此实现不是一成不变的。它实现了 CredentialsContainer界面,以允许输入密码 认证后擦除。如果您这样做可能会产生副作用 将实例存储在内存中并重用它们。如果是这样,请确保您 每次调用UserDetailsService时都会返回一个副本。
如果您想了解更多信息,可以查看Spring-security source code:
private boolean eraseCredentialsAfterAuthentication = true;
...
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful then it
// will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent
// AuthenticationManager already published it
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
还有User.java源代码:
@Override
public void eraseCredentials() {
this.password = null;
}
顺便说一句,以这种方式缓存登录用户看起来很奇怪。登录期间,最好从数据库而不是从缓存中获取新记录。您可以在其他地方使用缓存的用户,但是很少在登录时看到它。
如果确实需要这样做,可以按照doc中所述将默认标志更改为false
,只需注入AuthenticationManager
并调用:
setEraseCredentialsAfterAuthentication(false)