Spring引导会话管理 - 为什么会有两个sessionRegistry实例?

时间:2017-08-25 15:42:17

标签: java spring spring-mvc spring-boot spring-security

我正在努力实施强制退出'我的Spring Boot应用程序中的功能(例如,管理员禁用用户帐户后)。

我按照各种教程中指定的步骤访问会话注册表,以使用户的会话失效(baeldung step 6verbose version on myyurt;还related SO)。

但是,在将SessionRegistryImpl注册为@Bean之后,在使用调试器时,我发现依赖注入机制中有两个不同的实例:

  • Spring Security在登录和注销时使用一个sessionRegistry实例,并按预期保存主体和会话。下面的截图是在登录后拍摄的 - 我在registerNewSession()方法中有一个断点。注意已登录用户的id和地图。 registerNewSession() happens at login

  • 另一个sessionRegistry实例仅提供给我自己的SessionManager类,该类需要SessionRegistry作为依赖项并调用getAllPrincipals()。注意id是不同的,地图是空的(我多次登录后调用了getAllPrincipals()并拍了第一张截图) getAllPrincipals() query from my own class

我注册sessionRegistry bean的类(我删除了不必要的代码并只保留了我的自定义过滤器,以防它可能与Springs自动配置有关):

@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {

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

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

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

        http
            .addFilterBefore(myFirstFilter, UsernamePasswordAuthenticationFilter.class)
            .addFilterAfter(mySecondFilter, FilterSecurityInterceptor.class)
            .formLogin() // skipping details
            .and()
            .x509() // skipping details
            .and()
            .logout()
                .invalidateHttpSession(true)
                .permitAll()
            .and()
            .authorizeRequests() // skipping details
            .and()
            .sessionManagement()
                .invalidSessionUrl("/login")
                .enableSessionUrlRewriting(false)
                .maximumSessions(-1)
                    .maxSessionsPreventsLogin(false)
                    .sessionRegistry(sessionRegistry())
                    .expiredUrl("/login?expire");
    }    
}

我使用sessionRegistry依赖关系的类:

@Component
class DefaultSessionManager implements SessionManager {    
    private final SessionRegistry sessionRegistry;

    @Autowired public DefaultSessionManager(SessionRegistry sessionRegistry) {
        this.sessionRegistry = sessionRegistry;
    }

    public void expireUserSessions(String username) {
        for (Object principal : sessionRegistry.getAllPrincipals()) {
            // do stuff, but won't enter because the list is empty
        }
    }
}

如果有帮助,我用Actuator / beans端点查找bean设置,这就是它返回的内容

        {
            "bean": "defaultSessionManager",
            "aliases":
            [
            ],
            "scope": "singleton",
            "type": "com.foo.bar.DefaultSessionManager",
            "resource": // file path
            "dependencies":
            [
                "sessionRegistry"
            ]
        },

        {
            "bean": "httpSessionEventPublisher",
            "aliases":
            [
            ],
            "scope": "singleton",
            "type": "org.springframework.security.web.session.HttpSessionEventPublisher",
            "resource": "class path resource [com/foo/bar/SecurityConfig.class]",
            "dependencies":
            [
            ]
        },
        {
            "bean": "sessionRegistry",
            "aliases":
            [
            ],
            "scope": "singleton",
            "type": "org.springframework.security.core.session.SessionRegistryImpl",
            "resource": "class path resource [com/foo/bar/SecurityConfig.class]",
            "dependencies":
            [
            ]
        },

如果DI系统声明为Singleton,那么如何在DI系统中有两个不同的实例? 你有什么可能错的提示吗?

我使用的是spring-boot-starter-parent 1.5.2.RELEASE,它使用Spring Security 4.2.2.RELEASE。

1 个答案:

答案 0 :(得分:1)

问题是@Bean注释方法上的静态关键字。在您调用的配置中

.sessionRegistry(sessionRegistry())

它直接调用静态方法,而不是通过Spring代理,它将从应用程序上下文中获取bean。这意味着,对于您的安全配置,您需要创建一个新实例,而不是从应用程序上下文中获取该bean。使用非静态方法,Spring会拦截同一调用中的直接方法调用,然后Spring会检查bean是否已经存在于应用程序上下文中,如果是,则返回bean。