基于 Spring STOMP 令牌的安全性

Posted

技术标签:

【中文标题】基于 Spring STOMP 令牌的安全性【英文标题】:Spring STOMP Token Based Security 【发布时间】:2018-02-08 23:28:21 【问题描述】:

我想为 Web 套接字连接实现基于令牌的身份验证。

我的后端系统是server,机器是client,机器和服务器之间收发消息时如何实现认证,这里不做用户认证。

@Configuration
@EnableWebSocketMessageBroker
public class MyConfig extends AbstractWebSocketMessageBrokerConfigurer 

  @Override
  public void configureClientInboundChannel(ChannelRegistration registration) 
    registration.setInterceptors(new ChannelInterceptorAdapter() 

        @Override
        public Message<?> preSend(Message<?> message, MessageChannel channel) 

            StompHeaderAccessor accessor =
                MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

            if (StompCommand.CONNECT.equals(accessor.getCommand())) 
                String authToken = accessor.getFirstNativeHeader("X-Auth-Token");
                log.debug("webSocket token is ", authToken);
                Principal user = ... ; // access authentication header(s)
                accessor.setUser(user);
            

            return message;
        
    );
  

但是,我完全失去了在“主要用户 = ... ;”中的表现。我将如何使用令牌获得 Principle?因为这里没有用户,只有机器和后端服务器之间的通信

【问题讨论】:

您是否在问如何在没有用户交互的情况下通过服务器验证客户端并创建要在客户端上使用的令牌?或者您是否已经有一个令牌并且您正在询问如何从令牌中获取委托人? 如何在没有用户交互的情况下通过服务器验证客户端并创建在客户端上使用的令牌? 您的应用程序如何进入客户端设备?用户是否下载并安装它?您能否提供有关客户端应用程序性质的更多信息? 客户端订阅主题中的消息并服务器从订阅url中获取消息,因为客户端将连接服务器...... 【参考方案1】:

猜你可以尝试从 spring 安全上下文中获取委托人

SecurityContextHolder.getContext().getAuthentication().getPrincipal();

【讨论】:

但是没有登录,我可以得到原理吗?因为这里只通信服务器和客户端,这里不涉及用户.. 有基于令牌的身份验证。 websystique.com/spring-security/… 因此,您在 http 标头中包含一个令牌,而不是登录名/密码。 Google for oauth http-basic examples。不知道如何正确地将它添加到网络套接字。【参考方案2】:

听起来您可能需要一个类似于构建物联网解决方案的人所使用的解决方案。一个很好的起点是看看 OpenID Connect。

这里有一篇很好的文章讨论了相关的挑战和解决方案: https://www.gluu.org/blog/oauth2-for-iot/

OpenId Connect 的站点: http://openid.net/connect/

【讨论】:

【参考方案3】:

我知道这个问题已经存在很长时间了,并且 Stack Overflow 上已经有很多答案可用,例如 this 和 this。这些答案确实解决了这个问题,但所有人都在尝试拥有自己的 Principle 接口实现,因为 Spring Security 已经实现了我们需要的一切。

回答你的问题:原理需要从请求中提供的token创建。您可以使用new BearerTokenAuthenticationToken("token-string-without-bearer") 轻松地将字符串令牌转换为Principle,然后使用authenticationManager.authenticate(bearerTokenAuthenticationToken) 对令牌进行身份验证。因此,代码是这样的:

// access authentication header(s)
Principal user = authenticationManager.authenticate(new BearerTokenAuthenticationToken(authToken)); 

configureClientInboundChannel 方法的完整实现应该是(抱歉我只有 Kotlin 代码,但你应该能明白):

    override fun configureClientInboundChannel(registration: ChannelRegistration) 
        registration.interceptors(object : ChannelInterceptor 
            override fun preSend(message: Message<*>, channel: MessageChannel): Message<*>? 
                val accessor = MessageHeaderAccessor
                        .getAccessor(message, StompHeaderAccessor::class.java)

                if (accessor != null && accessor.command == StompCommand.CONNECT) 
                    // assume the value of Authorization header has Bearer at the beginning
                    val authorization = accessor.getFirstNativeHeader("Authorization")
                            ?.split(" ", limit = 2)
                            ?: return message

                    // check token type is Bearer
                    if (authorization.getOrNull(0) == "Bearer") 

                        val token = authorization.getOrNull(1)?.takeIf  it.isNotBlank() 
                        // if token exists, authenticate and (if succeeded) then assign user to the session
                        if (token != null) 
                            val user = try 
                                authenticationManager.authenticate(BearerTokenAuthenticationToken(token))
                             catch (ex: AuthenticationException) 
                                // if throw an exception, do not touch the user header
                                null
                            
                            if (user != null) 
                                accessor.user = user
                            
                        
                    
                
                return message
            
        )
    

注意BearerTokenAuthenticationToken来自org.springframework.boot:spring-boot-starter-oauth2-resource-server,所以你需要这个依赖才能使用它。

authenticationManager 来自WebSecurityConfigurerAdapter 的实现,通过覆盖authenticationManagerBean 方法并将其标记为@Bean,然后将Bean 注入到您的AbstractWebSocketMessageBrokerConfigurer 实现中,在这种情况下,是MyConfig

另外,不要忘记用@Order(Ordered.HIGHEST_PRECEDENCE + 99) 标记MyConfig 类,以便它在Spring Security 接管之前设置用户标头。

【讨论】:

以上是关于基于 Spring STOMP 令牌的安全性的主要内容,如果未能解决你的问题,请参考以下文章

将 Spring websockets (sockJS + Stomp) 与基于令牌的身份验证 (JWT) 一起使用的最佳方法

Spring(Websockets / REST / Security)、JWT 和 Sockjs(Stomp)集成

Tomcat 中的 LTPA 令牌(Spring 安全性)

使用 JWT 令牌的 Spring Boot webflux 安全性

为啥这个基于令牌的 Spring Security 过滤器没有被调用?

通过 spring websocket、sockJs 和 STOMP 向经过身份验证的用户发送通知