Browse Source

Use consistent Authorization header format for WebSocket API key authentication

pull/15170/head
Andrii Landiak 7 months ago
parent
commit
e706b2e82f
  1. 19
      application/src/main/java/org/thingsboard/server/config/ApiKeyHandshakeInterceptor.java
  2. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
  3. 4
      application/src/test/java/org/thingsboard/server/controller/ApiKeyWebSocketApiTest.java

19
application/src/main/java/org/thingsboard/server/config/ApiKeyHandshakeInterceptor.java

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
@ -35,14 +36,13 @@ import java.util.Map;
@RequiredArgsConstructor
public class ApiKeyHandshakeInterceptor implements HandshakeInterceptor {
public static final String API_KEY_HEADER = "X-API-Key";
public static final String API_KEY_SECURITY_CTX_ATTR = "apiKeySecurityCtx";
private final ApiKeyAuthenticationProvider apiKeyAuthenticationProvider;
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
String apiKey = request.getHeaders().getFirst(API_KEY_HEADER);
String apiKey = extractApiKey(request);
if (apiKey != null) {
if (apiKey.isEmpty()) {
log.debug("Empty API key provided during WS handshake");
@ -52,8 +52,8 @@ public class ApiKeyHandshakeInterceptor implements HandshakeInterceptor {
try {
SecurityUser securityUser = apiKeyAuthenticationProvider.authenticate(apiKey);
attributes.put(API_KEY_SECURITY_CTX_ATTR, securityUser);
} catch (Exception e) {
log.debug("API key authentication failed during WS handshake: {}", e.getMessage());
} catch (AuthenticationException e) {
log.warn("API key authentication failed during WS handshake: {}", e.getMessage());
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return false;
}
@ -61,6 +61,17 @@ public class ApiKeyHandshakeInterceptor implements HandshakeInterceptor {
return true;
}
private String extractApiKey(ServerHttpRequest request) {
String header = request.getHeaders().getFirst(ThingsboardSecurityConfiguration.AUTHORIZATION_HEADER);
if (header == null) {
header = request.getHeaders().getFirst(ThingsboardSecurityConfiguration.AUTHORIZATION_HEADER_V2);
}
if (header != null && header.startsWith(ThingsboardSecurityConfiguration.API_KEY_HEADER_PREFIX)) {
return header.substring(ThingsboardSecurityConfiguration.API_KEY_HEADER_PREFIX.length());
}
return null;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
// no-op

2
application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java

@ -122,7 +122,7 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest {
}
protected TbTestWebSocketClient buildAndConnectWebSocketClientWithApiKeyHeader(String apiKey) throws URISyntaxException, InterruptedException {
TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws"), Map.of("X-API-Key", apiKey));
TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws"), Map.of("X-Authorization", "ApiKey " + apiKey));
assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue();
return wsClient;
}

4
application/src/test/java/org/thingsboard/server/controller/ApiKeyWebSocketApiTest.java

@ -86,7 +86,7 @@ public class ApiKeyWebSocketApiTest extends WebSocketApiTest {
@Test
public void testInvalidApiKeyHeader_connectionRejected() throws Exception {
TbTestWebSocketClient client = new TbTestWebSocketClient(
new URI(WS_URL + wsPort + "/api/ws"), Map.of("X-API-Key", "invalid-key"));
new URI(WS_URL + wsPort + "/api/ws"), Map.of("X-Authorization", "ApiKey invalid-key"));
try {
boolean connected = client.connectBlocking(TIMEOUT, TimeUnit.SECONDS);
assertThat(connected).isFalse();
@ -98,7 +98,7 @@ public class ApiKeyWebSocketApiTest extends WebSocketApiTest {
@Test
public void testEmptyApiKeyHeader_connectionRejected() throws Exception {
TbTestWebSocketClient client = new TbTestWebSocketClient(
new URI(WS_URL + wsPort + "/api/ws"), Map.of("X-API-Key", ""));
new URI(WS_URL + wsPort + "/api/ws"), Map.of("X-Authorization", "ApiKey "));
try {
boolean connected = client.connectBlocking(TIMEOUT, TimeUnit.SECONDS);
assertThat(connected).isFalse();

Loading…
Cancel
Save