预览 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 实例 中。或者,我们可以将 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 社区中所有即将举行的活动。

查看全部