Vaadin 23.2.5应用重新部署后无法完整重载问题求助
Vaadin 23.2.5 生产环境重部署后加载停滞问题解决
问题描述
生产环境重部署后,应用无法完成重载,顶部持续显示蓝色进度条;浏览器控制台报以下JS错误:
- FireFox:
Cannot read properties of undefined (reading 'length') - Chrome:
Cannot read properties of undefined (reading '0')
已移除应用中所有Grid组件,问题仍未解决。
配置代码
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true) public class SecurityConfiguration extends VaadinWebSecurityConfigurerAdapter { private final ClientRegistrationRepository clientRegistrationRepository; private final GrantedAuthoritiesMapper authoritiesMapper; private final ProfileService profileService; SecurityConfiguration(ClientRegistrationRepository clientRegistrationRepository, GrantedAuthoritiesMapper authoritiesMapper, ProfileService profileService) { this.clientRegistrationRepository = clientRegistrationRepository; this.authoritiesMapper = authoritiesMapper; this.profileService = profileService; SecurityContextHolder.setStrategyName(VaadinAwareSecurityContextHolderStrategy.class.getName()); } @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); http // Enable OAuth2 login .oauth2Login(oauth2Login -> oauth2Login .clientRegistrationRepository(clientRegistrationRepository) .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint // Use a custom authorities mapper to get the roles from the identity provider into the Authentication token .userAuthoritiesMapper(authoritiesMapper) ) // Use a Vaadin aware authentication success handler .successHandler(new KeycloakVaadinAuthenticationSuccessHandler(profileService)) ) // Configure logout .logout(logout -> logout // Enable OIDC logout (requires that we use the 'openid' scope when authenticating) .logoutSuccessHandler(logoutSuccessHandler()) // When CSRF is enabled, the logout URL normally requires a POST request with the CSRF // token attached. This makes it difficult to perform a logout from within a Vaadin // application (since Vaadin uses its own CSRF tokens). By changing the logout endpoint // to accept GET requests, we can redirect to the logout URL from within Vaadin. .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")) ); } @Bean @Primary public SpringViewAccessChecker springViewAccessChecker(AccessAnnotationChecker accessAnnotationChecker) { return new KeycloakSpringViewAccessChecker(accessAnnotationChecker, "/oauth2/authorization/keycloak"); } private OidcClientInitiatedLogoutSuccessHandler logoutSuccessHandler() { var logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository); logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}"); return logoutSuccessHandler; } @Override public void configure(WebSecurity web) throws Exception { super.configure(web); // Don't apply security rules on our static pages web.ignoring().antMatchers("/session-expired", "/images/*"); } @Bean public PolicyFactory htmlSanitizer() { // This is the policy we will be using to sanitize HTML input return Sanitizers.FORMATTING.and(Sanitizers.BLOCKS).and(Sanitizers.STYLES).and(Sanitizers.LINKS); } } @Component class VaadinSessionConfiguration implements VaadinServiceInitListener, SystemMessagesProvider, SessionDestroyListener { private final String relativeSessionExpiredUrl; VaadinSessionConfiguration(ServerProperties serverProperties) { relativeSessionExpiredUrl = UriComponentsBuilder.fromPath(serverProperties.getServlet().getContextPath()).path("logout").build().toUriString(); } @Override public SystemMessages getSystemMessages(SystemMessagesInfo systemMessagesInfo) { var messages = new CustomizedSystemMessages(); // Redirect to a specific screen when the session expires. In this particular case we don't want to logout // just yet. If you would like the user to be completely logged out when the session expires, this URL // should the logout URL. messages.setSessionExpiredURL(relativeSessionExpiredUrl); return messages; } @Override public void sessionDestroy(SessionDestroyEvent event) { // We also want to destroy the underlying HTTP session since it is the one that contains the authentication // token. try { event.getSession().getSession().invalidate(); } catch (Exception ignore) { // Session was probably already invalidated. } } @Override public void serviceInit(ServiceInitEvent event) { event.getSource().setSystemMessagesProvider(this); event.getSource().addSessionDestroyListener(this); } } public final class VaadinAwareSecurityContextHolderStrategy implements SecurityContextHolderStrategy { private final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>(); @Override public void clearContext() { contextHolder.remove(); } @Override @NonNull public SecurityContext getContext() { var context = contextHolder.get(); if (context == null) { context = getFromVaadinSession().orElseGet(() -> { var newCtx = createEmptyContext(); // This copies the behaviour of ThreadLocalSecurityContextHolder. contextHolder.set(newCtx); return newCtx; }); } return context; } @NonNull private Optional<SecurityContext> getFromVaadinSession() { // Don't store this security context in the ThreadLocal as that may lead to the context leaking // into other sessions as threads may be reused. var session = VaadinSession.getCurrent(); if (session == null) { return Optional.empty(); } var securityContext = session.getSession().getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); if (securityContext instanceof SecurityContext) { return Optional.of((SecurityContext) securityContext); } else { return Optional.empty(); } } @Override public void setContext(@NonNull SecurityContext securityContext) { contextHolder.set(requireNonNull(securityContext)); } @Override @NonNull public SecurityContext createEmptyContext() { return new SecurityContextImpl(); } }
补充截图(更新1)

解决方案
1. 修复客户端缓存不兼容问题
在application.properties中开启Vaadin资源缓存破坏机制,确保重部署后客户端加载新资源:
vaadin.frontend.cache.buster=true
部署时同步清理反向代理/CDN缓存,用户端需强制刷新浏览器(Ctrl+F5)。
2. 完善Spring Security资源放行规则
当前配置未放行Vaadin核心静态资源,导致加载失败。修改WebSecurity配置:
@Override public void configure(WebSecurity web) throws Exception { super.configure(web); web.ignoring().antMatchers( "/VAADIN/**", "/frontend/**", "/icons/**", "/session-expired", "/images/*" ); }
3. 修正SecurityContext获取逻辑
自定义的VaadinAwareSecurityContextHolderStrategy在资源加载阶段(VaadinSession未创建时)可能引发上下文异常,调整getFromVaadinSession()方法:
@NonNull private Optional<SecurityContext> getFromVaadinSession() { var session = VaadinSession.getCurrent(); if (session == null) { return Optional.empty(); } var httpSession = session.getSession(); if (httpSession == null) { return Optional.empty(); } var securityContext = httpSession.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); if (securityContext instanceof SecurityContext) { return Optional.of((SecurityContext) securityContext); } else { return Optional.empty(); } }
4. 清理重部署残留会话
在服务器配置中关闭会话持久化(如Tomcat设置sessionPersist=false),避免旧会话与新部署应用不匹配;或在应用启动时添加旧会话清理逻辑。
5. 升级Vaadin版本
Vaadin 23.2.5存在已知部署相关bug,建议升级至23.x最新稳定版(如23.3.24),官方已修复此类兼容问题。
内容的提问来源于stack exchange,提问作者alexanoid
相关产品推荐
相关产品推荐

