Home > Enterprise >  How can I update user info from Scheduled in Spring Boot
How can I update user info from Scheduled in Spring Boot

Time:10-19

When I try to save a user in a scheduled task and then access it in a Controller through Authentication.getPrincipal() it won't get updated though the underlying database record changes.

@Scheduled(fixedRate = 10_000)
public void job() {
    User user = userRepo.findById(1).get();
    user.setEmail("[email protected]");
    userRepo.save(user);
}

I use Spring Security and I believe that somehow the user's info gets cached in the SecurityContextHolder thus not letting me to use the updated values for saved user.

Also if I perform SignOut - SignIn the data is updated but this cannot be considered as a solution.

As a workaround I tried to use autowired EntityManager and refresh the record with user before getting its data but this assumes that I should do it for each request where I need to get a user. Not the best solution as well

Other entities except User are being saved fine

CodePudding user response:

You must update the user in security context holder before each request in a filter like this:

public class TokenValidationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException {
        // read user from database using data from request header information
        // set user in SecurityContextHolder
        chain.doFilter(request, response);
    }
}

then you must add this filter to the configuration

@Configuration
@EnableWebSecurity
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new TokenValidationFilter(), UsernamePasswordAuthenticationFilter.class)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

You will not have a problem doing this

CodePudding user response:

I've ended up having a service with container that holds users whose data I need to change.

@Service
@Getter
@Setter
public class AuthService {

    private List<User> users = new CopyOnWriteArrayList<>();

}

And also I've implemented a filter as Mehdi suggested.

public class AuthFilter extends GenericFilterBean {

    @Autowired
    private AuthService authService;
    @Autowired
    private UserRepository userRepo;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null || !auth.isAuthenticated() || !(auth.getPrincipal() instanceof UserDetails)) {
            chain.doFilter(request, response);
            return;
        }
        User currUser = ... // getting current user from auth
        if (authService.getUsers().contains(currUser)) {
            UserDetails details = myUserDetailsService.loadUserByUsername(currUser.getFirstName());
            Authentication updatedAuth = new UsernamePasswordAuthenticationToken(details, currUser.getPassword(), details.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(updatedAuth);
            authService.getUsers().remove(currUser);
        }
        chain.doFilter(request, response);
    }
}

It's sad that spring doesn't provide a container with all Authentication objects to make changing a user easier. Or maybe I didn't found it Also I think it's possible to create such container in a custom filter by creating some sort of a wrapper around SecurityContextHolder (or whatever spring security class that has this information) but it would be harder to implement

  • Related