Skip to content

Commit

Permalink
WebSocketAuthenticatorService & AuthChannelInterceptorAdapter tests
Browse files Browse the repository at this point in the history
  • Loading branch information
anthonyraymond committed Apr 9, 2018
1 parent 12aa47c commit f910619
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.springframework.stereotype.Component;

import javax.inject.Inject;
import java.io.IOException;

/**
* Created by raymo on 08/07/2017.
Expand Down
1 change: 0 additions & 1 deletion src/main/java/org/araymond/joal/web/config/BeanConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import java.io.IOException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.google.common.annotations.VisibleForTesting;
import org.araymond.joal.web.annotations.ConditionalOnWebUi;
import org.araymond.joal.web.config.security.websocket.interceptor.AuthChannelInterceptorAdapter;
import org.araymond.joal.web.config.security.websocket.services.WebSocketAuthenticatorService;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
Expand All @@ -21,11 +20,11 @@
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebSocketAuthenticationSecurityConfig extends AbstractWebSocketMessageBrokerConfigurer {
private final WebSocketAuthenticatorService webSocketAuthenticatorService;
private final AuthChannelInterceptorAdapter authChannelInterceptorAdapter;

@Inject
public WebSocketAuthenticationSecurityConfig(final WebSocketAuthenticatorService webSocketAuthenticatorService) {
this.webSocketAuthenticatorService = webSocketAuthenticatorService;
public WebSocketAuthenticationSecurityConfig(final AuthChannelInterceptorAdapter authChannelInterceptorAdapter) {
this.authChannelInterceptorAdapter = authChannelInterceptorAdapter;
}

@Override
Expand All @@ -40,7 +39,7 @@ public void configureClientInboundChannel(final ChannelRegistration registration

@VisibleForTesting
ChannelInterceptor[] createChannelInterceptors() {
return new ChannelInterceptor[]{new AuthChannelInterceptorAdapter(this.webSocketAuthenticatorService)};
return new ChannelInterceptor[]{this.authChannelInterceptorAdapter};
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.araymond.joal.web.config.security.websocket.interceptor;

import org.araymond.joal.web.annotations.ConditionalOnWebUi;
import org.araymond.joal.web.config.security.websocket.services.WebSocketAuthenticatorService;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
Expand All @@ -9,16 +10,22 @@
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import javax.inject.Inject;

/**
* Created by raymo on 30/07/2017.
*/
@ConditionalOnWebUi
@Component
public class AuthChannelInterceptorAdapter extends ChannelInterceptorAdapter {
private static final String USERNAME_HEADER = "X-Joal-Username";
private static final String TOKEN_HEADER = "X-Joal-Auth-Token";
static final String USERNAME_HEADER = "X-Joal-Username";
static final String TOKEN_HEADER = "X-Joal-Auth-Token";

private final WebSocketAuthenticatorService webSocketAuthenticatorService;

@Inject
public AuthChannelInterceptorAdapter(final WebSocketAuthenticatorService webSocketAuthenticatorService) {
this.webSocketAuthenticatorService = webSocketAuthenticatorService;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public UsernamePasswordAuthenticationToken getAuthenticatedOrFail(final CharSequ
if (StringUtils.isBlank(authToken)) {
throw new AuthenticationCredentialsNotFoundException("Authentication token was null or empty.");
}
if (!appSecretToken.equals(authToken)) {
if (!appSecretToken.contentEquals(authToken)) {
throw new BadCredentialsException("Authentication token does not match the expected token");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.araymond.joal.web.config.security;

import org.araymond.joal.web.config.security.websocket.interceptor.AuthChannelInterceptorAdapter;
import org.araymond.joal.web.config.security.websocket.services.WebSocketAuthenticatorService;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -16,8 +17,8 @@ public class WebSocketAuthenticationSecurityConfigTest {

@Test
public void shouldRegisterInterceptors() {
final WebSocketAuthenticatorService authService = mock(WebSocketAuthenticatorService.class);
final WebSocketAuthenticationSecurityConfig webSocketAuthenticationSecurityConfig = spy(new WebSocketAuthenticationSecurityConfig(authService));
final AuthChannelInterceptorAdapter authAdaptor = mock(AuthChannelInterceptorAdapter.class);
final WebSocketAuthenticationSecurityConfig webSocketAuthenticationSecurityConfig = spy(new WebSocketAuthenticationSecurityConfig(authAdaptor));

final ChannelRegistration registration = mock(ChannelRegistration.class);
webSocketAuthenticationSecurityConfig.configureClientInboundChannel(registration);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.araymond.joal.web.config.security.websocket.interceptor;

import org.araymond.joal.TestConstant;
import org.araymond.joal.web.config.WebSocketConfig;
import org.araymond.joal.web.config.security.WebSecurityConfig;
import org.araymond.joal.web.config.security.websocket.services.WebSocketAuthenticatorService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.socket.WebSocketHttpHeaders;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.messaging.WebSocketStompClient;

import javax.inject.Inject;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;


@RunWith(SpringRunner.class)
@SpringBootTest(
classes = {
AuthChannelInterceptorAdapter.class,
WebSocketConfig.class,
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration.class,
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration.class,
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.class,
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration.class,
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration.class,
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration.class,
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration.class,
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration.class,
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration.class,
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.class,
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration.class,
},
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {
"spring.main.web-environment=true",
"joal.ui.path.prefix=" + TestConstant.UI_PATH_PREFIX,
"joal.ui.secret-token=" + TestConstant.UI_SECRET_TOKEN
}
)
@Import(AuthChannelInterceptorAdapterWebAppTest.TestWebSocketAuthenticationConfig.class)
public class AuthChannelInterceptorAdapterWebAppTest {

@LocalServerPort
private int port;

@MockBean
private WebSocketAuthenticatorService authenticatorService;

@TestConfiguration
public static class TestWebSocketAuthenticationConfig extends AbstractWebSocketMessageBrokerConfigurer {
private final AuthChannelInterceptorAdapter authChannelInterceptorAdapter;
@Inject
public TestWebSocketAuthenticationConfig(final AuthChannelInterceptorAdapter authChannelInterceptorAdapter) {
this.authChannelInterceptorAdapter = authChannelInterceptorAdapter;
}
@Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
// Endpoints are already registered on WebSocketConfig, no need to add more.
}
@Override
public void configureClientInboundChannel(final ChannelRegistration registration) {
registration.interceptors(this.authChannelInterceptorAdapter);
}
}

@Test
public void shouldCallAuthServiceWhenUserTriesToConnect() throws InterruptedException, ExecutionException, TimeoutException {
final WebSocketStompClient stompClient = new WebSocketStompClient(new StandardWebSocketClient());

final StompHeaders stompHeaders = new StompHeaders();
stompHeaders.add(AuthChannelInterceptorAdapter.USERNAME_HEADER, "john");
stompHeaders.add(AuthChannelInterceptorAdapter.TOKEN_HEADER, TestConstant.UI_SECRET_TOKEN);

stompClient.connect("ws://localhost:" + port + "/" + TestConstant.UI_PATH_PREFIX, new WebSocketHttpHeaders(), stompHeaders, new StompSessionHandlerAdapter() {
}).get(10, TimeUnit.SECONDS);

verify(authenticatorService, times(1)).getAuthenticatedOrFail("john", TestConstant.UI_SECRET_TOKEN);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.araymond.joal.web.config.security.websocket.services;

import org.araymond.joal.TestConstant;
import org.assertj.core.api.Condition;
import org.junit.Test;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class WebSocketAuthenticatorServiceTest {

@Test
public void shouldThrowExceptionOnNullOrEmptyUsername() {
final WebSocketAuthenticatorService authService = new WebSocketAuthenticatorService(TestConstant.UI_SECRET_TOKEN);
assertThatThrownBy(() -> authService.getAuthenticatedOrFail(" ", TestConstant.UI_SECRET_TOKEN))
.isInstanceOf(AuthenticationCredentialsNotFoundException.class)
.hasMessageContaining("Username");

assertThatThrownBy(() -> authService.getAuthenticatedOrFail("", TestConstant.UI_SECRET_TOKEN))
.isInstanceOf(AuthenticationCredentialsNotFoundException.class)
.hasMessageContaining("Username");

assertThatThrownBy(() -> authService.getAuthenticatedOrFail(null, TestConstant.UI_SECRET_TOKEN))
.isInstanceOf(AuthenticationCredentialsNotFoundException.class)
.hasMessageContaining("Username");
}

@Test
public void shouldThrowExceptionOnNullOrEmptyToken() {
final WebSocketAuthenticatorService authService = new WebSocketAuthenticatorService(TestConstant.UI_SECRET_TOKEN);
assertThatThrownBy(() -> authService.getAuthenticatedOrFail("john", " "))
.isInstanceOf(AuthenticationCredentialsNotFoundException.class)
.hasMessageContaining("Authentication token");

assertThatThrownBy(() -> authService.getAuthenticatedOrFail("john", ""))
.isInstanceOf(AuthenticationCredentialsNotFoundException.class)
.hasMessageContaining("Authentication token");

assertThatThrownBy(() -> authService.getAuthenticatedOrFail("john", null))
.isInstanceOf(AuthenticationCredentialsNotFoundException.class)
.hasMessageContaining("Authentication token");
}

@Test
public void shouldThrowExceptionIfTokenDoesNotMatches() {
final WebSocketAuthenticatorService authService = new WebSocketAuthenticatorService(TestConstant.UI_SECRET_TOKEN);
assertThatThrownBy(() -> authService.getAuthenticatedOrFail("john", "nop"))
.isInstanceOf(BadCredentialsException.class)
.hasMessageContaining("Authentication token does not match");
}

@Test
public void shouldReturnAuthenticationTokenOnSuccess() {
final WebSocketAuthenticatorService authService = new WebSocketAuthenticatorService(TestConstant.UI_SECRET_TOKEN);

final UsernamePasswordAuthenticationToken authToken = authService.getAuthenticatedOrFail("john", TestConstant.UI_SECRET_TOKEN);

assertThat(authToken.getName()).isEqualTo("john");
}

@Test
public void shouldReturnInstanceOfUsernamePasswordAuthenticationTokenOnSuccess() {
// This is not a useless test, Spring security chain test if the instance of the returned AuthToken is UsernamePasswordAuthenticationToken
final WebSocketAuthenticatorService authService = new WebSocketAuthenticatorService(TestConstant.UI_SECRET_TOKEN);

final UsernamePasswordAuthenticationToken authToken = authService.getAuthenticatedOrFail("john", TestConstant.UI_SECRET_TOKEN);

assertThat(authToken).isInstanceOf(UsernamePasswordAuthenticationToken.class);
}

@Test
public void shouldDefineAtLeastOneGrantedAuthorityOnSuccess() {
// This is not a useless test, Spring security chain test if there is at least one granted authority, if there is none, we are considered as non authenticated
final WebSocketAuthenticatorService authService = new WebSocketAuthenticatorService(TestConstant.UI_SECRET_TOKEN);

final UsernamePasswordAuthenticationToken authToken = authService.getAuthenticatedOrFail("john", TestConstant.UI_SECRET_TOKEN);

assertThat(authToken.getAuthorities().size()).isGreaterThanOrEqualTo(1);
}

}

0 comments on commit f910619

Please sign in to comment.