How can I have list of all users logged in (via spring security) my web application

SpringWeb ApplicationsLoginSpring Security

Spring Problem Overview


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?

Spring Solutions


Solution 1 - Spring

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>

Solution 2 - Spring

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.

Solution 3 - Spring

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.

Solution 4 - Spring

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!

Solution 5 - Spring

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());
}

Solution 6 - Spring

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;
}

Solution 7 - Spring

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");
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatin KhView Question on Stackoverflow
Solution 1 - SpringdimasView Answer on Stackoverflow
Solution 2 - SpringAdamView Answer on Stackoverflow
Solution 3 - SpringelyschView Answer on Stackoverflow
Solution 4 - SpringrolyanosView Answer on Stackoverflow
Solution 5 - Springk13iView Answer on Stackoverflow
Solution 6 - SpringjvlemincView Answer on Stackoverflow
Solution 7 - SpringcyberdemonView Answer on Stackoverflow