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.HttpStatus;
import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServerHttpResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor; import org.springframework.web.socket.server.HandshakeInterceptor;
@ -35,14 +36,13 @@ import java.util.Map;
@RequiredArgsConstructor @RequiredArgsConstructor
public class ApiKeyHandshakeInterceptor implements HandshakeInterceptor { 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"; public static final String API_KEY_SECURITY_CTX_ATTR = "apiKeySecurityCtx";
private final ApiKeyAuthenticationProvider apiKeyAuthenticationProvider; private final ApiKeyAuthenticationProvider apiKeyAuthenticationProvider;
@Override @Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) { 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 != null) {
if (apiKey.isEmpty()) { if (apiKey.isEmpty()) {
log.debug("Empty API key provided during WS handshake"); log.debug("Empty API key provided during WS handshake");
@ -52,8 +52,8 @@ public class ApiKeyHandshakeInterceptor implements HandshakeInterceptor {
try { try {
SecurityUser securityUser = apiKeyAuthenticationProvider.authenticate(apiKey); SecurityUser securityUser = apiKeyAuthenticationProvider.authenticate(apiKey);
attributes.put(API_KEY_SECURITY_CTX_ATTR, securityUser); attributes.put(API_KEY_SECURITY_CTX_ATTR, securityUser);
} catch (Exception e) { } catch (AuthenticationException e) {
log.debug("API key authentication failed during WS handshake: {}", e.getMessage()); log.warn("API key authentication failed during WS handshake: {}", e.getMessage());
response.setStatusCode(HttpStatus.UNAUTHORIZED); response.setStatusCode(HttpStatus.UNAUTHORIZED);
return false; return false;
} }
@ -61,6 +61,17 @@ public class ApiKeyHandshakeInterceptor implements HandshakeInterceptor {
return true; 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 @Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
// no-op // 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 { 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(); assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue();
return wsClient; return wsClient;
} }

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

@ -86,7 +86,7 @@ public class ApiKeyWebSocketApiTest extends WebSocketApiTest {
@Test @Test
public void testInvalidApiKeyHeader_connectionRejected() throws Exception { public void testInvalidApiKeyHeader_connectionRejected() throws Exception {
TbTestWebSocketClient client = new TbTestWebSocketClient( 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 { try {
boolean connected = client.connectBlocking(TIMEOUT, TimeUnit.SECONDS); boolean connected = client.connectBlocking(TIMEOUT, TimeUnit.SECONDS);
assertThat(connected).isFalse(); assertThat(connected).isFalse();
@ -98,7 +98,7 @@ public class ApiKeyWebSocketApiTest extends WebSocketApiTest {
@Test @Test
public void testEmptyApiKeyHeader_connectionRejected() throws Exception { public void testEmptyApiKeyHeader_connectionRejected() throws Exception {
TbTestWebSocketClient client = new TbTestWebSocketClient( 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 { try {
boolean connected = client.connectBlocking(TIMEOUT, TimeUnit.SECONDS); boolean connected = client.connectBlocking(TIMEOUT, TimeUnit.SECONDS);
assertThat(connected).isFalse(); assertThat(connected).isFalse();

Loading…
Cancel
Save