Spring WebFlux2.4.3 - WebSocketHandlerにPrincipalを受け渡す
要約
- 現時点で、WebFluxでWebSocketをハンドリングする
WebSocketHandler
で受け取っているWebSocketSession
のHandshakeInfo
が保有するprincipleMono
は、Mono.empty()
を返している。- これは
HttpWebHandlerAdaper
が、DefaultServerWebExchange
を生成しており、spring-framework/HttpWebHandlerAdapter.java at a2ef6badc4c76790128910851fdde0df55ec15f9 · spring-projects/spring-framework · GitHub、これを元にHandshakeInfo
を作成しているためである。
- これは
- つまり、そのままだと
WebSocketHandler
では、ログイン中のユーザー情報(Principal
)を受け取ることができない。 - 解決法として、デフォルトで利用される
WebSocketService
実装を、ProtocolのUpgrade処理時にSessionをPrincipalに解決できるようにする実装に置き換える。
問題
- 現時点で、WebFluxでWebSocketをハンドリングする
WebSocketHandler
で受け取っているWebSocketSession
のHandshakeInfo
が保有するprincipleMono
は、Mono.empty()
を返している。公式としてWebFlux WebSocketはサポートしていないようで、記事を書いている現在もまだissueはOpenされたままとなっている。
そのため、以下のようにWebSocketSession
でPrincipal
の情報を取得しようとしても、完全に処理が停止してしまい、ユーザー情報とFluxを結合させることができない。
public class WebSocketEndpointHandler implements WebSocketHandler { // ... @Override public @NonNull Mono<Void> handle(WebSocketSession session) { final var fluxWithPrincipal = session.getHandshakeInfo() .getPrincipal() .flatMap(principal -> session .receive() .map(webSocketMessage -> // 各メッセージとPricipleの結合 )); } // ... }
解決策
解決策としては、ReactorNettyRequestUpgradeStrategy
をベースに、オリジナルの RequestUpgradeStrategy
を実装し、これを利用するようにWebFluxConfigurer
を設定する。
ます、ReactorNettyRequestUpgradeStrategy
をオーバーライドして、以下のようにServerSecurityContextRepository
からPrincipal
を解決するようにし、新たなHandshakeInfo
を返すようにしたものを用意する。
@RequiredArgsConstructor public class ReactorNettyRequestUpgradeStrategyWithPrincipal extends ReactorNettyRequestUpgradeStrategy { @NonNull private final ServerSecurityContextRepository serverSecurityContextRepository; @Override public Mono<Void> upgrade(ServerWebExchange exchange, WebSocketHandler handler, @Nullable String subProtocol, Supplier<HandshakeInfo> handshakeInfoFactory) { return super.upgrade(exchange, handler, subProtocol, () -> { final var handShakeInfo = handshakeInfoFactory.get(); final var gamePlayerPrincipal = serverSecurityContextRepository .load(exchange) .map(SecurityContext::getAuthentication) .map(Authentication::getPrincipal) .cast(GamePlayer.class) .map(player -> (Principal) player::getPlayerId); return new HandshakeInfo( handShakeInfo.getUri(), handShakeInfo.getHeaders(), gamePlayerPrincipal, handShakeInfo.getSubProtocol(), handShakeInfo.getRemoteAddress(), handShakeInfo.getAttributes(), handShakeInfo.getLogPrefix() ); }); } }
そして、自作の ReactorNettyRequestUpgradeStrategy
を利用するように WebFluxConfigurer
を設定する。
@Configuration @EnableWebFlux public class WebFluxConfig implements WebFluxConfigurer { // ... @Autowired private ServerSecurityContextRepository serverSecurityContextRepository; @Override public WebSocketService getWebSocketService() { return new HandshakeWebSocketService( new ReactorNettyRequestUpgradeStrategyWithPrincipal( serverSecurityContextRepository)); } // ... }
これにより、WebFlux WebSocketからでもSpring Securityによって解決されたユーザー情報を利用することができるようになる。