This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new f6e5179214 IGNITE-19099 Java client: Add basic authentication support 
(#1935)
f6e5179214 is described below

commit f6e5179214485c0826bdef6b6c745e25860671fa
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Thu Apr 13 17:36:48 2023 +0300

    IGNITE-19099 Java client: Add basic authentication support (#1935)
    
    * Add another client handshake extension `authn-type` to future-proof for 
different providers.
    * Rename existing handshake extensions to be more generic and match 
`AuthenticationRequest` on server.
    * Add `AnonymousRequest` to handle the case when client does not provide in 
handshake.
    * Add `IgniteClientAuthenticator` interface on the client side, with a 
single `BasicAuthenticator` implementation.
    * Update `IgniteClientConfiguration` and pass credentials in the handshake.
    * Move `AuthenticationException` from internal package to public in 
`ignite-api` module, add missing constructor.
---
 .../ignite/security}/AuthenticationException.java  |  15 ++-
 .../internal/client/proto/HandshakeExtension.java  |   5 +-
 .../ignite/client/handler/ItClientHandlerTest.java |  14 ++-
 .../handler/ClientInboundMessageHandler.java       |  35 ++++--
 .../apache/ignite/client/BasicAuthenticator.java   | 101 ++++++++++++++++
 .../org/apache/ignite/client/IgniteClient.java     |  20 +++-
 .../ignite/client/IgniteClientAuthenticator.java}  |  32 +++--
 .../ignite/client/IgniteClientConfiguration.java   |   9 ++
 .../client/IgniteClientConfigurationImpl.java      |  17 ++-
 .../ignite/internal/client/TcpClientChannel.java   |  30 ++++-
 .../ignite/client/ClientAuthenticationTest.java    | 132 +++++++++++++++++++++
 .../org/apache/ignite/client/RetryPolicyTest.java  |   2 +-
 .../DelegatingAuthenticationProvider.java          |   2 +-
 .../AnonymousRequest.java}                         |  19 +--
 .../authentication/AuthenticationManagerImpl.java  |   2 +-
 .../security/authentication/Authenticator.java     |   3 +-
 .../AuthenticationManagerImplTest.java             |   2 +-
 17 files changed, 398 insertions(+), 42 deletions(-)

diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/exception/AuthenticationException.java
 
b/modules/api/src/main/java/org/apache/ignite/security/AuthenticationException.java
similarity index 68%
copy from 
modules/security/src/main/java/org/apache/ignite/internal/security/exception/AuthenticationException.java
copy to 
modules/api/src/main/java/org/apache/ignite/security/AuthenticationException.java
index f669a66d77..100ac20336 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/exception/AuthenticationException.java
+++ 
b/modules/api/src/main/java/org/apache/ignite/security/AuthenticationException.java
@@ -15,8 +15,9 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.security.exception;
+package org.apache.ignite.security;
 
+import java.util.UUID;
 import org.apache.ignite.lang.ErrorGroups.Authentication;
 import org.apache.ignite.lang.IgniteException;
 
@@ -27,4 +28,16 @@ public class AuthenticationException extends IgniteException 
{
     public AuthenticationException(String message) {
         super(Authentication.COMMON_AUTHENTICATION_ERR, message);
     }
+
+    /**
+     * Creates an exception with the given trace ID, error code, detailed 
message, and cause.
+     *
+     * @param traceId Unique identifier of the exception.
+     * @param code Full error code.
+     * @param message Detailed message.
+     * @param cause Optional nested exception (can be {@code null}).
+     */
+    public AuthenticationException(UUID traceId, int code, String message, 
Throwable cause) {
+        super(traceId, code, message, cause);
+    }
 }
diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/HandshakeExtension.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/HandshakeExtension.java
index 34397328b6..8656a2df62 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/HandshakeExtension.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/HandshakeExtension.java
@@ -21,8 +21,9 @@ import org.jetbrains.annotations.Nullable;
 
 /** Handshake extensions. */
 public enum HandshakeExtension {
-    USERNAME("username", String.class),
-    PASSWORD("password", String.class),
+    AUTHENTICATION_TYPE("authn-type", String.class),
+    AUTHENTICATION_IDENTITY("authn-identity", String.class),
+    AUTHENTICATION_SECRET("authn-secret", String.class),
     ;
 
     private final String key;
diff --git 
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
index 8ff55202af..6bb8ce8241 100644
--- 
a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
+++ 
b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ItClientHandlerTest.java
@@ -54,6 +54,7 @@ public class ItClientHandlerTest {
 
     private int serverPort;
 
+    @SuppressWarnings("unused")
     @InjectConfiguration
     private AuthenticationConfiguration authenticationConfiguration;
 
@@ -156,7 +157,7 @@ public class ItClientHandlerTest {
             packer.packInt(0);
             packer.packInt(0);
             packer.packInt(0);
-            packer.packInt(7 + 8 + 5 + 10 + 10); // Size.
+            packer.packInt(67); // Size.
 
             packer.packInt(3); // Major.
             packer.packInt(0); // Minor.
@@ -165,10 +166,12 @@ public class ItClientHandlerTest {
             packer.packInt(2); // Client type: general purpose.
 
             packer.packBinaryHeader(0); // Features.
-            packer.packMapHeader(2); // Extensions.
-            packer.packString("username");
+            packer.packMapHeader(3); // Extensions.
+            packer.packString("authn-type");
+            packer.packString("basic");
+            packer.packString("authn-identity");
             packer.packString("admin");
-            packer.packString("password");
+            packer.packString("authn-secret");
             packer.packString("password");
 
             out.write(packer.toByteArray());
@@ -257,12 +260,11 @@ public class ItClientHandlerTest {
             assertEquals(COMMON_AUTHENTICATION_ERR, code);
 
             assertThat(errMsg, containsString("Authentication failed"));
-            
assertEquals("org.apache.ignite.internal.security.exception.AuthenticationException",
 errClassName);
+            assertEquals("org.apache.ignite.security.AuthenticationException", 
errClassName);
             assertNull(errStackTrace);
         }
     }
 
-
     @Test
     void testHandshakeInvalidVersionReturnsError() throws Exception {
         try (var sock = new Socket("127.0.0.1", serverPort)) {
diff --git 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
index 9229755c3b..797e86dbc2 100644
--- 
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
+++ 
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
@@ -94,8 +94,10 @@ import 
org.apache.ignite.internal.jdbc.proto.JdbcQueryCursorHandler;
 import org.apache.ignite.internal.jdbc.proto.JdbcQueryEventHandler;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
+import org.apache.ignite.internal.security.authentication.AnonymousRequest;
 import 
org.apache.ignite.internal.security.authentication.AuthenticationManager;
 import 
org.apache.ignite.internal.security.authentication.AuthenticationRequest;
+import org.apache.ignite.internal.security.authentication.UserDetails;
 import 
org.apache.ignite.internal.security.authentication.UsernamePasswordRequest;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
 import org.apache.ignite.internal.table.IgniteTablesInternal;
@@ -104,6 +106,8 @@ import org.apache.ignite.lang.IgniteException;
 import org.apache.ignite.lang.IgniteInternalCheckedException;
 import org.apache.ignite.network.ClusterNode;
 import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.security.AuthenticationException;
+import org.apache.ignite.security.AuthenticationType;
 import org.apache.ignite.sql.IgniteSql;
 import org.apache.ignite.tx.IgniteTransactions;
 import org.jetbrains.annotations.Nullable;
@@ -267,9 +271,9 @@ public class ClientInboundMessageHandler extends 
ChannelInboundHandlerAdapter im
             var clientCode = unpacker.unpackInt();
             var featuresLen = unpacker.unpackBinaryHeader();
             var features = BitSet.valueOf(unpacker.readPayload(featuresLen));
-            var extensions = extractExtensions(unpacker);
 
-            var userDetails = 
authenticationManager.authenticate(createAuthenticationRequest(extensions));
+            Map<HandshakeExtension, Object> extensions = 
extractExtensions(unpacker);
+            UserDetails userDetails = authenticate(extensions);
 
             clientContext = new ClientContext(clientVer, clientCode, features, 
userDetails);
 
@@ -321,9 +325,26 @@ public class ClientInboundMessageHandler extends 
ChannelInboundHandlerAdapter im
         }
     }
 
-    private AuthenticationRequest<?, ?> 
createAuthenticationRequest(Map<HandshakeExtension, Object> extensions) {
-        return new UsernamePasswordRequest((String) 
extensions.get(HandshakeExtension.USERNAME),
-                (String) extensions.get(HandshakeExtension.PASSWORD));
+    private UserDetails authenticate(Map<HandshakeExtension, Object> 
extensions) {
+        AuthenticationRequest<?, ?> authenticationRequest = 
createAuthenticationRequest(extensions);
+
+        return authenticationManager.authenticate(authenticationRequest);
+    }
+
+    private static AuthenticationRequest<?, ?> 
createAuthenticationRequest(Map<HandshakeExtension, Object> extensions) {
+        Object authnType = 
extensions.get(HandshakeExtension.AUTHENTICATION_TYPE);
+
+        if (authnType == null) {
+            return new AnonymousRequest();
+        }
+
+        if (authnType instanceof String && 
AuthenticationType.BASIC.name().equalsIgnoreCase((String) authnType)) {
+            return new UsernamePasswordRequest(
+                    (String) 
extensions.get(HandshakeExtension.AUTHENTICATION_IDENTITY),
+                    (String) 
extensions.get(HandshakeExtension.AUTHENTICATION_SECRET));
+        }
+
+        throw new AuthenticationException("Unsupported authentication type: " 
+ authnType);
     }
 
     private void writeMagic(ChannelHandlerContext ctx) {
@@ -648,7 +669,7 @@ public class ClientInboundMessageHandler extends 
ChannelInboundHandlerAdapter im
         return CompletableFuture.completedFuture(null);
     }
 
-    private Map<HandshakeExtension, Object> 
extractExtensions(ClientMessageUnpacker unpacker) {
+    private static Map<HandshakeExtension, Object> 
extractExtensions(ClientMessageUnpacker unpacker) {
         EnumMap<HandshakeExtension, Object> extensions = new 
EnumMap<>(HandshakeExtension.class);
         int mapSize = unpacker.unpackMapHeader();
         for (int i = 0; i < mapSize; i++) {
@@ -660,7 +681,7 @@ public class ClientInboundMessageHandler extends 
ChannelInboundHandlerAdapter im
         return extensions;
     }
 
-    private Object unpackExtensionValue(HandshakeExtension handshakeExtension, 
ClientMessageUnpacker unpacker) {
+    private static Object unpackExtensionValue(HandshakeExtension 
handshakeExtension, ClientMessageUnpacker unpacker) {
         Class<?> type = handshakeExtension.valueType();
         if (type == String.class) {
             return unpacker.unpackString();
diff --git 
a/modules/client/src/main/java/org/apache/ignite/client/BasicAuthenticator.java 
b/modules/client/src/main/java/org/apache/ignite/client/BasicAuthenticator.java
new file mode 100644
index 0000000000..7235b15c04
--- /dev/null
+++ 
b/modules/client/src/main/java/org/apache/ignite/client/BasicAuthenticator.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.client;
+
+import org.apache.ignite.security.AuthenticationType;
+
+/**
+ * Basic authenticator with username and password.
+ *
+ * <p>Credentials are sent to the server in plain text, unless SSL/TLS is 
enabled - see {@link IgniteClientConfiguration#ssl()}.
+ */
+public class BasicAuthenticator implements IgniteClientAuthenticator {
+    private final String username;
+
+    private final String password;
+
+    private BasicAuthenticator(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    /**
+     * Creates a new builder.
+     *
+     * @return Builder.
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String type() {
+        return AuthenticationType.BASIC.name();
+    }
+
+    @Override
+    public Object identity() {
+        return username;
+    }
+
+    @Override
+    public Object secret() {
+        return password;
+    }
+
+    /**
+     * Builder.
+     */
+    static class Builder {
+        private String username;
+        private String password;
+
+        /**
+         * Sets username.
+         *
+         * @param username Username.
+         * @return {@code this} for chaining.
+         */
+        public Builder username(String username) {
+            this.username = username;
+
+            return this;
+        }
+
+        /**
+         * Sets password.
+         *
+         * @param password Password.
+         * @return {@code this} for chaining.
+         */
+        public Builder password(String password) {
+            this.password = password;
+
+            return this;
+        }
+
+        /**
+         * Builds a new authenticator.
+         *
+         * @return Authenticator.
+         */
+        public BasicAuthenticator build() {
+            return new BasicAuthenticator(username, password);
+        }
+    }
+}
diff --git 
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java 
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
index ffdeb70fbf..f108e2165c 100644
--- a/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
+++ b/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
@@ -107,6 +107,9 @@ public interface IgniteClient extends Ignite {
         /** Metrics enabled flag. */
         private boolean metricsEnabled;
 
+        /** Authenticator. */
+        private @Nullable IgniteClientAuthenticator authenticator;
+
         /**
          * Sets the addresses of Ignite server nodes within a cluster. An 
address can be an IP address or a hostname, with or without port.
          * If port is not set then Ignite will generate multiple addresses for 
default port range. See {@link
@@ -317,6 +320,20 @@ public interface IgniteClient extends Ignite {
             return this;
         }
 
+        /**
+         * Sets the authenticator.
+         *
+         * <p>See also: {@link BasicAuthenticator}.
+         *
+         * @param authenticator Authenticator.
+         * @return This instance.
+         */
+        public Builder authenticator(@Nullable IgniteClientAuthenticator 
authenticator) {
+            this.authenticator = authenticator;
+
+            return this;
+        }
+
         /**
          * Builds the client.
          *
@@ -345,7 +362,8 @@ public interface IgniteClient extends Ignite {
                     retryPolicy,
                     loggerFactory,
                     sslConfiguration,
-                    metricsEnabled);
+                    metricsEnabled,
+                    authenticator);
 
             return TcpIgniteClient.startAsync(cfg);
         }
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/exception/AuthenticationException.java
 
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClientAuthenticator.java
similarity index 64%
copy from 
modules/security/src/main/java/org/apache/ignite/internal/security/exception/AuthenticationException.java
copy to 
modules/client/src/main/java/org/apache/ignite/client/IgniteClientAuthenticator.java
index f669a66d77..a534593f68 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/exception/AuthenticationException.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClientAuthenticator.java
@@ -15,16 +15,30 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.security.exception;
-
-import org.apache.ignite.lang.ErrorGroups.Authentication;
-import org.apache.ignite.lang.IgniteException;
+package org.apache.ignite.client;
 
 /**
- * The general authentication exception.
+ * Ignite client authenticator. Provides authentication information during 
server handshake.
  */
-public class AuthenticationException extends IgniteException {
-    public AuthenticationException(String message) {
-        super(Authentication.COMMON_AUTHENTICATION_ERR, message);
-    }
+public interface IgniteClientAuthenticator {
+    /**
+     * Authenticator type.
+     *
+     * @return Authenticator type.
+     */
+    String type();
+
+    /**
+     * Identity.
+     *
+     * @return Identity.
+     */
+    Object identity();
+
+    /**
+     * Secret.
+     *
+     * @return Secret.
+     */
+    Object secret();
 }
diff --git 
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
 
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
index 20652b45b5..4f93eb6625 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
@@ -177,4 +177,13 @@ public interface IgniteClientConfiguration {
      * @return {@code true} if metrics are enabled.
      */
     boolean metricsEnabled();
+
+    /**
+     * Gets the authenticator.
+     *
+     * <p>See also: {@link BasicAuthenticator}.
+     *
+     * @return Authenticator.
+     */
+    @Nullable IgniteClientAuthenticator authenticator();
 }
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
index af7372a2ec..4975c8eae6 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.client;
 
 import java.util.concurrent.Executor;
 import org.apache.ignite.client.IgniteClientAddressFinder;
+import org.apache.ignite.client.IgniteClientAuthenticator;
 import org.apache.ignite.client.IgniteClientConfiguration;
 import org.apache.ignite.client.RetryPolicy;
 import org.apache.ignite.client.SslConfiguration;
@@ -65,6 +66,8 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
 
     private final boolean metricsEnabled;
 
+    private final @Nullable  IgniteClientAuthenticator authenticator;
+
     /**
      * Constructor.
      *
@@ -78,8 +81,10 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
      * @param heartbeatInterval Heartbeat message interval.
      * @param heartbeatTimeout Heartbeat message timeout.
      * @param retryPolicy Retry policy.
-     * @param loggerFactory Logger factory which will be used to create a 
logger instance for this this particular client when needed.
+     * @param loggerFactory Logger factory which will be used to create a 
logger instance for this this particular client when
+     *         needed.
      * @param metricsEnabled Whether metrics are enabled.
+     * @param authenticator Authenticator.
      */
     public IgniteClientConfigurationImpl(
             IgniteClientAddressFinder addressFinder,
@@ -94,7 +99,8 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
             @Nullable RetryPolicy retryPolicy,
             @Nullable LoggerFactory loggerFactory,
             @Nullable SslConfiguration sslConfiguration,
-            boolean metricsEnabled) {
+            boolean metricsEnabled,
+            @Nullable IgniteClientAuthenticator authenticator) {
         this.addressFinder = addressFinder;
 
         //noinspection AssignmentOrReturnOfFieldWithMutableType (cloned in 
Builder).
@@ -111,6 +117,7 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
         this.loggerFactory = loggerFactory;
         this.sslConfiguration = sslConfiguration;
         this.metricsEnabled = metricsEnabled;
+        this.authenticator = authenticator;
     }
 
     /** {@inheritDoc} */
@@ -190,4 +197,10 @@ public final class IgniteClientConfigurationImpl 
implements IgniteClientConfigur
     public boolean metricsEnabled() {
         return metricsEnabled;
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public IgniteClientAuthenticator authenticator() {
+        return authenticator;
+    }
 }
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
index fc5b62dde0..4bd492d973 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
@@ -42,6 +42,7 @@ import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
+import org.apache.ignite.client.IgniteClientAuthenticator;
 import org.apache.ignite.client.IgniteClientConnectionException;
 import org.apache.ignite.internal.client.io.ClientConnection;
 import org.apache.ignite.internal.client.io.ClientConnectionMultiplexer;
@@ -51,6 +52,7 @@ import 
org.apache.ignite.internal.client.proto.ClientMessageCommon;
 import org.apache.ignite.internal.client.proto.ClientMessagePacker;
 import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
 import org.apache.ignite.internal.client.proto.ClientOp;
+import org.apache.ignite.internal.client.proto.HandshakeExtension;
 import org.apache.ignite.internal.client.proto.ProtocolVersion;
 import org.apache.ignite.internal.client.proto.ResponseFlags;
 import org.apache.ignite.internal.client.proto.ServerMessageType;
@@ -513,7 +515,23 @@ class TcpClientChannel implements ClientChannel, 
ClientMessageHandler, ClientCon
         req.packInt(2); // Client type: general purpose.
 
         req.packBinaryHeader(0); // Features.
-        req.packMapHeader(0); // Extensions.
+
+        IgniteClientAuthenticator authenticator = 
cfg.clientConfiguration().authenticator();
+
+        if (authenticator != null) {
+            req.packMapHeader(3); // Extensions.
+
+            req.packString(HandshakeExtension.AUTHENTICATION_TYPE.key());
+            req.packString(authenticator.type());
+
+            req.packString(HandshakeExtension.AUTHENTICATION_IDENTITY.key());
+            packAuthnObj(req, authenticator.identity());
+
+            req.packString(HandshakeExtension.AUTHENTICATION_SECRET.key());
+            packAuthnObj(req, authenticator.secret());
+        } else {
+            req.packMapHeader(0); // Extensions.
+        }
 
         return write(req);
     }
@@ -604,6 +622,16 @@ class TcpClientChannel implements ClientChannel, 
ClientMessageHandler, ClientCon
         return Math.min(configuredInterval, recommendedHeartbeatInterval);
     }
 
+    private static void packAuthnObj(ClientMessagePacker packer, Object obj) {
+        if (obj == null) {
+            packer.packNil();
+        } else if (obj instanceof String) {
+            packer.packString((String) obj);
+        } else {
+            throw new IllegalArgumentException("Unsupported authentication 
object type: " + obj.getClass().getName());
+        }
+    }
+
     /**
      * Client request future.
      */
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/ClientAuthenticationTest.java
 
b/modules/client/src/test/java/org/apache/ignite/client/ClientAuthenticationTest.java
new file mode 100644
index 0000000000..c8c4a70728
--- /dev/null
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/ClientAuthenticationTest.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.client;
+
+import java.util.UUID;
+import org.apache.ignite.client.fakes.FakeIgnite;
+import org.apache.ignite.internal.configuration.AuthenticationConfiguration;
+import 
org.apache.ignite.internal.configuration.BasicAuthenticationProviderChange;
+import 
org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
+import 
org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.testframework.IgniteTestUtils;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.security.AuthenticationException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Tests client authentication.
+ */
+@SuppressWarnings({"resource", "ThrowableNotThrown"})
+@ExtendWith(ConfigurationExtension.class)
+public class ClientAuthenticationTest {
+    @SuppressWarnings("unused")
+    @InjectConfiguration
+    private AuthenticationConfiguration authenticationConfiguration;
+
+    private TestServer server;
+
+    private IgniteClient client;
+
+    @BeforeEach
+    public void beforeEach() {
+        authenticationConfiguration.change(change -> {
+            change.changeEnabled(false);
+            change.changeProviders().delete("basic");
+        }).join();
+    }
+
+    @AfterEach
+    public void afterEach() throws Exception {
+        IgniteUtils.closeAll(client, server);
+    }
+
+    @Test
+    public void testNoAuthnOnServerNoAuthnOnClient() {
+        server = startServer(false);
+        client = startClient(null);
+    }
+
+    @Test
+    public void testAuthnOnClientNoAuthnOnServer() {
+        server = startServer(false);
+
+        
startClient(BasicAuthenticator.builder().username("u").password("p").build());
+    }
+
+    @Test
+    public void testAuthnOnServerNoAuthnOnClient() {
+        server = startServer(true);
+
+        IgniteTestUtils.assertThrowsWithCause(() -> startClient(null), 
AuthenticationException.class, "Authentication failed");
+    }
+
+    @Test
+    public void testAuthnOnServerBadAuthnOnClient() {
+        server = startServer(true);
+
+        BasicAuthenticator authenticator = 
BasicAuthenticator.builder().username("u").password("p").build();
+
+        IgniteTestUtils.assertThrowsWithCause(() -> 
startClient(authenticator), AuthenticationException.class, "Authentication 
failed");
+    }
+
+    @Test
+    public void testAuthnOnClientAuthnOnServer() {
+        server = startServer(false);
+
+        
startClient(BasicAuthenticator.builder().username("usr").password("pwd").build());
+    }
+
+    private IgniteClient startClient(@Nullable IgniteClientAuthenticator 
authenticator) {
+        return IgniteClient.builder()
+                .addresses("127.0.0.1:" + server.port())
+                .authenticator(authenticator)
+                .build();
+    }
+
+    @NotNull
+    private TestServer startServer(boolean basicAuthn) {
+        var server = new TestServer(
+                10800,
+                10,
+                1000,
+                new FakeIgnite(),
+                null,
+                null,
+                null,
+                UUID.randomUUID(),
+                authenticationConfiguration);
+
+        if (basicAuthn) {
+            authenticationConfiguration.change(change -> {
+                change.changeEnabled(true);
+                change.changeProviders().create("basic", 
authenticationProviderChange ->
+                        
authenticationProviderChange.convert(BasicAuthenticationProviderChange.class)
+                                .changeUsername("usr")
+                                .changePassword("pwd")
+                                .changeName("basic"));
+            }).join();
+        }
+
+        return server;
+    }
+}
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java 
b/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
index 07f4de771f..9b377ca3b4 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
@@ -228,7 +228,7 @@ public class RetryPolicyTest {
     @Test
     public void testRetryReadPolicyAllOperationsSupported() {
         var plc = new RetryReadPolicy();
-        var cfg = new IgniteClientConfigurationImpl(null, null, 0, 0, 0, 0, 
null, 0, 0, null, null, null, false);
+        var cfg = new IgniteClientConfigurationImpl(null, null, 0, 0, 0, 0, 
null, 0, 0, null, null, null, false, null);
 
         for (var op : ClientOperationType.values()) {
             var ctx = new RetryPolicyContextImpl(cfg, op, 0, null);
diff --git 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/authentication/DelegatingAuthenticationProvider.java
 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/authentication/DelegatingAuthenticationProvider.java
index a46a4d62ca..55314742f8 100644
--- 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/authentication/DelegatingAuthenticationProvider.java
+++ 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/authentication/DelegatingAuthenticationProvider.java
@@ -27,7 +27,7 @@ import 
org.apache.ignite.internal.security.authentication.AuthenticationManager;
 import org.apache.ignite.internal.security.authentication.Authenticator;
 import org.apache.ignite.internal.security.authentication.UserDetails;
 import 
org.apache.ignite.internal.security.authentication.UsernamePasswordRequest;
-import org.apache.ignite.internal.security.exception.AuthenticationException;
+import org.apache.ignite.security.AuthenticationException;
 import org.reactivestreams.Publisher;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.FluxSink;
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/exception/AuthenticationException.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AnonymousRequest.java
similarity index 68%
rename from 
modules/security/src/main/java/org/apache/ignite/internal/security/exception/AuthenticationException.java
rename to 
modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AnonymousRequest.java
index f669a66d77..c2867ecf1a 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/exception/AuthenticationException.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AnonymousRequest.java
@@ -15,16 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.security.exception;
-
-import org.apache.ignite.lang.ErrorGroups.Authentication;
-import org.apache.ignite.lang.IgniteException;
+package org.apache.ignite.internal.security.authentication;
 
 /**
- * The general authentication exception.
+ * Represents a request to authenticate anonymously.
  */
-public class AuthenticationException extends IgniteException {
-    public AuthenticationException(String message) {
-        super(Authentication.COMMON_AUTHENTICATION_ERR, message);
+public class AnonymousRequest implements AuthenticationRequest<Void, Void> {
+    @Override
+    public Void getIdentity() {
+        return null;
+    }
+
+    @Override
+    public Void getSecret() {
+        return null;
     }
 }
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImpl.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImpl.java
index eadee2cb37..589419f4f0 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImpl.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImpl.java
@@ -30,7 +30,7 @@ import 
org.apache.ignite.internal.configuration.AuthenticationProviderView;
 import org.apache.ignite.internal.configuration.AuthenticationView;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
-import org.apache.ignite.internal.security.exception.AuthenticationException;
+import org.apache.ignite.security.AuthenticationException;
 import org.jetbrains.annotations.Nullable;
 
 /**
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/Authenticator.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/Authenticator.java
index 7dab7ef1b4..a3bcf2519c 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/Authenticator.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/Authenticator.java
@@ -17,11 +17,12 @@
 
 package org.apache.ignite.internal.security.authentication;
 
-import org.apache.ignite.internal.security.exception.AuthenticationException;
+import org.apache.ignite.security.AuthenticationException;
 
 /**
  * General interface for all authenticators.
  */
+@SuppressWarnings("InterfaceMayBeAnnotatedFunctional")
 public interface Authenticator {
 
     /**
diff --git 
a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImplTest.java
 
b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImplTest.java
index b9fbe36d39..29afda6906 100644
--- 
a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImplTest.java
+++ 
b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationManagerImplTest.java
@@ -30,7 +30,7 @@ import 
org.apache.ignite.internal.configuration.AuthenticationView;
 import 
org.apache.ignite.internal.configuration.BasicAuthenticationProviderChange;
 import 
org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import 
org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
-import org.apache.ignite.internal.security.exception.AuthenticationException;
+import org.apache.ignite.security.AuthenticationException;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 


Reply via email to