接着Spring 5.2.2 WebSockets之STOMP的(简单和外部)代理、身份验证继续讲STOMP。
java
1三、令牌身份验证web
Spring Security OAuth支持基于令牌的安全性,包括JSON Web Token (JWT)。你能够在Web应用程序中使用此机制做为身份验证机制,包括STOMP over WebSocket交互,如前面所述(即,经过基于cookie的会话维护身份)。spring
同时,基于cookie的会话并不老是最合适的(例如,在不维护服务器端会话的应用程序中,或者在一般使用头进行身份验证的移动应用程序中)。浏览器
WebSocket协议RFC 6455“没有规定在WebSocket握手期间服务端能够对客户端进行身份验证的任何特定方式”。可是,实际上,浏览器客户端只能使用标准的身份验证头(即基本的HTTP身份验证)或Cookie,而且不能(例如)提供自定义的头。一样,SockJS JavaScript客户端也不提供发送带有SockJS传输请求的HTTP头的方法。相反,它确实容许发送能够用来发送令牌的查询参数,但这有其自身的缺点(例如,令牌可能无心中与服务端日志中的URL一块儿记录)。安全
上述限制适用于基于浏览器的客户端,不适用于基于Spring java的STOMP客户端,后者支持同时发送WebSocket和SockJS请求的头。服务器
所以,避免使用cookies的应用程序在HTTP协议级别可能没有任何好的认证替代方案。与其使用cookies,可能更喜欢在STOMP消息传递协议级别使用header进行身份验证,这样作须要两个简单的步骤:微信
使用STOMP客户端在链接时传递身份验证头。websocket
使用ChannelInterceptor处理身份验证头。cookie
下一个示例使用服务端配置注册自定义身份验证侦听器。注意,拦截器只须要对CONNECT Message进行身份验证和设置user头。Spring记录并保存通过身份验证的用户,并与同一会话中的后续STOMP消息相关联。如下示例显示如何注册自定义身份验证侦听器:app
public class MyConfig implements WebSocketMessageBrokerConfigurer {
public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(new ChannelInterceptor() { public Message<?> preSend(Message<?> message, MessageChannel channel) { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (StompCommand.CONNECT.equals(accessor.getCommand())) { Authentication user = ... ; //访问身份验证头 accessor.setUser(user); } return message; } }); }}
另外,请注意,当您使用Spring Security的消息受权时,目前,你须要确保身份验证ChannelInterceptor
配置的顺序比Spring Security的提早。最好是在标记为@Order的WebSocketMessageBrokerConfigurer
的实现中声明自定义拦截器@Order(Ordered.HIGHEST_PRECEDENCE + 99)。
1四、用户消息发送目的地
应用程序能够发送针对特定用户的消息,Spring的STOMP支持为此识别前缀为/user//的目的地。例如,客户端可能订阅/user/queue/position-updates目的地。这个目的地由UserDestinationMessageHandler处理,并转换为用户会话特有的目的地(例如/queue/position-updates-user123)。这提供了订阅一个通用命名目的地的便利,同时,确保不会与订阅同一目的地的其余用户发生冲突,以便每一个用户均可以接收到惟一的存储位置更新。
在发送端,能够将消息发送到/user/{username}/queue/position-updates等目的地,而后由UserDestinationMessageHandler将其转换为一个或多个目的地,每一个目的地对应于与用户相关联的每一个会话。这使得应用程序中的任何组件均可以发送针对特定用户的消息,而没必要知道他们的名称和通用目的地。这也经过注解和消息传递模板来支持。
消息处理方法能够经过@SendToUser注解向与正在处理的消息相关联的用户发送消息(在类级别上也支持共享一个公共目标),以下例所示:
public class PortfolioController {
public TradeResult executeTrade(Trade trade, Principal principal) { // ... return tradeResult; }}
若是用户有多个会话,默认状况下,全部订阅到给定目标的会话都是目标。可是,有时可能须要只针对发送正在处理的消息的会话。能够经过将broadcast
属性设置为false来执行此操做,以下例所示:
public class MyController {
"/action") ( public void handleAction() throws Exception{ // 在此处引起MyBusinessException }
"/queue/errors", broadcast=false) (destinations= public ApplicationError handleException(MyBusinessException exception) { // ... return appError; }}
虽然用户目的地一般意味着一个通过身份验证的用户,但并非严格要求的。未与已验证用户关联的WebSocket会话能够订阅用户目标。在这种状况下,@SendToUser注解的行为与broadcast=false彻底相同(即,只针对发送正在处理的消息的会话)。
你能够从任何应用程序组件向用户目的地发送消息,例如,经过注入由Java配置或XML命名空间建立的SimpMessagingTemplate
。(若是须要使用@Qualifier进行限定,则bean名称为“brokerMessagingTemplate”。)下面的示例演示了如何这样作:
public class TradeServiceImpl implements TradeService {
private final SimpMessagingTemplate messagingTemplate;
public TradeServiceImpl(SimpMessagingTemplate messagingTemplate) { this.messagingTemplate = messagingTemplate; }
// ...
public void afterTradeExecuted(Trade trade) { this.messagingTemplate.convertAndSendToUser( trade.getUserName(), "/queue/position-updates", trade.getResult()); }}
当你将用户目的地与外部Message Broker一块儿使用时,你应该检查有关如何管理非活动队列的代理文档,以便在用户会话结束时,删除全部惟一的用户队列。例如,RabbitMQ在使用/exchange等目标时建立/exchange/amq.direct/position-updates。所以,在这种状况下,客户端能够订阅/exchange/amq.direct/position-updates。相似地,ActiveMQ具备用于清除非活动目标的配置选项。
在多应用程序服务端方案中,因为用户链接到不一样的服务端,用户目标可能保持未解析状态。在这种状况下,你能够将目的地配置为广播未解析的消息,以便其余服务器有机会尝试。这能够经过Java配置中MessageBrokerRegistry
的userDestinationBroadcast
属性和XML中message-broker元素的user-destination-broadcast
属性来实现。
1五、消息的顺序
来自代理的消息被发布到clientOutboundChannel,在那里它们被写入WebSocket会话。因为通道由ThreadPoolExecutor支持,消息在不一样的线程中处理,客户端接收到的结果序列可能与发布的确切顺序不匹配。
若是这是一个问题,请启用setPreservePublishOrder
标志,以下例所示:
public class MyConfig implements WebSocketMessageBrokerConfigurer {
protected void configureMessageBroker(MessageBrokerRegistry registry) { // ... registry.setPreservePublishOrder(true); }
}
如下示例显示了与前一个示例等效的XML配置:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:websocket="http://www.springframework.org/schema/websocket" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/websocket https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker preserve-publish-order="true"> <!-- ... --> </websocket:message-broker>
</beans>
设置该标志后,同一客户机会话中的消息一次发布到clientOutboundChannel
,以便保证发布顺序。请注意,这会产生一个小的性能开销,所以你应该只在须要时才启用它。
STOMP未完待续!
敬请持续关注。
欢迎关注和转发Spring中文社区(加微信群,能够关注后加我微信):
本文分享自微信公众号 - Spring中文社区(gh_81d233bb13a4)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。