1

I inherited a Spring Boot 1.5 project which cannot be migrated up at the moment and need to work on session registry to manage users (ex: list logged users, email users of production updates etc.)

I tried all existing SO solutions I could find, but all of them provide a null result for sessionRegistry().getAllPrincipals(). My security config looks like:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .httpBasic().disable()
            .formLogin().disable()
            .headers().frameOptions().sameOrigin()
        .and()  
            .sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    @Bean
    public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
        return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher());
    }

}

My Application config looks like:

@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600000)
@EnableDiscoveryClient
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@EnableZuulProxy
@EnableFeignClients
@EnableSwagger2
@SpringBootApplication
@RibbonClients({
    @RibbonClient(name = "employeeService", configuration = StickySessionEditorRibbonConfiguration.class)
})
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public PreFilter preFilter() {
        return new PreFilter();
    }

    @Bean
    public PostFilter postFilter() {
        return new PostFilter();
    }

    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("JSESSIONID");
        serializer.setCookiePath("/");
        serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
        serializer.setCookieMaxAge(3600000);
        return serializer;
    }
}

Relevant code to access the session registry looks like this:

public class TestController extends BaseController {
    @Autowired
    private SessionRegistry sessionRegistry;

    ...

    public List<User> findAllLoggedInUsers() {
        final List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
    }
}

Using the actuator/bean endpoint I can see that the SessionRegistry Bean is active.

I am logging in successfully from a couple of browsers but allPrincipals is always size 0 before and after the logins.

Am at a loss why, and any help here is much appreciated.

Based on @M.Deinum 's comment regarding disabled login I want to add that the project uses Zuul Filters (preFilter and PostFilter) as indicated in the application config. We have an account-manager service completely different from this api-gateway service, which authenticates users based on simple login/password. The logic in preFilter looks like this:

public class PreFilter extends BaseFilter {

@Autowired
SessionRegistry sessionRegistry;

@Autowired
private SessionRepository sessionRepository;

@Override
public String filterType() {
    return "pre";
}

@Override
public boolean shouldFilter() {
    return true;
}

@Override
public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    HttpServletRequest req = ctx.getRequest();
    HttpSession session = req.getSession();
    try {

        String uri = req.getRequestURI();

        // user assumed is not authenticated
        String authToken = null;

        //Login code
        if (uri.contains("/api/public/authorization/login") && req.getMethod().equals("POST")) {

            session.removeAttribute(AUTH_TOKEN_HEADER);
            LoginRequest request = createLoginRequest(req);
            /* LoginRequest basically contains "userName" and "password" entered by user */

            ResponseEntity<MessageWrapper<String>> response = accountManagerFeignClient.authenticate(loginRequest);

            authToken = response.getBody().getData();

            if (authToken != null) {
                session.setAttribute(AUTH_TOKEN_HEADER, authToken);
                ctx.setResponseStatusCode(HttpStatus.OK.value());
                ctx.setSendZuulResponse(false);
                return null;
            }
            //  authToken == null implies the user was not authenticated by accountManager

            } else if ("internal or public apis are called, they won't need authentication") {
           // user remains unauthenticated, which is fine for public or internal apis
               return null; 

            } else {
               // Assume this is a protected API and provide authToken if one exists
               authToken = (String) session.getAttribute(AUTH_TOKEN_HEADER);
            }

           if (authToken == null)
              throw new Exception(UNAUTHORIZED + ". Log String: " + logString);

           // Validated user will go through here
           ctx.addZuulRequestHeader(AUTH_TOKEN_HEADER, authToken);
       } catch (Exception ex) {
       ctx.setResponseBody(UNAUTHORIZED);
       ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
       ctx.setSendZuulResponse(false);
   }

   return null;

} }

The only relevant logic in postFilter (similar to preFilter) disables sessions during logout in this manner:

       if (authToken != null) {

            session.removeAttribute(AUTH_TOKEN_HEADER);
            session.removeAttribute(StickySessionEditorRule.STICKY_ID);
            session.removeAttribute(StickySessionWSGRule.STICKY_ID);


            ctx.setResponseBody(LOGGED_OUT);
            ctx.setResponseStatusCode(HttpStatus.OK.value());
            ctx.setSendZuulResponse(false);
        }
        session.invalidate();

My other time-consuming option would be to use the HTTPSessionBindingListener as shown here . I have not tried that yet.

Finally, if none of the above work, how could I work directly with redis and do a findAll() ? It looks like there is a SessionRepository, but I could not find a documented way of using it.

Thank You.

Ya.Ma
  • 21
  • 6
  • Is your `SecurityConfig` actually an `@Configuration`? Also the default implementation you are using is in-memory so not sure how using Redis would help here? To see what is happening enable `trace` or `debug` logging for `org.springframework.security` and see if you get the session-created/session-destroyed events. Also you seem to have disabled all means of login, so how can one login? – M. Deinum Jun 16 '20 at 05:38
  • @M.Deinum Thanks for the response. Adding `@Configuration` to `SecurityConfig` did not help. I updated post to show how login/out is being performed. Autowiring sessionRepository and sessionRegistry in preFilter to see that `org.springframework.session.data.redis.RedisOperationsSessionRepository` exists. The session repository must exist. The combination of ZuulFilters and Spring Security is managing it. There is no other EXPLICIT code which manipulates the session repo. I do not know how to enable trace/debug for org.springframework.security but I will research that. Thanks, again. – Ya.Ma Jun 17 '20 at 06:39
  • I don't understand the filter, why have a `SessionRegistry` that doesn't know about the `SessionRepository`? Nor are the fields being used. So I really dont understand how this should even work. Nothing is ever going to be set in the registry. Feels like you are trying to combine Spring Session and Session Management in a kind of stateless application and working around Spring Session. – M. Deinum Jun 17 '20 at 08:04
  • 1
    @M.Deinum I think you are correct that `SessionRegistry` should not be used. I am new to this project and Spring Security so don't quite understand the difference between `SessionRegistry` and `SessionRepository` yet. Maybe I will work on getting the information via Redis. The annotations say that Redis is being used. Thanks, again. – Ya.Ma Jun 17 '20 at 17:41
  • 1
    The `SessionRepository` is used for Spring Session which is for storing the http session state in a shared repository. The `SessionRegistry` is a part of Spring Security which is used to manage concurrent session control. Depending on your usecase (which you didn't really describe) you don't need the `SessionRegistry` or simply can write an implementation for Spring Session. The `SessionRegistry` used by default is an in-memory one and will not work in a multi-instance environment. IMHO there are better ways on how to share security details with Spring Security, Session between services. – M. Deinum Jun 18 '20 at 05:21
  • 1
    @M.Deinum Since I am looking for user sessions across instances, I could use `Set redisKeys = redisTemplate.keys("spring:session:sessions:*");` to get session data...and I got the session keys. But could you clue me into how to obtain the user data given I have the keys? Any pointers here would be helpful. Thanks in advance. – Ya.Ma Jun 20 '20 at 00:56
  • If you're using custom authentication then it will not work out of the box and have to make some configurations. Refer this https://stackoverflow.com/a/65542389/9004116 – It's K Jan 02 '21 at 18:11

0 Answers0