领先一步
VMware 提供培训和认证,助您加速进步。
了解更多在我之前的博文中,我讨论了 Spring Security WebSocket 集成。其中一个问题是,在 Servlet 容器中,WebSocket 请求不会使 HttpSession 保持活动状态。
考虑一个主要通过 HTTP 请求完成大部分工作的电子邮件应用程序。然而,其中还嵌入了一个通过 WebSocket API 工作的聊天应用程序。如果用户正在与某人积极聊天,我们不应该让 HttpSession 超时,因为这将带来非常糟糕的用户体验。然而,这正是 JSR-356 所做的。
另一个问题是,根据 JSR-356,如果 HttpSession 超时,任何使用该 HttpSession 和已认证用户创建的 WebSocket 都将被强制关闭。这意味着,如果我们正在应用程序中积极聊天但未使用 HttpSession,那么我们也将断开与对话的连接!
Spring Security 团队最初着手在 Spring Security 4.0.0.M2 中解决这些问题。然而,我们意识到这是一个更广泛的问题,因此 Spring Session 应运而生。
第一步是配置我们的 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 集成,但已计划在 下个版本中支持。然而,我们可以轻松地自己实现。
第一步是确保我们在 WebSocket Session 中能够访问会话 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 分支中找到关于授权和会话管理的完整示例。