77

I'm using spring security in my web application, and now I want to have a list of all users who are logged in my program.

How can I have access to that list? Aren't they already kept somewhere within spring framework? Like SecurityContextHolder or SecurityContextRepository?

Al-Mothafar
  • 7,949
  • 7
  • 68
  • 102
Matin Kh
  • 5,192
  • 6
  • 53
  • 77
  • 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:07

7 Answers7

66

For accessing the list of all logged in users you need to inject SessionRegistry instance to your bean.

@Autowired
@Qualifier("sessionRegistry")
private SessionRegistry sessionRegistry;

And then using injcted SessionRegistry you can access the list of all principals:

List<Object> principals = sessionRegistry.getAllPrincipals();

List<String> usersNamesList = new ArrayList<String>();

for (Object principal: principals) {
    if (principal instanceof User) {
        usersNamesList.add(((User) principal).getUsername());
    }
}

But before injecting session registry you need to define session management part in your spring-security.xml (look at Session Management section in Spring Security reference documentation) and in concurrency-control section you should set alias for session registry object (session-registry-alias) by which you will inject it.

    <security:http access-denied-page="/error403.jsp" use-expressions="true" auto-config="false">
        <security:session-management session-fixation-protection="migrateSession" session-authentication-error-url="/login.jsp?authFailed=true"> 
            <security:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url="/login.html" session-registry-alias="sessionRegistry"/>
        </security:session-management>

    ...
    </security:http>
dimas
  • 6,033
  • 36
  • 29
  • I do not get, how do you say that the principal object is an instance of the User bean. Can you explain? Is there any configuration to connect sessionregistry with User bean? How do User beans get stored in Sessionregistry principals? – Newinjava Jan 23 '19 at 07:17
52

In JavaConfig, it would look like this:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        // ...
        http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());
    }

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

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

With the calling code looking like this:

public class UserController {
    @Autowired
    private SessionRegistry sessionRegistry;

    public void listLoggedInUsers() {
        final List<Object> allPrincipals = sessionRegistry.getAllPrincipals();

        for(final Object principal : allPrincipals) {
            if(principal instanceof SecurityUser) {
                final SecurityUser user = (SecurityUser) principal;

                // Do something with user
                System.out.println(user);
            }
        }
    }
}

Note that SecurityUser is my own class which implements UserDetails.

Adam
  • 2,616
  • 33
  • 29
  • 17
    this answers seems to be correct, but for me it's always returning and empty collection, any idea?? – azerafati Apr 13 '15 at 10:42
  • 3
    For the record, the ServletListenerRegistrationBean class is part of Spring Boot, so you will have to add a dependency with Spring Boot. In my organization, only Spring MVC and Spring Security are approved for use, so I can't use your solution (that looks awesome by the way). Will have to find another way to achieve this. Thanks! – Charles Morin Jul 20 '15 at 12:53
  • @mailman yeah i am saving session , and from that session i am getting list of logged in users – Ravi H Feb 25 '16 at 14:45
  • @mailman let me know if u want any help, and alternatively you can have all logged in users using httpListeners – Ravi H Feb 25 '16 at 14:49
  • 4
    @azerafati answer doesnt work if you use manual athentication, so remember that you also need to put user to the sessionRegistry: sessionRegistry.registerNewSession(request.getSession().getId(), auth.getPrincipal()); – MeetJoeBlack Oct 03 '16 at 20:23
  • I do not get, how do you say that the principal object is an instance of the SecurityUser bean. Can you explain? Is there any configuration to connect sessionregistry with SecurityUser bean? How does SecurityUser bean gets stored in Sessionregistry principals? – Newinjava Jan 23 '19 at 07:32
  • When it would remove the user from the principal list ? Like I logged in with the user A and then logged in with user B it shows that the both are in list of principals, but when then I logged out from both A and B and logged in with C its showing me 3 user logged in. Do I need to remove manually from the list ? – Root Apr 30 '19 at 11:28
  • How I see that's not working for session restored by Spring Session from storage like Redis in case the application was restarted? Any ideas about that? Is there a way to see all sessions including restored? – Alexey Usharovski Jul 03 '19 at 07:55
  • The session registry seem to be memory based, which will cause problems if the server is restarted or you run your application in a clustered environment. A better solution is to use Spring Session's sessionRepository to get data about user sessions. – Emil Feb 20 '20 at 17:09
  • @MeetJoeBlack where do I put this code "sessionRegistry.registerNewSession()" ? – It's K Dec 13 '20 at 09:56
10

Please correct me if I'm wrong.

I think @Adam's answer is incomplete. I noticed that sessions already expired in the list were appearing again.

public class UserController {
    @Autowired
    private SessionRegistry sessionRegistry;

    public void listLoggedInUsers() {
        final List<Object> allPrincipals = sessionRegistry.getAllPrincipals();

        for (final Object principal : allPrincipals) {
            if (principal instanceof SecurityUser) {
                final SecurityUser user = (SecurityUser) principal;

                List<SessionInformation> activeUserSessions =
                        sessionRegistry.getAllSessions(principal,
                                /* includeExpiredSessions */ false); // Should not return null;

                if (!activeUserSessions.isEmpty()) {
                    // Do something with user
                    System.out.println(user);
                }
            }
        }
    }
}

