首页 > 解决方案 > Spring Boot 1.5 sessionRegistry().getAllPrincipals() 总是返回空列表

问题描述

我继承了一个 Spring Boot 1.5 项目,该项目目前无法向上迁移,需要使用会话注册表来管理用户(例如:列出登录用户、生产更新的电子邮件用户等)

我尝试了所有我能找到的现有 SO 解决方案,但它们都为 sessionRegistry().getAllPrincipals() 提供了一个空结果。我的安全配置如下所示:

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

}

我的应用程序配置如下所示:

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

访问会话注册表的相关代码如下所示:

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

    ...

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

使用执行器/bean 端点,我可以看到 SessionRegistry Bean 处于活动状态。

我从几个浏览器成功登录,但登录前后 allPrincipals 的大小始终为 0。

我不知道为什么,非常感谢这里的任何帮助。

基于@M.Deinum 关于禁用登录的评论,我想补充一点,该项目使用 Zuul 过滤器(preFilter 和 PostFilter),如应用程序配置中所示。我们有一个与这个 api-gateway 服务完全不同的 account-manager 服务,它基于简单的登录名/密码对用户进行身份验证。preFilter 中的逻辑如下所示:

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;

} }

postFilter 中唯一相关的逻辑(类似于 preFilter)以这种方式在注销期间禁用会话:

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

我的另一个耗时的选择是使用 HTTPSessionBindingListener ,如此处所示。我还没有尝试过。

最后,如果上述方法都不起作用,我怎么能直接使用 redis 并执行 findAll() ?看起来有一个 SessionRepository,但我找不到使用它的文档化方式。

谢谢你。

标签: javaspring-bootsessionspring-security

解决方案


推荐阅读