Spring Boot 会话管理——为啥会有两个 sessionRegistry 实例?
Posted
技术标签:
【中文标题】Spring Boot 会话管理——为啥会有两个 sessionRegistry 实例?【英文标题】:Spring boot session management - why are there two instances of sessionRegistry?Spring Boot 会话管理——为什么会有两个 sessionRegistry 实例? 【发布时间】:2018-02-03 17:15:12 【问题描述】:我正在尝试在我的 Spring Boot 应用程序中实现“强制注销”功能(例如,在管理员禁用用户帐户后)。
我按照各种教程中指定的步骤来访问会话注册表,以使用户的会话到期(baeldung step 6 和 verbose version on myyurt;还有related SO)。
但是,在将 SessionRegistryImpl 注册为@Bean 后,在使用调试器时,我看到依赖注入机制中有 2 个不同的实例:
Spring Security 在登录和注销时使用一个 sessionRegistry 实例,并按预期保存主体和会话。下面的屏幕截图是在登录后拍摄的——我在 registerNewSession() 方法中有一个断点。注意 id 和已登录用户的地图。
另一个 sessionRegistry 实例只提供给我自己的 SessionManager 类,它需要 SessionRegistry 作为依赖项并调用 getAllPrincipals()。请注意 id 不同并且地图为空(我在多次登录并拍摄第一个屏幕截图后调用了 getAllPrincipals())
我注册 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":
[
]
,
如果都声明为 Singleton,DI 系统中怎么会有两个不同的实例? 您对可能出现的问题有任何提示吗?
我正在使用 spring-boot-starter-parent 1.5.2.RELEASE,它使用 Spring Security 4.2.2.RELEASE。
【问题讨论】:
不知道有没有关系,但是@Bean
注解的方法不需要static
。由于 Spring 在配置类中使用代理等,这可能是一个问题,但我不确定。也许尝试删除静态关键字。
我在春天遇到过这样的问题。你能显示引用 SessionRegistries 的对象吗?就我而言,应用程序中有上下文。
实际上我认为静态关键字是问题所在,因为在您调用 .sessionRegistry(sessionRegistry())
的配置中,它会直接调用静态方法,而不是通过 Spring 代理从应用程序中获取 bean语境。这意味着,对于您的安全配置,您需要创建一个新实例,而不是从应用程序上下文中获取 bean。
谢谢@dunni,就是这样。我删除了静态字段,它现在可以工作了……我没想到。如果您愿意,请将其发布为答案,我会接受。干杯!
【参考方案1】:
问题在于 @Bean
注释方法上的静态关键字。在您调用的配置中
.sessionRegistry(sessionRegistry())
它直接调用静态方法,而不是通过 Spring 代理从应用程序上下文中获取 bean。这意味着,对于您的安全配置,您将创建一个新实例,而不是从应用程序上下文中获取 bean。对于非静态方法,相同调用中的直接方法调用会被 Spring 拦截,然后检查 bean 是否已存在于应用程序上下文中,如果存在则返回 bean。
【讨论】:
谢谢,docs 在这方面不是很有帮助。但我会牢记未来。那里发生了很多魔法。 一般的经验法则(不是官方的,只是从我的角度来看):静态和 Spring 通常不能很好地结合在一起。以上是关于Spring Boot 会话管理——为啥会有两个 sessionRegistry 实例?的主要内容,如果未能解决你的问题,请参考以下文章
通过 spring-boot 进行 Spring WS UsernameToken 身份验证 + 会话管理
如何使用 Spring Security 管理 Spring Boot 中的会话?