You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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。
方案1:用Spring Security原生的UserCache

如果会话配置没问题,但你还是想把用户信息缓存起来(比如用户信息不常变,或者想减少数据库压力),Spring Security自带了UserCache接口,专门用来缓存UserDetails对象,默认可以用内存版的ConcurrentMapCache实现。

步骤很简单:

  1. 定义一个UserCache Bean:
@Bean
public UserCache userCache() {
    // 用ConcurrentMap作为内存缓存,缓存名称设为"users"
    return new SpringCacheUserCache(new ConcurrentMapCache("users"));
}
  1. 在配置AuthenticationManagerBuilder时,把这个缓存注入进去:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(customUserDetailsService)
        .passwordEncoder(passwordEncoder())
        .userCache(userCache()); // 绑定缓存
}

这样,当第一次调用loadUserByUsername查数据库后,用户信息会被存在内存的Map里,后续再查同一个用户就直接从缓存取了。这个方案不用加额外依赖,原生支持,适合小规模应用。

方案2:用Spring Cache抽象(更灵活)

如果你想让缓存逻辑更通用,或者以后可能要换成Redis这种分布式缓存,那用Spring Cache抽象更合适。它支持多种缓存实现,内存缓存可以用ConcurrentMapCache,也可以用Caffeine这种高性能的内存缓存库。

步骤:

  1. 首先在启动类上加上@EnableCaching注解,开启Spring Cache功能:
@SpringBootApplication
@EnableCaching
public class YourApplication {
    public static void main(String[] args) {
        SpringApplication.run(YourApplication.class, args);
    }
}
  1. 在你的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:36:22