Spring Security WebSocket 支持和会话预览

工程 | Rob Winch | 2014 年 9 月 16 日 | ...

简介

在我之前的文章中,我讨论了 Spring Security WebSocket 集成。其中一个问题是,在 servlet 容器中,WebSocket 请求不会保持 HttpSession 处于活动状态。

考虑一个主要通过 HTTP 请求完成工作的电子邮件应用程序。但是,其中还嵌入了一个通过 WebSocket API 工作的聊天应用程序。如果用户正在积极地与某人聊天,我们不应使 HttpSession 超时,因为这会带来非常糟糕的用户体验。然而,这正是 JSR-356 的作用

另一个问题是,根据 JSR-356,如果 HttpSession 超时,则使用该 HttpSession 创建的任何 WebSocket 以及经过身份验证的用户都应被强制关闭。这意味着如果我们在应用程序中积极聊天并且未使用 HttpSession,那么我们也将断开与对话的连接!

Spring Session

Spring Security 团队最初计划在 Spring Security 4.0.0.M2 中解决这些问题。但是,我们意识到这是一个更广泛的问题,因此 Spring Session 诞生了。

Spring Session HTTP 集成

第一步是在我们的 Web 应用程序中配置 Spring Session。这意味着我们的 HttpSession 现在由 Spring Session 支持,而不是由我们的容器支持。

在下面的示例中,我们将 Spring Session 添加到 Spring Security 的 HttpSecurity instance。或者,我们可以将 SessionRepositoryFilter 直接添加到 Servlet 容器的过滤器映射中,然后再添加 springSecurityFilterChain。您可以在 Spring Session 参考中找到更详细的步骤。

protected void configure(HttpSecurity http) throws Exception {
    http
        .addFilterBefore(new SessionRepositoryFilter(sessionRepository), ChannelProcessingFilter.class)
        ...
}

Spring Session WebSocket 集成

Spring Session 尚未支持 WebSocket 集成,但计划在下一个版本中实现。但是,我们可以轻松地自己实现它。

第一步是确保我们可以在 WebSocket 会话中访问会话 ID。 我们可以通过创建一个 HandshakeInterceptor 来做到这一点

public class HttpSessionIdHandshakeInterceptor implements HandshakeInterceptor {

        public boolean beforeHandshake(ServerHttpRequest request, 
                ServerHttpResponse response, 
                WebSocketHandler wsHandler, 
                Map<String, Object> attributes) 
                  throws Exception {
            if (request instanceof ServletServerHttpRequest) {
                ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
                HttpSession session = servletRequest.getServletRequest().getSession(false);
                if (session != null) {
                    attributes.put(SESSION_ATTR, session.getId());
                }
            }
            return true;
        }

        public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                   WebSocketHandler wsHandler, Exception ex) {
        }
    }

然后我们可以将 HandshakeInterceptor 添加到我们的端点。 例如

public class WebSocketConfig extends 
          AbstractWebSocketMessageBrokerConfigurer {

    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio")
                .withSockJS()
                .setInterceptors(new HttpSessionIdHandshakeInterceptor());

    }

   ...
}

接下来,我们可以创建一个 ChannelInterceptorAdapter,它使用会话 ID 来使用 Spring Session 更新上次访问时间。 例如

@Bean
public ChannelInterceptorAdapter sessionContextChannelInterceptorAdapter() {
    return new ChannelInterceptorAdapter() {
        @Override
        public Message<?> preSend(Message<?> message, MessageChannel channel) {
            Map<String, Object> sessionHeaders = SimpMessageHeaderAccessor.getSessionAttributes(message.getHeaders());
            String sessionId = (String) sessionHeaders.get(SESSION_ATTR);
            if (sessionId != null) {
                Session session = sessionRepository.getSession(sessionId);
                if (session != null) {

                    sessionRepository.save(session);
                }
            }
            return super.preSend(message, channel);
        }
    };
}

最后,我们需要配置传入消息以使用 ChannelInterceptorAdapter,以便每次收到消息时,我们都会更新 HttpSession 的上次访问时间。 例如

public class WebSocketConfig extends 
          AbstractWebSocketMessageBrokerConfigurer {

    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.setInterceptors(sessionContextChannelInterceptorAdapter());
    } 
    ...
}

示例应用程序

您可以在 rwinch/spring-websocket-portfolio 的 security 分支中找到授权和会话管理的完整示例。

获取 Spring 新闻邮件

随时了解 Spring 新闻邮件

订阅

抢占先机

VMware 提供培训和认证来加速您的进度。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部