Reputation: 802
I am building a spring REST web application using spring boot, spring secuirity, and spring session (redis). I am building a cloud application following the gateway pattern using spring cloud and zuul proxy. Within this pattern I am using spring session to manage the HttpSesssion in redis and using that to authorize requests on my resource servers. When an operation is executed that alters the session's authorities, I would like to update that object so that the user does not have to log out to have the updates reflected. Does anyone have a solution for this?
Upvotes: 7
Views: 8283
Reputation: 802
To update the authorities you need to modify the authentication object in two places. One in the Security Context and the other in the Request Context. Your principal object will be org.springframework.security.core.userdetails.User or extend that class (if you have overridden UserDetailsService). This works for modifying the current user.
Authentication newAuth = new UsernamePasswordAuthenticationToken({YourPrincipalObject},null,List<? extends GrantedAuthority>)
SecurityContextHolder.getContext().setAuthentication(newAuth);
RequestContextHolder.currentRequestAttributes().setAttribute("SPRING_SECURITY_CONTEXT", newAuth, RequestAttributes.SCOPE_GLOBAL_SESSION);
To update the session using spring session for any logged in user requires a custom filter. The filter stores a set of sessions that have been modified by some process. A messaging system updates that value when new sessions need to be modified. When a request has a matching session key, the filter looks up the user in the database to fetch the updates. Then it updates the "SPRING_SECURITY_CONTEXT" property on the session and updates the Authentication in the SecurityContextHolder. The user does not need to log out. When specifying the order of your filter it is important that it comes after SpringSessionRepositoryFilter. That object has an @Order of -2147483598 so I just altered my filter by one to make sure it is the next one that is executed.
The workflow looks like:
Next time User A passed through the filter, update their session
@Component
@Order(UpdateAuthFilter.ORDER_AFTER_SPRING_SESSION)
public class UpdateAuthFilter extends OncePerRequestFilter
{
public static final int ORDER_AFTER_SPRING_SESSION = -2147483597;
private Logger log = LoggerFactory.getLogger(this.getClass());
private Set<String> permissionsToUpdate = new HashSet<>();
@Autowired
private UserJPARepository userJPARepository;
private void modifySessionSet(String sessionKey, boolean add)
{
if (add) {
permissionsToUpdate.add(sessionKey);
} else {
permissionsToUpdate.remove(sessionKey);
}
}
public void addUserSessionsToSet(UpdateUserSessionMessage updateUserSessionMessage)
{
log.info("UPDATE_USER_SESSION - {} - received", updateUserSessionMessage.getUuid().toString());
updateUserSessionMessage.getSessionKeys().forEach(sessionKey -> modifySessionSet(sessionKey, true));
//clear keys for sessions not in redis
log.info("UPDATE_USER_SESSION - {} - success", updateUserSessionMessage.getUuid().toString());
}
@Override
public void destroy()
{
}
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException
{
HttpSession session = httpServletRequest.getSession();
if (session != null)
{
String sessionId = session.getId();
if (permissionsToUpdate.contains(sessionId))
{
try
{
SecurityContextImpl securityContextImpl = (SecurityContextImpl) session.getAttribute("SPRING_SECURITY_CONTEXT");
if (securityContextImpl != null)
{
Authentication auth = securityContextImpl.getAuthentication();
Optional<User> user = auth != null
? userJPARepository.findByUsername(auth.getName())
: Optional.empty();
if (user.isPresent())
{
user.get().getAccessControls().forEach(ac -> ac.setUsers(null));
MyCustomUser myCustomUser = new MyCustomUser (user.get().getUsername(),
user.get().getPassword(),
user.get().getAccessControls(),
user.get().getOrganization().getId());
final Authentication newAuth = new UsernamePasswordAuthenticationToken(myCustomUser ,
null,
user.get().getAccessControls());
SecurityContextHolder.getContext().setAuthentication(newAuth);
session.setAttribute("SPRING_SECURITY_CONTEXT", newAuth);
}
else
{
//invalidate the session if the user could not be found
session.invalidate();
}
}
else
{
//invalidate the session if the user could not be found
session.invalidate();
}
}
finally
{
modifySessionSet(sessionId, false);
}
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
Upvotes: 8