领先一步
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 会话中访问会话 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 分支中找到授权和会话管理的完整示例。