Search code examples
javaspringspring-bootspring-securityspring-messaging

How to get the list of users properly with Spring?


I want to get the list of all authenticated users.
I took the basic spring-security example from the official Spring site.

As it was recommended in other relative questions (51992610), I injected the DefaultSimpUserRegistry into the code. Still, the list is empty.

@Configuration
public class UsersConfig {
    final private SimpUserRegistry userRegistry = new DefaultSimpUserRegistry();
    
    @Bean
    @Primary
    public SimpUserRegistry userRegistry() {
        return userRegistry;
    }
}
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                            .antMatchers("/", "/home").permitAll()
                            .anyRequest().authenticated()
                            .and()
                    .formLogin()
                            .loginPage("/login")
                            .permitAll()
                            .and()
                    .logout()
                            .permitAll();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
            UserDetails user =
                     User.withDefaultPasswordEncoder()
                            .username("u")
                            .password("11")
                            .roles("USER")
                            .build();

            return new InMemoryUserDetailsManager(user);
    }
}
@RestController
public class WebSocketController {

    @Autowired
    private final SimpUserRegistry simpUserRegistry;

    public WebSocketController(SimpUserRegistry simpUserRegistry) {
        this.simpUserRegistry = simpUserRegistry;
    }

    @GetMapping("/users")
    public String connectedEquipments() {
        
        return this.simpUserRegistry
                .getUsers()
                .stream()
                .map(SimpUser::getName)
                .collect(Collectors.toList()).toString();
    }
}

Build jar, launch locally, login, enter http://localhost:8080/users. Result:

[]

The full code may be taken from the Spring site. The topics on SimpUserRegistry are so rare, I can't find a full example with it. The similar posts are unanswered yet (48804780, 58925128).

Sorry, I am new to Spring, is SimpUserRegistry the correct way to list users with Spring? If so, how to use it properly? Thanks.


Solution

  • Within your question, you're trying a few things:

    1. You're setting up InMemoryUserDetailsManager with a list of allowed users (in your case a user called u).
    2. You're using SimpUserRegistry to get a list of all connected users through Spring messaging (for example using WebSockets).

    If you're just trying to get a list of all users, and you're not using WebSockets, then the second approach won't work.

    If you're trying to get a list of all users that are stored within InMemoryUserDetailsManager, then the answer is that it's not possible to get that list. InMemoryUserDetailsManager uses an in-memory Map to store all users, and it doesn't expose that list.

    If you really want such a list, you'll have to create a custom in-memory UserDetailsService, for example:

    @Service
    public class ListingInMemoryUserDetailsService implements UserDetailsService {
        private final Map<String, InMemoryUser> users;
    
        public ListingInMemoryUserDetailsService() {
            this.users = new HashMap<>();
        }
    
        public ListingInMemoryUserDetailsService(UserDetails... userDetails) {
            this.users = stream(userDetails)
                .collect(Collectors.toMap(UserDetails::getUsername, InMemoryUser::new));
        }
    
        public Collection<String> getUsernames() {
            return users
                .values()
                .stream()
                .map(InMemoryUser::getUsername)
                .collect(toList());
        }
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            return Optional
                .ofNullable(users.get(username))
                .orElseThrow(() -> new UsernameNotFoundException("User does not exist"));
        }
    }
    

    In this example, InMemoryUser is an implementation of the UserDetails interface. When you create a custom implementation like that, you'll have to configure it with Spring Security:

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder);
    }
    

    Alternatively, if you're interested in retrieving a list of all created sessions, there's a better approach. First, you'll have to create a SessionRegistry bean:

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

    Then, you'll have to configure Spring Security to set up sessions, and to use your SessionRegistry to do that:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login").permitAll()
                .and()
            .logout().permitAll()
            // Add something like this:
            .sessionManagement()
                .maximumSessions(1)
                .sessionRegistry(sessionRegistry);
    }
    

    After that, you can autowire SessionRegistry, and use the getAllPrincipals() method:

    @GetMapping("/users")
    public Collection<String> findUsers() {
        return sessionRegistry
            .getAllPrincipals()
            .stream()
            .map(this::getUsername)
            .flatMap(Optional::stream)
            .collect(toList());
    }
    
    private Optional<String> getUsername(Object principal) {
        if (principal instanceof UserDetails) {
            return Optional.ofNullable(((UserDetails) principal).getUsername());
        } else {
            return Optional.empty();
        }
    }
    

    This will list all usernames of users that logged in within the application, and had a session. This also includes expired sessions, so you may want to filter on those as well.