Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Session Redis and Spring Security how to update user session?

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?

like image 533
Tim Schimandle Avatar asked Nov 30 '15 01:11

Tim Schimandle


People also ask

How to connect Redis with spring session data?

The first step is to add Spring Session Data Redis and Lettuce dependencies: Having the dependencies ok, it is time to configure the connection factory and enable Spring Session with Redis. This is done by the following config class:

Does spring security make use of session creation options?

But if the application creates one, Spring Security will make use of it. Finally, the strictest session creation option, “ stateless “, is a guarantee that the application won't create any session at all.

What is the use of Redis in Spring Boot?

Redis is a fast and easily scalable option. With sharding and clustering, Redis scales easily when the user base scales, also since session will expire after sometime, Redis expiring keys makes it a powerful and flexible solution. Spring session with Redis is a powerful and flexible solution in you are looking for:

How do I get session data from Spring Boot?

By Default Spring boot stores user session info in Server’s memory. If we have more than one instance of web application behind a load balancer, this will cause problem because the request has to be route to the same instance to retrieve session data. One solution is to use sticky session.


1 Answers

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:

  1. Modify User A Authority
  2. Send Message To Filter
  3. Add User A Session Keys to Set (In the filter)
  4. 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);
    }
    
like image 182
Tim Schimandle Avatar answered Oct 28 '22 04:10

Tim Schimandle