Hope it helps.

HopeKing
  • 3,317
  • 7
  • 39
  • 62
elysch
  • 1,846
  • 4
  • 25
  • 43
  • I do not get, how do you say that the principal object is an instance of the SecurityUser bean. Can you explain? Is there any configuration to connect sessionregistry with SecurityUser bean? How does SecurityUser bean gets stored in Sessionregistry principals? – Newinjava Jan 23 '19 at 07:32
  • I can't access right now my source code, but I can say it does work. I hope to find some time to look into it soon, but I can't promise just now. Sorry – elysch Feb 01 '19 at 17:57
7

Please correct me if I'm wrong too.

I think @Adam's and @elysch`s answer is incomplete. I noticed that there are needed to add listener:

 servletContext.addListener(HttpSessionEventPublisher.class);

to

public class AppInitializer implements WebApplicationInitializer {

@Override
public void onStartup(ServletContext servletContext) {
  ...
servletContext.addListener(HttpSessionEventPublisher.class);
}

with security conf:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        // ...
        http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());
    }

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

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

And then you will get current online users!

rolyanos
  • 143
  • 2
  • 9
  • I'm using Spring Boot and after declaring the HttpSessionEventPublisher bean it was picked up and used automatically. – fap Mar 19 '18 at 00:40
5

You need to inject SessionRegistry (as mentioned eariler) and then you can do it in one pipeline like this:

public List<UserDetails> findAllLoggedInUsers() {
    return sessionRegistry.getAllPrincipals()
            .stream()
            .filter(principal -> principal instanceof UserDetails)
            .map(UserDetails.class::cast)
            .collect(Collectors.toList());
}
k13i
  • 4,011
  • 3
  • 35
  • 63
2

Found this note to be quite important and relevant:

"[21] Authentication by mechanisms which perform a redirect after authenticating (such as form-login) will not be detected by SessionManagementFilter, as the filter will not be invoked during the authenticating request. Session-management functionality has to be handled separately in these cases."

https://docs.spring.io/spring-security/site/docs/3.1.x/reference/session-mgmt.html#d0e4399

Also, apparently a lot of people have troubles getting sessionRegistry.getAllPrincipals() returning something different from an empty array. In my case, I fixed it by adding the sessionAuthenticationStrategy to my custom authenticationFilter:

@Bean
public CustomUsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
...

  authenticationFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
}

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

//cf. https://stackoverflow.com/questions/32463022/sessionregistry-is-empty-when-i-use-concurrentsessioncontrolauthenticationstrate
public SessionAuthenticationStrategy sessionAuthenticationStrategy() {
    List<SessionAuthenticationStrategy> stratList = new ArrayList<>();
    SessionFixationProtectionStrategy concStrat = new SessionFixationProtectionStrategy();
    stratList.add(concStrat);
    RegisterSessionAuthenticationStrategy regStrat = new RegisterSessionAuthenticationStrategy(sessionRegistry());
    stratList.add(regStrat);
    CompositeSessionAuthenticationStrategy compStrat = new CompositeSessionAuthenticationStrategy(stratList);
    return compStrat;
}
jvleminc
  • 71
  • 1
  • 3
1

Similar to @rolyanos solution, mine for me always works:

- for the controller

@RequestMapping(value = "/admin")
public String admin(Map<String, Object> model) {

    if(sessionRegistry.getAllPrincipals().size() != 0) {
        logger.info("ACTIVE USER: " + sessionRegistry.getAllPrincipals().size());
        model.put("activeuser",  sessionRegistry.getAllPrincipals().size());
    }
    else
        logger.warn("EMPTY" );

    logger.debug(log_msg_a + " access ADMIN page. Access granted." + ANSI_RESET);
    return "admin";
}

- for the front end

<tr th:each="activeuser, iterStat: ${activeuser}">
    <th><b>Active users: </b></th> <td align="center" th:text="${activeuser}"></td>
    </tr>

- for spring confing

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

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


@Override
protected void configure(HttpSecurity http) throws Exception {

    http.logout()
    .logoutSuccessUrl("/home")
    .logoutUrl("/logout")
    .invalidateHttpSession(true)
    .deleteCookies("JSESSIONID");


    http.authorizeRequests()
    .antMatchers("/", "/home")
    .permitAll()

    .antMatchers("/admin")
    .hasRole("ADMIN") 
    .anyRequest()
    .authenticated()

    .and()
    .formLogin()
    .loginPage("/home")
    .defaultSuccessUrl("/main")
    .permitAll()
    .and()
    .logout()
    .permitAll();

    http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());

    http.authorizeRequests().antMatchers("/webjars/**").permitAll();

    http.exceptionHandling().accessDeniedPage("/403");
}
cyberdemon
  • 43
  • 2
  • 10