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