微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用Springboot缓存UserDetails

如何解决使用Springboot缓存UserDetails

我正在尝试在我的应用程序中实现UserCache,以避免在我使用基本身份验证的情况下多次调用User表。我根据此topic的可接受答案创建了CacheConfig,其中CachingUserDetailsService用于管理用户缓存。波纹管是UserServiceCacheConfigSecurityConfig代码


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密码而无法验证用户身份。但是我不明白为什么从缓存中重试的用户如果密码正确存储,密码为空。我不确定如何使用缓存解决问题。有什么想法吗?

非常感谢您的帮助!

解决方法

@ M.Deinum评论是绝对正确的。您可以参考文档here

请注意,此实现不是一成不变的。它实现了 CredentialsContainer界面,以允许输入密码 认证后擦除。如果您这样做可能会产生副作用 将实例存储在内存中并重用它们。如果是这样,请确保您 每次调用UserDetailsS​​ervice时都会返回一个副本。

如果您想了解更多信息,可以查看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)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。