0

I want to implement token based authentication for web socket connection.

My backend system is server and a machine is client, how to implement authentication during sending and receiving message between machine and server, no user authentication here.

@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;
        }
    });
  }
}

However, I'm totally lost how I would do at "Principal user = ... ;". How would I get Principle with the token? As here no user exist, only communication between a machine and a backend server

Amit
  • 1,540
  • 1
  • 15
  • 28
  • Are you asking how to authenticate a client with a server without user interaction and create a token to use on the client? Or do you already have a token and you're asking how to get a principal from the token? – CamW Aug 31 '17 at 06:36
  • how to authenticate a client with a server without user interaction and create a token to use on the client? – Amit Aug 31 '17 at 06:38
  • How does your application get on to a client device? Does a user download and install it? Can you provide more info about the nature of the client app? – CamW Aug 31 '17 at 06:44
  • Client subscribe the message in a topic and sever fetch the message from the subscription url, as client will connect with server... – Amit Aug 31 '17 at 06:46

3 Answers3

0

Guess you can try to get principal from spring security context

SecurityContextHolder.getContext().getAuthentication().getPrincipal();
StanislavL
  • 56,971
  • 9
  • 68
  • 98
  • But without login, may I get the Principle? as here only communicate server and client, no user involved here.. – Amit Aug 31 '17 at 06:10
  • There is token based auth. http://websystique.com/spring-security/secure-spring-rest-api-using-oauth2/ so instead of login/password you include a token in http header. Google for oauth http-basic examples. Not sure how to add this to web sockets properly. – StanislavL Aug 31 '17 at 06:13
0

Sounds like you might need a solution similar to something that would be used by someone building an IoT solution. A good starting point would be to have a look at OpenID Connect.

There's a good article here discussing the related challenges and solutions: https://www.gluu.org/blog/oauth2-for-iot/

OpenId Connect's site: http://openid.net/connect/

CamW
  • 3,223
  • 24
  • 34
0

I know this issue has been around for a long time, and there are already plenty of answers available on Stack Overflow, such as this and this. These answers indeed fix this issue but all are trying to have their own implementations of the Principle interface which is not needed as Spring Security has already implemented all we need.

Answer your question: The principle needs to be created from the token provided in the request. You can easily convert string token to a Principle by using new BearerTokenAuthenticationToken("token-string-without-bearer"), then after that, you should authenticate the token by using authenticationManager.authenticate(bearerTokenAuthenticationToken). Therefore, the code is like this:

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

The full implementation of the configureClientInboundChannel method should be (sorry I only have Kotlin code, but you should be able to get the idea):

    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
            }
        })
    }

Note that BearerTokenAuthenticationToken comes from org.springframework.boot:spring-boot-starter-oauth2-resource-server, so you need this dependency in order to use it.

authenticationManager comes from the implementation of WebSecurityConfigurerAdapter, exposing as a Bean by overriding authenticationManagerBean method and mark it as @Bean, and then injecting the Bean into your AbstractWebSocketMessageBrokerConfigurer implementation, which in this case, is MyConfig.

Also, don't forget to mark MyConfig class with @Order(Ordered.HIGHEST_PRECEDENCE + 99) so that it sets up the user header before Spring Security takeover.

Yihao Gao
  • 535
  • 7
  • 11