接着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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。