You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vaadin/Spring Boot中如何解决闲置会话过期后HTTP会话未重置问题?

在Vaadin+Okta认证应用中正确实现闲置会话过期

问题核心

集成Okta OAuth2认证的Vaadin应用中,当达到server.servlet.session.timeout设置的闲置时长后,Vaadin会显示自定义会话过期对话框,但用户点击窗口或按ESC后,HTTP会话(JSESSIONID)并未被终止或重建,系统检测到有效Okta会话会自动完成登录,无法触发重新认证流程。后续添加server.servlet.session.cookie.max-age配置解决了会话不刷新的问题,但该配置会强制用户重新登录,无法识别用户的活动状态。

当前配置

SecurityConfiguration

@EnableWebSecurity
@Configuration
@Order(99)
public class S1SecurityConfiguration extends SecurityConfiguration {
    ...
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http); // 应用默认配置,处理匿名用户

        http.oauth2Login(oauth2 ->
            oauth2
            .userInfoEndpoint(userInfo -> userInfo.oidcUserService(oidcUserService()))
            .authorizationEndpoint(authEndpoint -> authEndpoint
                .authorizationRequestResolver(
                    new ForcePromptLoginRequestResolver(
                        clientRegistrationRepository,
                        "/oauth2/authorization"
                    )
                )
            )
            .successHandler(s1authSuccessHandler)
            .failureHandler(authFailureHandler)

        );

        // 启用会话并发管理
        http.sessionManagement(sessionManagement ->
            sessionManagement
            .sessionFixation(sessionFixation -> sessionFixation.migrateSession())
            .sessionConcurrency(sessionConcurrency ->
                sessionConcurrency
                .sessionRegistry(sessionRegistry())
                .maximumSessions(1)
                .maxSessionsPreventsLogin(false) // 允许第二次登录,同时使第一次登录的会话失效
                .expiredUrl("/session-expired") // 会话过期时跳转的页面
            )
        );

        ...
    }

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

    @Bean
    public SessionAuthenticationStrategy sessionAuthenticationStrategy(SessionRegistry sessionRegistry) {
        // 并发会话控制策略
        ConcurrentSessionControlAuthenticationStrategy concurrencyStrategy =
            new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
        concurrencyStrategy.setMaximumSessions(1);
        concurrencyStrategy.setExceptionIfMaximumExceeded(true); // 等同于maxSessionsPreventsLogin(true)

        // 组合会话固定保护与并发控制策略
        return new CompositeSessionAuthenticationStrategy(
            Arrays.asList(
                new ChangeSessionIdAuthenticationStrategy(), // 也可使用MigrateSession
                concurrencyStrategy
            )
        );
    }

    @Bean
    public S1AuthenticationSuccessHandler s1authFailureHandler(SessionAuthenticationStrategy sessionAuthenticationStrategy) {
        return new S1AuthenticationSuccessHandler(sessionAuthenticationStrategy);
    }

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

    @Bean
    public OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
        final OidcUserService delegate = new OidcUserService();
        return userRequest -> {
            // 通过委托加载默认OidcUser
            OidcUser oidcUser = delegate.loadUser(userRequest);

            String principalName = oidcUser.getAttribute("email");

            // 返回自定义SimpleOidcUser,仅用邮箱做 equality 校验,保证并发检查生效
            return new SimpleOidcUser(
                oidcUser.getAuthorities(),
                oidcUser.getIdToken(),
                oidcUser.getUserInfo(),
                principalName
            );
        };
    }
}

application.properties

server.servlet.session.timeout=30m
# 设置closeIdleSessions为true,避免心跳/推送请求重置会话闲置计时器
vaadin.closeIdleSessions=true

SessionExpiredMessageInitServiceListener

@Component
public class SessionExpiredMessageInitServiceListener implements VaadinServiceInitListener {

    @Override
    public void serviceInit(ServiceInitEvent event) {
        event.getSource().setSystemMessagesProvider(new SystemMessagesProvider() {
            @Override
            public CustomizedSystemMessages getSystemMessages(SystemMessagesInfo systemMessagesInfo) {
                CustomizedSystemMessages messages = new CustomizedSystemMessages();
                messages.setSessionExpiredCaption("Session expired");
                messages.setSessionExpiredMessage(
                        "Your session has expired. Press ESC or click anywhere in this window to continue."
                );
                // 会话过期跳转的URL
                messages.setSessionExpiredURL("/envdata");
                messages.setSessionExpiredNotificationEnabled(true);
                return messages;
            }
        });
    }
}

解决方案

1. 同步销毁Vaadin会话与HTTP会话

添加Vaadin会话销毁监听器,当Vaadin会话因闲置过期被销毁时,同步销毁对应的HTTP会话:

@Component
public class VaadinSessionDestroyListener implements VaadinSessionDestroyListener {

    @Override
    public void sessionDestroy(VaadinSessionDestroyEvent event) {
        HttpServletRequest request = VaadinServletRequest.getCurrent().getHttpServletRequest();
        if (request != null) {
            // 销毁HTTP会话,清除JSESSIONID
            request.getSession().invalidate();
        }
    }
}

2. 修改会话过期跳转逻辑,强制触发重新认证

创建一个专门的端点用于销毁会话并跳转至Okta认证入口,替换原有的sessionExpiredURL:

// 在SecurityConfiguration中添加端点
@GetMapping("/session-expired-handler")
public String handleSessionExpired(HttpServletRequest request) {
    // 销毁当前HTTP会话
    request.getSession().invalidate();
    // 跳转至Okta认证入口,触发重新登录
    return "redirect:/oauth2/authorization/okta";
}

然后更新SessionExpiredMessageInitServiceListener中的跳转URL:

messages.setSessionExpiredURL("/session-expired-handler");

该配置会强制Cookie在指定时间后过期,忽略用户的活动状态,直接移除即可恢复基于闲置时长的会话过期逻辑。

4. 优化Spring Security会话管理

确保会话过期后,Spring Security能正确清除认证上下文:

  • 检查HttpSessionEventPublisher是否正常工作,它负责向Spring Security发布会话销毁事件,确保SessionRegistry能及时更新会话状态。
  • 确认sessionConcurrency配置中的expiredUrl指向上述的/session-expired-handler端点,保证会话过期后统一处理。

验证步骤

  1. 设置较短的会话超时时间(如1分钟)进行测试。
  2. 等待会话过期,确认Vaadin显示自定义对话框。
  3. 点击窗口或按ESC,确认被跳转至Okta登录页面,而非自动登录。
  4. 登录后检查新的JSESSIONID是否与之前不同,确保会话已重建。

内容的提问来源于stack exchange,提问作者Georgi Koemdzhiev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 08:55:56