0


Spring WebSocket 认证与授权:掌控安全通道,迈向巅峰之旅!

一、需要了解的事项

  • http和WebSocket的安全链和安全配置是完全独立的。
  • SpringAuthenticationProvider根本不参与 Websocket 身份验证。
  • 将要给出的示例中,身份验证不会发生在 HTTP 协商端点上,因为 JavaScript STOMP(websocket)库不会随 HTTP 请求一起发送必要的身份验证标头。
  • 一旦在 CONNECT 请求上设置,用户( simpUser) 将被存储在 websocket 会话中,并且以后的消息将不再需要进行身份验证。

二、依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-messaging</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-messaging</artifactId></dependency>

三、WebSocket 配置

3.1 、简单的消息代理

@Configuration@EnableWebSocketMessageBrokerpublicclassWebSocketConfigextendsWebSocketMessageBrokerConfigurer{@OverridepublicvoidconfigureMessageBroker(finalMessageBrokerRegistry config){
        config.enableSimpleBroker("/queue/topic");
        config.setApplicationDestinationPrefixes("/app");}@OverridepublicvoidregisterStompEndpoints(finalStompEndpointRegistry registry){
        registry.addEndpoint("stomp");setAllowedOrigins("*")}}

3.2 、Spring安全配置

由于 Stomp 协议依赖于第一个 HTTP 请求,因此需要授权对 stomp 握手端点的 HTTP 调用。

@ConfigurationpublicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{@Overrideprotectedvoidconfigure(finalHttpSecurity http)throwsException
        http.httpBasic().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/stomp").permitAll().anyRequest().denyAll();}}

然后创建一个负责验证用户身份的服务。

@ComponentpublicclassWebSocketAuthenticatorService{publicUsernamePasswordAuthenticationTokengetAuthenticatedOrFail(finalString  username,finalString password)throwsAuthenticationException{if(username ==null|| username.trim().isEmpty()){thrownewAuthenticationCredentialsNotFoundException("Username was null or empty.");}if(password ==null|| password.trim().isEmpty()){thrownewAuthenticationCredentialsNotFoundException("Password was null or empty.");}if(fetchUserFromDb(username, password)==null){thrownewBadCredentialsException("Bad credentials for user "+ username);}returnnewUsernamePasswordAuthenticationToken(
                username,null,Collections.singleton((GrantedAuthority)()->"USER")// 必须给至少一个角色);}}

接着需要创建一个拦截器,它将设置“simpUser”标头或在 CONNECT 消息上抛出“AuthenticationException”。

@ComponentpublicclassAuthChannelInterceptorAdapterextendsChannelInterceptor{privatestaticfinalStringUSERNAME_HEADER="login";privatestaticfinalStringPASSWORD_HEADER="passcode";privatefinalWebSocketAuthenticatorService webSocketAuthenticatorService;@InjectpublicAuthChannelInterceptorAdapter(finalWebSocketAuthenticatorService webSocketAuthenticatorService){this.webSocketAuthenticatorService = webSocketAuthenticatorService;}@OverridepublicMessage<?>preSend(finalMessage<?> message,finalMessageChannel channel)throwsAuthenticationException{finalStompHeaderAccessor accessor =MessageHeaderAccessor.getAccessor(message,StompHeaderAccessor.class);if(StompCommand.CONNECT== accessor.getCommand()){finalString username = accessor.getFirstNativeHeader(USERNAME_HEADER);finalString password = accessor.getFirstNativeHeader(PASSWORD_HEADER);finalUsernamePasswordAuthenticationToken user = webSocketAuthenticatorService.getAuthenticatedOrFail(username, password);

            accessor.setUser(user);}return message;}}

请注意:preSend() 必须返回 UsernamePasswordAuthenticationToken,Spring 安全链中会对此进行测试。如果UsernamePasswordAuthenticationToken构建没有通过GrantedAuthority,则身份验证将失败,因为没有授予权限的构造函数自动设置authenticated = false 这是一个重要的细节,在 spring-security 中没有记录。

最后再创建两个类来分别处理授权和身份验证。

@Configuration@Order(Ordered.HIGHEST_PRECEDENCE+99)publicclassWebSocketAuthenticationSecurityConfigextendsWebSocketMessageBrokerConfigurer{@InjectprivateAuthChannelInterceptorAdapter authChannelInterceptorAdapter;@OverridepublicvoidregisterStompEndpoints(finalStompEndpointRegistry registry){// 这里不用给任何东西}@OverridepublicvoidconfigureClientInboundChannel(finalChannelRegistration registration){
        registration.setInterceptors(authChannelInterceptorAdapter);}}

请注意:这@Order是至关重要的,它允许我们的拦截器首先在安全链中注册。

@ConfigurationpublicclassWebSocketAuthorizationSecurityConfigextendsAbstractSecurityWebSocketMessageBrokerConfigurer{@OverrideprotectedvoidconfigureInbound(finalMessageSecurityMetadataSourceRegistry messages){// 添加自己的映射
        messages.anyMessage().authenticated();}// 这里请自己按需求修改@OverrideprotectedbooleansameOriginDisabled(){returntrue;}}

之后编写客户端进行连接,我们就可以这样指定客户端进行消息的发送。

@MessageMapping("/greeting")publicvoidgreetingReturn(@PayloadObject ojd){
         simpMessagingTemplate.convertAndSendToUser(username,"/topic/greeting",ojd);}
标签: spring websocket 安全

本文转载自: https://blog.csdn.net/weixin_55756734/article/details/133136190
版权归原作者 尘缘.. 所有, 如有侵权,请联系我们删除。

“Spring WebSocket 认证与授权:掌控安全通道,迈向巅峰之旅!”的评论:

还没有评论