Spring Security优化:如何缓存登录用户信息避免重复数据库查询
嘿,我完全懂你的痛点——每次刷新页面都去查数据库,哪怕用户信息压根没变化,这纯纯是浪费资源啊!刚好我之前在Spring Security项目里也解决过类似问题,给你几个实用的方案,一步步来:
首先,你得先确认为啥每次访问/welcome都触发数据库查询。默认情况下,Spring Security会把用户的Authentication对象存在HttpSession里,登录成功后,后续请求直接从会话里取认证信息,不会再调用UserDetailsService查数据库。
如果你的项目里每次都查,大概率是会话配置有问题:
- 检查你的Security配置类里,
sessionManagement()的sessionCreationPolicy是不是设成了STATELESS(无状态)。如果是,那每次请求都得重新认证,自然会反复查库。改成IF_REQUIRED(默认值)或者ALWAYS就行:
@Override protected void configure(HttpSecurity http) throws Exception { http // 其他配置... .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); }
- 另外,有没有自定义的Filter或者拦截器,强制每次请求都重新加载用户信息?如果有,得调整逻辑,避免重复调用
UserDetailsService。
如果会话配置没问题,但你还是想把用户信息缓存起来(比如用户信息不常变,或者想减少数据库压力),Spring Security自带了UserCache接口,专门用来缓存UserDetails对象,默认可以用内存版的ConcurrentMapCache实现。
步骤很简单:
- 定义一个
UserCacheBean:
@Bean public UserCache userCache() { // 用ConcurrentMap作为内存缓存,缓存名称设为"users" return new SpringCacheUserCache(new ConcurrentMapCache("users")); }
- 在配置
AuthenticationManagerBuilder时,把这个缓存注入进去:
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService) .passwordEncoder(passwordEncoder()) .userCache(userCache()); // 绑定缓存 }
这样,当第一次调用loadUserByUsername查数据库后,用户信息会被存在内存的Map里,后续再查同一个用户就直接从缓存取了。这个方案不用加额外依赖,原生支持,适合小规模应用。
如果你想让缓存逻辑更通用,或者以后可能要换成Redis这种分布式缓存,那用Spring Cache抽象更合适。它支持多种缓存实现,内存缓存可以用ConcurrentMapCache,也可以用Caffeine这种高性能的内存缓存库。
步骤:
- 首先在启动类上加上
@EnableCaching注解,开启Spring Cache功能:
@SpringBootApplication @EnableCaching public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } }
- 在你的
UserDetailsService实现类的loadUserByUsername方法上,加上@Cacheable注解,指定缓存名称和缓存key:
@Service public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override @Cacheable(value = "users", key = "#username") // 按用户名缓存 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("用户不存在:" + username)); // 把数据库User转换成UserDetails return User.withUsername(user.getUsername()) .password(user.getPassword()) .authorities(user.getRoles().stream().map(Role::getRoleName).toArray(String[]::new)) .build(); } }
这样,第一次查询后,用户信息会被存在内存缓存里,后续相同用户名的请求直接走缓存。如果以后要换Redis,只需要添加Redis依赖并配置缓存管理器,不用改业务代码,非常灵活。
不管用哪种缓存方案,都要考虑用户信息更新后的缓存失效问题。比如用户改了密码或者权限,得把旧的缓存删掉,不然会取到旧数据。
- 用Spring Cache的话,可以在更新用户信息的方法上加上
@CacheEvict注解:
@CacheEvict(value = "users", key = "#user.username") public void updateUser(User user) { userRepository.save(user); }
- 用Spring Security的
UserCache的话,可以手动调用userCache.removeUserFromCache(username)来删除缓存。
这样就保证了用户信息更新后,下次查询能拿到最新的数据。
内容的提问来源于stack exchange,提问作者Senio Vak

