Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CachingUserDetailsService doesn't properly support expiraton of cache #16645

Open
ugard opened this issue Feb 24, 2025 · 0 comments
Open

CachingUserDetailsService doesn't properly support expiraton of cache #16645

ugard opened this issue Feb 24, 2025 · 0 comments
Labels
status: waiting-for-triage An issue we've not yet triaged type: bug A general bug

Comments

@ugard
Copy link

ugard commented Feb 24, 2025

Describe the bug
Using Spring Security 6.3.1 we're using LdapUserDetailsService to fetch user data and authorities. To decrease number of requests to LDAP infrastructure, we're using CachingUserDetailsService configured with LdapUserDetailsService as delegate, and SpringCacheBasedUserCache backed by Caffeine.
Caffeine is set up with "expireAfterWrite=1m" spec, so authorities of user are cached 1 minute after LDAP call.

The issue: delegate method LdapUserDetailsService::loadUserByUsername should be called every minute if user is sending requests in small intervals (<1 minute), but it's called only once per request batch - each access of user details in CachingUserDetailsService refreshes the cache with cached value:

@Override
public UserDetails loadUserByUsername(String username) {
	UserDetails user = this.userCache.getUserFromCache(username);
	if (user == null) {
		user = this.delegate.loadUserByUsername(username);
	}
	Assert.notNull(user, () -> "UserDetailsService " + this.delegate + " returned null for username " + username
			+ ". " + "This is an interface contract violation");

	// FIXME it's putting value read from cache back into the cache, thus resetting expiryAfterWrite timeout
	this.userCache.putUserInCache(user);
	return user;
}

CachingUserDetailsService

moving putUserInCache just after user is loaded from delegate inside the if block should fix the issue.

To Reproduce
Configure UserDetailsService as:

@Bean 
UserDetailsService userDetailsService(LdapUserSearch search, LdapAuthoritiesPopulator populator, LdapDetailsMapper mapper) {
    // ldap search
    var delegate = new LdapUserDetailsService(search, populator);
    delegate.setUserDetailsMapper(mapper);

    // cache definition
    var caffeine = new CaffeineCacheManager();
    caffeine.setCacheSpecification("expireAfterWrite=1m");

    //final bean
    var userDetailsService = new CachingUserDetailsService(delegate);
    userDetailsService.setUserCache(new SpringCacheBasedUserCache(caffeine.getCache("userCache")));
    return userDetailsService;
}

then require user detais more often that expiryAfterWrite cache timeout.

Expected behavior
Cache expires a minute after first requests, refreshes authorities from LDAP.

@ugard ugard added status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Feb 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting-for-triage An issue we've not yet triaged type: bug A general bug
Projects
None yet
Development

No branches or pull requests

1 participant