如何获得通过Web安全性登录的所有用户的列表(通过Spring Security)


70

我在Web应用程序中使用Spring Security,现在我想获得一个已登录程序的所有用户的列表。

我如何访问该列表?他们不是已经在Spring框架中保留了什么吗?像SecurityContextHolderSecurityContextRepository吗?


如果您使用的是自定义身份验证,那么它将无法立即使用,必须进行一些配置。请参阅此stackoverflow.com/a/65542389/9004116
它是

Answers:


62

要访问所有已登录用户的列表,需要将SessionRegistry实例注入到bean中。

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

然后使用注入的SessionRegistry您可以访问所有主体的列表:

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

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

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

但是在注入会话注册表之前,您需要在spring-security.xml中定义会话管理部分(请参阅Spring Security参考文档中的“会话管理”部分),并在并发控制部分中,应为会话注册表对象设置别名(session-registry-别名),您将通过它注入。

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

我不明白,您怎么说主体对象是User bean的实例。你可以解释吗?是否有任何配置将sessionregistry与User Bean连接?用户Bean如何存储在Sessionregistry主体中?
Newinjava '19

45

在JavaConfig中,它看起来像这样:

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

调用代码如下所示:

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

请注意,这SecurityUser是我自己实现的类UserDetails


15
这个答案似乎是正确的,但对我来说,它总是返回并且是空集合,任何想法吗?
azerafati 2015年

3
作为记录,ServletListenerRegistrationBean类是Spring Boot的一部分,因此您将不得不在Spring Boot中添加一个依赖项。在我的组织中,只有Spring MVC和Spring Security被批准使用,因此我不能使用您的解决方案(顺便说一句,真棒)。将不得不寻找另一种方式来实现这一目标。谢谢!
Charles Morin

@mailman是的,我正在保存会话,并且从该会话中我可以获取已登录用户的列表
Ravi H

@mailman告诉我您是否需要帮助,或者您也可以使用httpListeners全部登录用户
Ravi H

3
如果您使用手动认证,则@azerafati答案无效,因此请记住,还需要将用户置于sessionRegistry中:sessionRegistry.registerNewSession(request.getSession()。getId(),auth.getPrincipal());
MeetJoeBlack

9

如果我错了,请纠正我。

我认为@Adam的答案不完整。我注意到列表中已经过期的会话再次出现。

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

希望能帮助到你。


我不明白,您怎么说主体对象是SecurityUser bean的实例。你可以解释吗?是否有任何配置将sessionregistry与SecurityUser bean连接?SecurityUser bean如何存储在Sessionregistry主体中?
Newinjava '19

我现在无法访问源代码,但是可以说它确实有效。我希望能有时间尽快研究它,但是我不能保证现在。抱歉
elysch

7

如果我也错了,请纠正我。

我认为@Adam和@elysch的答案不完整。我注意到需要添加侦听器:

 servletContext.addListener(HttpSessionEventPublisher.class);

public class AppInitializer implements WebApplicationInitializer {

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

使用安全配置:

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

然后,您将获得当前的在线用户!


我正在使用Spring Boot,并在声明HttpSessionEventPublisher bean之后将其自动拾取并使用。
FAP

3

您需要注入SessionRegistry(如前面提到的eariler),然后可以在一个管道中执行以下操作:

public List<UserDetails> findAllLoggedInUsers() {
    return sessionRegistry.getAllPrincipals()
            .stream()
            .filter(principal -> principal instanceof UserDetails)
            .map(UserDetails.class::cast)
            .collect(Collectors.toList());
}

2

发现此注释非常重要且相关:

“ [21]通过在身份验证后执行重定向的机制(例如,表单登录)进行的身份验证将不会被SessionManagementFilter检测到,因为在身份验证请求期间不会调用该过滤器。会话管理功能必须在这些请求中分别处理案件。”

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

同样,很显然,很多人在让sessionRegistry.getAllPrincipals()返回不同于空数组的内容时遇到麻烦。就我而言,我通过将sessionAuthenticationStrategy添加到我的自定义authenticationFilter来解决此问题

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

  authenticationFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
}

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

//cf. /programming/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;
}

1

与@rolyanos解决方案类似,对我来说,我的作品始终有效:

-对于控制器

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

-对于前端

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

-用于春季发泄

@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");
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.