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

apkhmv 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 2b341b4fec IGNITE-20643 Migrate basic auth provider to users list 
(#2921)
2b341b4fec is described below

commit 2b341b4fecd88886e6001401261b77e64bceb8e0
Author: Mikhail <pochat...@users.noreply.github.com>
AuthorDate: Thu Dec 7 21:35:58 2023 +0300

    IGNITE-20643 Migrate basic auth provider to users list (#2921)
---
 .../configuration/ItConfigCommandTest.java         |  14 +-
 .../cluster-configuration-with-enabled-auth.conf   |  27 +--
 .../ignite/client/handler/ItClientHandlerTest.java |   3 +-
 .../handler/ClientInboundMessageHandlerTest.java   | 107 ++++-----
 .../ignite/client/ClientAuthenticationTest.java    |  14 +-
 modules/distribution-zones/build.gradle            |   1 +
 ...niteDistributionZoneManagerNodeRestartTest.java |   8 +-
 .../ignite/jdbc/ItJdbcAuthenticationTest.java      |  10 +-
 .../rest/ItInitializedClusterRestTest.java         |   2 +-
 .../rest/authentication/ItAuthenticationTest.java  | 265 +++++++--------------
 .../runner/app/PlatformTestNodeRunner.java         |   5 +-
 .../AuthenticationProviderEqualityVerifier.java    |  22 +-
 .../authentication/AuthenticatorFactory.java       |   8 +-
 .../SecurityConfigurationModule.java               |  15 +-
 ...cAuthenticationProviderConfigurationSchema.java |  16 +-
 .../authentication/basic/BasicAuthenticator.java   |  37 +--
 ...iderConfigurationSchema.java => BasicUser.java} |  35 +--
 ...hema.java => BasicUserConfigurationSchema.java} |  15 +-
 .../AuthenticationProvidersValidatorImpl.java      |  39 ++-
 .../AuthenticationManagerImplTest.java             |  10 +-
 .../AuthenticationProvidersValidatorImplTest.java  |   5 +-
 .../SecurityConfigurationModuleTest.java           |  13 +-
 ...henticationProviderConfigurationSchemaTest.java |  10 +-
 .../basic/BasicAuthenticatorTest.java              |   6 +-
 24 files changed, 329 insertions(+), 358 deletions(-)

diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/configuration/ItConfigCommandTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/configuration/ItConfigCommandTest.java
index e7263f0df7..ff9453151a 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/configuration/ItConfigCommandTest.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/configuration/ItConfigCommandTest.java
@@ -119,7 +119,7 @@ class ItConfigCommandTest extends 
CliCommandTestInitializedIntegrationBase {
     @DisplayName("Should update config with key-value format when valid 
cluster-endpoint-url is given")
     void updateClusterConfigWithoutQuoting() {
         execute("cluster", "config", "update", "--cluster-endpoint-url", 
NODE_URL,
-                
"security.authentication.providers.basic1={type=basic,username=asd,password=asadf}");
+                
"security.authentication.providers.default={type=basic,users=[{username=asd,password=pass1}]}");
 
         assertAll(
                 this::assertExitCodeIsZero,
@@ -130,7 +130,7 @@ class ItConfigCommandTest extends 
CliCommandTestInitializedIntegrationBase {
 
         //Emulate config with spaces
         execute("cluster", "config", "update", "--cluster-endpoint-url", 
NODE_URL,
-                "security.authentication.providers.basic2", "=", "{", 
"type=basic,", "username=asd,", "password=asadf}");
+                "security.authentication.providers.default", "=", "{", 
"type=basic,", "users=[{", "username=asd,", "password=pass2}]}");
 
         assertAll(
                 this::assertExitCodeIsZero,
@@ -144,7 +144,7 @@ class ItConfigCommandTest extends 
CliCommandTestInitializedIntegrationBase {
     void updateClusterWithQuotedArgs() {
         //Emulate quoting config
         execute("cluster", "config", "update", "--cluster-endpoint-url", 
NODE_URL,
-                
"\"security.authentication.providers.basic3={type=basic,username=asd,password=asadf}\"");
+                
"\"security.authentication.providers.default={type=basic,users=[{username=asd,password=pass3}]}\"");
 
         assertAll(
                 this::assertExitCodeIsZero,
@@ -154,7 +154,7 @@ class ItConfigCommandTest extends 
CliCommandTestInitializedIntegrationBase {
 
         //Emulate quoting config
         execute("cluster", "config", "update", "--cluster-endpoint-url", 
NODE_URL,
-                "\"security.authentication.providers.basic4\"", 
"\"={type=basic,username=asd,password=asadf}\"");
+                "\"security.authentication.providers.default\"", 
"\"={type=basic,users=[{username=asd,password=pass4}]}\"");
 
         assertAll(
                 this::assertExitCodeIsZero,
@@ -164,7 +164,7 @@ class ItConfigCommandTest extends 
CliCommandTestInitializedIntegrationBase {
 
         //Emulate quoting config
         execute("cluster", "config", "update", "--cluster-endpoint-url", 
NODE_URL,
-                "security.authentication.providers.basic5", 
"\"={type=basic,username=asd,password=asadf}\"");
+                "security.authentication.providers.default", 
"\"={type=basic,users=[{username=asd,password=pass5}]}\"");
 
         assertAll(
                 this::assertExitCodeIsZero,
@@ -177,7 +177,7 @@ class ItConfigCommandTest extends 
CliCommandTestInitializedIntegrationBase {
     @DisplayName("Test using arguments in parameters")
     void useOptionsInArguments() {
         execute("cluster", "config", "update", "--cluster-endpoint-url", 
NODE_URL,
-                
"security.authentication.providers.basic6={type=basic,username:", "--verbose,", 
"password=--verbose}");
+                
"security.authentication.providers.default={type=basic,users=[{username:", 
"--verbose,", "password=--verbose}]}");
 
         assertAll(
                 () -> assertExitCodeIs(2),
@@ -188,7 +188,7 @@ class ItConfigCommandTest extends 
CliCommandTestInitializedIntegrationBase {
         resetOutput();
 
         execute("cluster", "config", "update", "--cluster-endpoint-url", 
NODE_URL,
-                
"\"security.authentication.providers.basic7={type=basic,username: --verbose, 
password=--verbose}\"");
+                
"\"security.authentication.providers.default={type=basic,users=[{username: 
--verbose, password=--verbose}]}\"");
 
         assertAll(
                 this::assertExitCodeIsZero,
diff --git 
a/modules/cli/src/testFixtures/resources/cluster-configuration-with-enabled-auth.conf
 
b/modules/cli/src/testFixtures/resources/cluster-configuration-with-enabled-auth.conf
index f9461400ef..211f31d24d 100644
--- 
a/modules/cli/src/testFixtures/resources/cluster-configuration-with-enabled-auth.conf
+++ 
b/modules/cli/src/testFixtures/resources/cluster-configuration-with-enabled-auth.conf
@@ -1,19 +1,18 @@
 security: {
   enabled: true,
   authentication: {
-    providers: [
-      {
-        name: basic,
-        type: basic,
-        username: admin,
-        password: password
-      },
-      {
-        name: basic1,
-        type: basic,
-        username: admin1,
-        password: password
-      }
-    ]
+    providers.default: {
+      type: basic,
+      users: [
+        {
+          username: admin,
+          password: password
+        },
+        {
+          username: admin1,
+          password: password
+        }
+      ]
+    }
   }
 }
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 4d4adaae6f..288552f77b 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
@@ -460,8 +460,7 @@ public class ItClientHandlerTest extends 
BaseIgniteAbstractTest {
             change.changeEnabled(true);
             change.changeAuthentication().changeProviders().create("basic", 
authenticationProviderChange -> {
                 
authenticationProviderChange.convert(BasicAuthenticationProviderChange.class)
-                        .changeUsername(username)
-                        .changePassword(password);
+                        .changeUsers(users -> users.create(username, user -> 
user.changePassword(password)));
             });
         }).join();
     }
diff --git 
a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientInboundMessageHandlerTest.java
 
b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientInboundMessageHandlerTest.java
index 595d4b4656..db97e65c78 100644
--- 
a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientInboundMessageHandlerTest.java
+++ 
b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientInboundMessageHandlerTest.java
@@ -17,7 +17,9 @@
 
 package org.apache.ignite.client.handler;
 
+import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static org.awaitility.Awaitility.await;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
@@ -69,6 +71,8 @@ import org.msgpack.core.MessagePack;
 class ClientInboundMessageHandlerTest extends BaseIgniteAbstractTest {
     private static final Duration TIMEOUT_OF_DURING = Duration.ofSeconds(2);
 
+    private static final String PROVIDER_NAME = "basic";
+
     @InjectConfiguration
     private ClientConnectorConfiguration configuration;
 
@@ -166,22 +170,20 @@ class ClientInboundMessageHandlerTest extends 
BaseIgniteAbstractTest {
         authenticationManager.listen(handler);
         securityConfiguration.listen(authenticationManager);
 
-        securityConfiguration.change(change -> {
+        CompletableFuture<Void> future = securityConfiguration.change(change 
-> {
             change.changeEnabled(true);
             change.changeAuthentication(authChange -> {
                 authChange.changeProviders(providersChange -> {
-                    providersChange.create("basic", basicChange -> {
-                        
basicChange.convert(BasicAuthenticationProviderChange.class)
-                                .changeUsername("admin")
-                                .changePassword("password");
-                    }).create("basic1", basicChange -> {
+                    providersChange.create(PROVIDER_NAME, basicChange -> {
                         
basicChange.convert(BasicAuthenticationProviderChange.class)
-                                .changeUsername("admin1")
-                                .changePassword("password");
+                                .changeUsers(users ->
+                                        users.create("admin", user -> 
user.changePassword("password"))
+                                                .create("admin1", user -> 
user.changePassword("password")));
                     });
                 });
             });
-        }).join();
+        });
+        assertThat(future, willCompleteSuccessfully());
 
         handler.channelRegistered(ctx);
     }
@@ -190,80 +192,49 @@ class ClientInboundMessageHandlerTest extends 
BaseIgniteAbstractTest {
     void disableAuthentication() throws IOException {
         handshake();
 
-        securityConfiguration.change(change -> {
+        CompletableFuture<Void> future = securityConfiguration.change(change 
-> {
             change.changeEnabled(false);
-        }).join();
+        });
+        assertThat(future, willCompleteSuccessfully());
 
         await().during(TIMEOUT_OF_DURING).untilAtomic(ctxClosed, is(false));
     }
 
     @Test
-    void enableAuthentication() throws InterruptedException, IOException {
-        securityConfiguration.change(change -> {
+    void enableAuthentication() throws IOException {
+        CompletableFuture<Void> future1 = securityConfiguration.change(change 
-> {
             change.changeEnabled(false);
-        }).join();
-
-        handshake();
-
-        securityConfiguration.change(change -> {
-            change.changeEnabled(true);
-        }).join();
-
-        await().untilAtomic(ctxClosed, is(true));
-    }
+        });
+        assertThat(future1, willCompleteSuccessfully());
 
-    @Test
-    void changeCurrentProvider() throws IOException {
         handshake();
 
-        securityConfiguration.change(change -> {
+        CompletableFuture<Void> future = securityConfiguration.change(change 
-> {
             change.changeEnabled(true);
-            change.changeAuthentication(authChange -> {
-                authChange.changeProviders(providersChange -> {
-                    providersChange.update("basic", basicChange -> {
-                        
basicChange.convert(BasicAuthenticationProviderChange.class)
-                                .changeUsername("admin")
-                                .changePassword("new-password");
-                    });
-                });
-            });
-        }).join();
+        });
+        assertThat(future, willCompleteSuccessfully());
 
         await().untilAtomic(ctxClosed, is(true));
     }
 
     @Test
-    void changeAnotherProvider() throws IOException {
+    void changeProvider() throws IOException {
         handshake();
 
-        securityConfiguration.change(change -> {
+        CompletableFuture<Void> future = securityConfiguration.change(change 
-> {
             change.changeEnabled(true);
             change.changeAuthentication(authChange -> {
                 authChange.changeProviders(providersChange -> {
-                    providersChange.update("basic1", basicChange -> {
+                    providersChange.update(PROVIDER_NAME, basicChange -> {
                         
basicChange.convert(BasicAuthenticationProviderChange.class)
-                                .changeUsername("admin1")
-                                .changePassword("new-password");
+                                .changeUsers(users ->
+                                        users.update("admin", user -> 
user.changePassword("new-password"))
+                                );
                     });
                 });
             });
-        }).join();
-
-        await().during(TIMEOUT_OF_DURING).untilAtomic(ctxClosed, is(false));
-    }
-
-    @Test
-    void deleteCurrentProvider() throws IOException {
-        handshake();
-
-        securityConfiguration.change(change -> {
-            change.changeEnabled(true);
-            change.changeAuthentication(authChange -> {
-                authChange.changeProviders(providersChange -> {
-                    providersChange.delete("basic");
-                });
-            });
-        }).join();
+        });
+        assertThat(future, willCompleteSuccessfully());
 
         await().untilAtomic(ctxClosed, is(true));
     }
@@ -272,34 +243,38 @@ class ClientInboundMessageHandlerTest extends 
BaseIgniteAbstractTest {
     void deleteAnotherProvider() throws IOException {
         handshake();
 
-        securityConfiguration.change(change -> {
+        CompletableFuture<Void> future = securityConfiguration.change(change 
-> {
             change.changeEnabled(true);
             change.changeAuthentication(authChange -> {
                 authChange.changeProviders(providersChange -> {
                     providersChange.delete("basic1");
                 });
             });
-        }).join();
+        });
+        assertThat(future, willCompleteSuccessfully());
 
         await().during(TIMEOUT_OF_DURING).untilAtomic(ctxClosed, is(false));
     }
 
     @Test
     void createNewProvider() throws IOException {
+        String newProviderName = "basic2";
         handshake();
 
-        securityConfiguration.change(change -> {
+        CompletableFuture<Void> future = securityConfiguration.change(change 
-> {
             change.changeEnabled(true);
             change.changeAuthentication(authChange -> {
                 authChange.changeProviders(providersChange -> {
-                    providersChange.create("basic2", basicChange -> {
+                    providersChange.create(newProviderName, basicChange -> {
                         
basicChange.convert(BasicAuthenticationProviderChange.class)
-                                .changeUsername("admin2")
-                                .changePassword("admin");
+                                .changeUsers(users ->
+                                        users.create("admin2", user -> 
user.changePassword("admin"))
+                                );
                     });
                 });
             });
-        }).join();
+        });
+        assertThat(future, willCompleteSuccessfully());
 
         await().during(TIMEOUT_OF_DURING).untilAtomic(ctxClosed, is(false));
     }
@@ -315,7 +290,7 @@ class ClientInboundMessageHandlerTest extends 
BaseIgniteAbstractTest {
         packer.packBinaryHeader(0); // Features.
         packer.packInt(3); // Extensions.
         packer.packString("authn-type");
-        packer.packString("basic");
+        packer.packString(PROVIDER_NAME);
         packer.packString("authn-identity");
         packer.packString("admin");
         packer.packString("authn-secret");
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
index 7c109a5667..5741879c3f 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/ClientAuthenticationTest.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/ClientAuthenticationTest.java
@@ -115,12 +115,14 @@ public class ClientAuthenticationTest extends 
BaseIgniteAbstractTest {
                 null);
 
         if (basicAuthn) {
-            securityConfiguration.change(change -> {
-                change.changeEnabled(true);
-                
change.changeAuthentication().changeProviders().create("basic", 
authenticationProviderChange ->
-                        
authenticationProviderChange.convert(BasicAuthenticationProviderChange.class)
-                                .changeUsername("usr")
-                                .changePassword("pwd"));
+            securityConfiguration.change(securityChange -> {
+                securityChange.changeEnabled(true);
+                
securityChange.changeAuthentication().changeProviders().create("basic", change 
->
+                        change.convert(BasicAuthenticationProviderChange.class)
+                                .changeUsers(users -> users.create("usr", user 
->
+                                        user.changePassword("pwd"))
+                                )
+                );
             }).join();
         }
 
diff --git a/modules/distribution-zones/build.gradle 
b/modules/distribution-zones/build.gradle
index 33fb0e3ccc..7e948adc28 100644
--- a/modules/distribution-zones/build.gradle
+++ b/modules/distribution-zones/build.gradle
@@ -88,6 +88,7 @@ dependencies {
     integrationTestImplementation project(':ignite-affinity')
     integrationTestImplementation project(':ignite-network-api')
     integrationTestImplementation project(':ignite-network')
+    integrationTestImplementation project(':ignite-security')
     integrationTestImplementation project(':ignite-system-view-api')
     integrationTestImplementation testFixtures(project(':ignite-core'))
     integrationTestImplementation testFixtures(project(':ignite-runner'))
diff --git 
a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java
 
b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java
index b21564d4c0..5095725906 100644
--- 
a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java
+++ 
b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItIgniteDistributionZoneManagerNodeRestartTest.java
@@ -56,6 +56,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -65,6 +66,7 @@ import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.function.Consumer;
 import java.util.function.LongFunction;
 import java.util.stream.Stream;
+import org.apache.ignite.configuration.validation.Validator;
 import org.apache.ignite.internal.BaseIgniteRestartTest;
 import org.apache.ignite.internal.catalog.CatalogManager;
 import org.apache.ignite.internal.catalog.CatalogManagerImpl;
@@ -98,6 +100,7 @@ import 
org.apache.ignite.internal.metastorage.server.TestRocksDbKeyValueStorage;
 import 
org.apache.ignite.internal.metastorage.server.raft.MetaStorageWriteHandler;
 import org.apache.ignite.internal.network.configuration.NetworkConfiguration;
 import org.apache.ignite.internal.network.recovery.VaultStateIds;
+import 
org.apache.ignite.internal.security.authentication.validator.AuthenticationProvidersValidatorImpl;
 import org.apache.ignite.internal.testframework.TestIgnitionManager;
 import org.apache.ignite.internal.util.ByteUtils;
 import org.apache.ignite.internal.vault.VaultManager;
@@ -219,11 +222,14 @@ public class 
ItIgniteDistributionZoneManagerNodeRestartTest extends BaseIgniteRe
                 modules.distributed().polymorphicSchemaExtensions()
         );
 
+        Set<Validator<?, ?>> validators = new 
HashSet<>(modules.distributed().validators());
+        validators.remove(AuthenticationProvidersValidatorImpl.INSTANCE);
+
         var clusterCfgMgr = new ConfigurationManager(
                 modules.distributed().rootKeys(),
                 cfgStorage,
                 distributedConfigurationGenerator,
-                
ConfigurationValidatorImpl.withDefaultValidators(distributedConfigurationGenerator,
 modules.distributed().validators())
+                
ConfigurationValidatorImpl.withDefaultValidators(distributedConfigurationGenerator,
 validators)
         );
 
         ConfigurationRegistry clusterConfigRegistry = 
clusterCfgMgr.configurationRegistry();
diff --git 
a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcAuthenticationTest.java
 
b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcAuthenticationTest.java
index e264223ecd..8ea08ba7f7 100644
--- 
a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcAuthenticationTest.java
+++ 
b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcAuthenticationTest.java
@@ -76,10 +76,14 @@ class ItJdbcAuthenticationTest extends 
IgniteIntegrationTest {
                             + "    \"authentication\": {\n"
                             + "      \"providers\": [\n"
                             + "        {\n"
-                            + "          \"name\": \"basic\",\n"
+                            + "          \"name\": \"default\",\n"
                             + "          \"type\": \"basic\",\n"
-                            + "          \"username\": \"usr\",\n"
-                            + "          \"password\": \"pwd\"\n"
+                            + "          \"users\": [\n"
+                            + "            {\n"
+                            + "              \"username\": \"usr\",\n"
+                            + "              \"password\": \"pwd\"\n"
+                            + "            }\n"
+                            + "          ]\n"
                             + "        }\n"
                             + "      ]\n"
                             + "    }\n"
diff --git 
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
index d04eafa616..3ee30dba4c 100644
--- 
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
+++ 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
@@ -173,7 +173,7 @@ public class ItInitializedClusterRestTest extends 
AbstractRestTestBase {
                         400,
                         containsString(
                                 "Validation did not pass for keys: "
-                                        + "[security.authentication.providers, 
Providers must be present, if security is enabled]"
+                                        + "[security.authentication.providers, 
Default provider default is not removable.]"
                         )
                 )
         );
diff --git 
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/authentication/ItAuthenticationTest.java
 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/authentication/ItAuthenticationTest.java
index b87527c357..7b14d7196b 100644
--- 
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/authentication/ItAuthenticationTest.java
+++ 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/authentication/ItAuthenticationTest.java
@@ -17,258 +17,179 @@
 
 package org.apache.ignite.internal.rest.authentication;
 
-
-import static java.util.stream.Collectors.toList;
-import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
-import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static 
org.apache.ignite.internal.testframework.matchers.HttpResponseMatcher.hasStatusCode;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.URI;
+import java.net.UnknownHostException;
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpRequest.BodyPublishers;
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandlers;
-import java.nio.file.Path;
 import java.time.Duration;
 import java.util.Base64;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.IntStream;
-import org.apache.ignite.internal.rest.RestNode;
-import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
-import org.apache.ignite.internal.testframework.WorkDirectory;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.ignite.InitParametersBuilder;
+import org.apache.ignite.internal.ClusterPerTestIntegrationTest;
+import org.apache.ignite.internal.app.IgniteImpl;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
-import org.junit.jupiter.api.AfterEach;
+import org.apache.ignite.network.NetworkAddress;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.api.extension.ExtendWith;
 
-
 /** Tests for the REST authentication configuration. */
 @ExtendWith(WorkDirectoryExtension.class)
-public class ItAuthenticationTest extends BaseIgniteAbstractTest {
-
+public class ItAuthenticationTest extends ClusterPerTestIntegrationTest {
     /** HTTP client that is expected to be defined in subclasses. */
     private HttpClient client;
 
-    private List<RestNode> nodes;
+    private String username = "admin";
+
+    private String password = "password";
 
-    @WorkDirectory
-    private Path workDir;
+    private boolean enableAuth = true;
 
     @BeforeEach
-    void setUp(TestInfo testInfo) {
+    @Override
+    public void setup(TestInfo testInfo) {
         client = HttpClient.newBuilder()
                 .build();
-
-        nodes = IntStream.range(0, 3)
-                .mapToObj(id -> {
-                    return RestNode.builder()
-                            .workDir(workDir)
-                            .name(testNodeName(testInfo, id))
-                            .networkPort(3344 + id)
-                            .httpPort(10300 + id)
-                            .httpsPort(10400 + id)
-                            .sslEnabled(false)
-                            .dualProtocol(false)
-                            .build();
-                })
-                .collect(toList());
-
-        nodes.stream().parallel().forEach(RestNode::start);
     }
 
-    @Test
-    public void disabledAuthentication(TestInfo testInfo) throws IOException, 
InterruptedException {
-        RestNode metaStorageNode = nodes.get(0);
+    @Override
+    protected void customizeInitParameters(InitParametersBuilder builder) {
+        String config = "security.enabled=" + enableAuth;
+
+        if (username != null && password != null) {
+            config += ",\n"
+                    + "security.authentication.providers.default={"
+                    + "type=basic,"
+                    + "users=[{username=" + username
+                    + ",password=" + password
+                    + "}]}";
+        }
 
-        // When.
-        String initClusterBody = "{\n"
-                + "    \"metaStorageNodes\": [\n"
-                + "        \"" + metaStorageNode.name() + "\"\n"
-                + "    ],\n"
-                + "    \"cmgNodes\": [],\n"
-                + "    \"clusterName\": \"cluster\"\n"
-                + "}";
+        builder.clusterConfiguration(config);
+    }
 
-        initCluster(metaStorageNode.httpAddress(), initClusterBody);
+    @Test
+    public void disabledAuthentication(TestInfo testInfo) throws Exception {
+        enableAuth = false;
+        super.setup(testInfo);
 
         // Then.
-        for (RestNode node : nodes) {
-            assertTrue(isRestAvailable(node.httpAddress(), "", ""));
-        }
+        cluster.runningNodes().forEach(node ->
+                assertTrue(isRestAvailable(node.restHttpAddress(), "", ""))
+        );
     }
 
     @Test
-    public void defaultUser(TestInfo testInfo) throws InterruptedException, 
IOException {
-        // When.
-        RestNode metaStorageNode = nodes.get(0);
-
-        String initClusterBody = "{\n"
-                + "    \"metaStorageNodes\": [\n"
-                + "        \"" + metaStorageNode.name() + "\"\n"
-                + "    ],\n"
-                + "    \"cmgNodes\": [],\n"
-                + "    \"clusterName\": \"cluster\",\n"
-                + "    \"clusterConfiguration\": \"{"
-                + "         security.enabled:true"
-                + "     }\"\n"
-                + "  }";
-
-        initCluster(metaStorageNode.httpAddress(), initClusterBody);
+    public void defaultUser(TestInfo testInfo) throws Exception {
+        username = null;
+        password = null;
+        super.setup(testInfo);
 
-        // Then.
         // Authentication is enabled.
-        for (RestNode node : nodes) {
-            assertTrue(waitForCondition(() -> 
isRestNotAvailable(node.httpAddress(), "", ""),
+        Set<IgniteImpl> nodes = 
cluster.runningNodes().collect(Collectors.toSet());
+        for (IgniteImpl node : nodes) {
+            assertTrue(waitForCondition(() -> 
isRestNotAvailable(node.restHttpAddress(), "", ""),
                     Duration.ofSeconds(5).toMillis()));
         }
 
         // REST is available with valid credentials
-        for (RestNode node : nodes) {
-            assertTrue(isRestAvailable(node.httpAddress(), "ignite", 
"ignite"));
+        for (IgniteImpl node : nodes) {
+            assertTrue(isRestAvailable(node.restHttpAddress(), "ignite", 
"ignite"));
         }
     }
 
     @Test
-    public void changeCredentials(TestInfo testInfo) throws 
InterruptedException, IOException {
-        // When.
-        RestNode metaStorageNode = nodes.get(0);
-
-        String initClusterBody = "{\n"
-                + "    \"metaStorageNodes\": [\n"
-                + "        \"" + metaStorageNode.name() + "\"\n"
-                + "    ],\n"
-                + "    \"cmgNodes\": [],\n"
-                + "    \"clusterName\": \"cluster\",\n"
-                + "    \"clusterConfiguration\": \"{"
-                + "         security.enabled:true, "
-                + "         
security.authentication.providers:[{name:basic,password:password,type:basic,username:admin}]"
-                + "     }\"\n"
-                + "  }";
-
-        initCluster(metaStorageNode.httpAddress(), initClusterBody);
+    public void changeCredentials(TestInfo testInfo) throws Exception {
+        super.setup(testInfo);
+        Set<IgniteImpl> nodes = 
cluster.runningNodes().collect(Collectors.toSet());
 
         // Then.
         // Authentication is enabled.
-        for (RestNode node : nodes) {
-            assertTrue(waitForCondition(() -> 
isRestNotAvailable(node.httpAddress(), "", ""),
+        for (IgniteImpl node : nodes) {
+            assertTrue(waitForCondition(() -> 
isRestNotAvailable(node.restHttpAddress(), "", ""),
                     Duration.ofSeconds(5).toMillis()));
         }
 
         // REST is available with valid credentials
-        for (RestNode node : nodes) {
-            assertTrue(isRestAvailable(node.httpAddress(), "admin", 
"password"));
+        for (IgniteImpl node : nodes) {
+            assertTrue(isRestAvailable(node.restHttpAddress(), "admin", 
"password"));
         }
 
         // REST is not available with invalid credentials
-        for (RestNode node : nodes) {
-            assertFalse(isRestAvailable(node.httpAddress(), "admin", 
"wrong-password"));
+        for (IgniteImpl node : nodes) {
+            assertFalse(isRestAvailable(node.restHttpAddress(), "admin", 
"wrong-password"));
         }
 
         // Change credentials.
-        String updateRestAuthConfigBody = "{\n"
-                + "    \"security\": {\n"
-                + "        \"enabled\": true,\n"
-                + "        \"authentication\": {\n"
-                + "            \"providers\": [\n"
-                + "                {\n"
-                + "                    \"name\": \"basic\",\n"
-                + "                    \"type\": \"basic\",\n"
-                + "                    \"username\": \"admin\",\n"
-                + "                    \"password\": \"new-password\"\n"
-                + "                }\n"
-                + "            ]\n"
-                + "        }\n"
-                + "    }\n"
-                + "}";
-
-        updateClusterConfiguration(metaStorageNode.httpAddress(), "admin", 
"password", updateRestAuthConfigBody);
+        String updateRestAuthConfigBody = 
"security.authentication.providers.default.users.admin.password=new-password";
+
+        updateClusterConfiguration(node(0).restHttpAddress(), "admin", 
"password", updateRestAuthConfigBody);
 
         // REST is not available with old credentials
-        for (RestNode node : nodes) {
-            assertTrue(waitForCondition(() -> 
isRestNotAvailable(node.httpAddress(), "admin", "password"),
+        for (IgniteImpl node : nodes) {
+            assertTrue(waitForCondition(() -> 
isRestNotAvailable(node.restHttpAddress(), "admin", "password"),
                     Duration.ofSeconds(5).toMillis()));
         }
 
         // REST is available with new credentials
-        for (RestNode node : nodes) {
-            assertTrue(isRestAvailable(node.httpAddress(), "admin", 
"new-password"));
+        for (IgniteImpl node : nodes) {
+            assertTrue(isRestAvailable(node.restHttpAddress(), "admin", 
"new-password"));
         }
     }
 
     @Test
-    public void enableAuthenticationAndRestartNode(TestInfo testInfo) throws 
InterruptedException, IOException {
-        // When.
-        RestNode metaStorageNode = nodes.get(0);
-
-        String initClusterBody = "{\n"
-                + "    \"metaStorageNodes\": [\n"
-                + "        \"" + metaStorageNode.name() + "\"\n"
-                + "    ],\n"
-                + "    \"cmgNodes\": [],\n"
-                + "    \"clusterName\": \"cluster\",\n"
-                + "    \"clusterConfiguration\": \"{"
-                + "         security.enabled:true, "
-                + "         
security.authentication.providers:[{name:basic,password:password,type:basic,username:admin}]}\"\n"
-                + "  }";
-
-        initCluster(metaStorageNode.httpAddress(), initClusterBody);
+    public void enableAuthenticationAndRestartNode(TestInfo testInfo) throws 
Exception {
+        super.setup(testInfo);
+        Set<IgniteImpl> nodes = 
cluster.runningNodes().collect(Collectors.toSet());
 
         // Then.
         // Authentication is enabled.
-        for (RestNode node : nodes) {
-            assertTrue(waitForCondition(() -> 
isRestNotAvailable(node.httpAddress(), "", ""),
+        for (IgniteImpl node : nodes) {
+            assertTrue(waitForCondition(() -> 
isRestNotAvailable(node.restHttpAddress(), "", ""),
                     Duration.ofSeconds(5).toMillis()));
         }
 
         // REST is available with valid credentials
-        for (RestNode node : nodes) {
-            assertTrue(isRestAvailable(node.httpAddress(), "admin", 
"password"));
+        for (IgniteImpl node : nodes) {
+            assertTrue(isRestAvailable(node.restHttpAddress(), "admin", 
"password"));
         }
 
         // REST is not available with invalid credentials
-        for (RestNode node : nodes) {
-            assertFalse(isRestAvailable(node.httpAddress(), "admin", 
"wrong-password"));
+        for (IgniteImpl node : nodes) {
+            assertFalse(isRestAvailable(node.restHttpAddress(), "admin", 
"wrong-password"));
         }
 
         // Restart a node.
-        RestNode nodeToRestart = nodes.get(2);
-        nodeToRestart.restart();
-        waitForAllNodesStarted(Collections.singletonList(nodeToRestart));
+        restartNode(2);
+
+        nodes = cluster.runningNodes().collect(Collectors.toSet());
 
         // REST is available with valid credentials
-        for (RestNode node : nodes) {
-            assertTrue(isRestAvailable(node.httpAddress(), "admin", 
"password"));
+        for (IgniteImpl node : nodes) {
+            assertTrue(isRestAvailable(node.restHttpAddress(), "admin", 
"password"));
         }
 
         // REST is not available with invalid credentials
-        for (RestNode node : nodes) {
-            assertFalse(isRestAvailable(node.httpAddress(), "admin", 
"wrong-password"));
+        for (IgniteImpl node : nodes) {
+            assertFalse(isRestAvailable(node.restHttpAddress(), "admin", 
"wrong-password"));
         }
     }
 
-    private void initCluster(String baseUrl, String initClusterBody) throws 
IOException, InterruptedException {
-        URI clusterInitUri = URI.create(baseUrl + 
"/management/v1/cluster/init");
-        HttpRequest initRequest = HttpRequest.newBuilder(clusterInitUri)
-                .header("content-type", "application/json")
-                .POST(BodyPublishers.ofString(initClusterBody))
-                .build();
-
-        assertThat(sendRequest(client, initRequest), hasStatusCode(200));
-
-        waitForAllNodesStarted(nodes);
-    }
-
-    private void updateClusterConfiguration(String baseUrl, String username, 
String password, String configToApply) {
-        URI updateClusterConfigUri = URI.create(baseUrl + 
"/management/v1/configuration/cluster/");
+    private void updateClusterConfiguration(NetworkAddress address, String 
username, String password, String configToApply) {
+        URI updateClusterConfigUri = URI.create("http://"; + hostUrl(address) + 
"/management/v1/configuration/cluster/");
         HttpRequest updateClusterConfigRequest = 
HttpRequest.newBuilder(updateClusterConfigUri)
                 .header("content-type", "text/plain")
                 .header("Authorization", basicAuthenticationHeader(username, 
password))
@@ -278,12 +199,12 @@ public class ItAuthenticationTest extends 
BaseIgniteAbstractTest {
         assertThat(sendRequest(client, updateClusterConfigRequest), 
hasStatusCode(200));
     }
 
-    private boolean isRestNotAvailable(String baseUrl, String username, String 
password) {
+    private boolean isRestNotAvailable(NetworkAddress baseUrl, String 
username, String password) {
         return !isRestAvailable(baseUrl, username, password);
     }
 
-    private boolean isRestAvailable(String baseUrl, String username, String 
password) {
-        URI clusterConfigUri = URI.create(baseUrl + 
"/management/v1/configuration/cluster/");
+    private boolean isRestAvailable(NetworkAddress address, String username, 
String password) {
+        URI clusterConfigUri = URI.create("http://"; + hostUrl(address) + 
"/management/v1/configuration/cluster/");
         HttpRequest clusterConfigRequest = 
HttpRequest.newBuilder(clusterConfigUri)
                 .header("Authorization", basicAuthenticationHeader(username, 
password))
                 .build();
@@ -294,7 +215,16 @@ public class ItAuthenticationTest extends 
BaseIgniteAbstractTest {
         } else if (code == 401) {
             return false;
         } else {
-            throw new IllegalStateException("Unexpected response code: " + 
code +  ", body: " + response.body());
+            throw new IllegalStateException("Unexpected response code: " + 
code + ", body: " + response.body());
+        }
+    }
+
+    private static String hostUrl(NetworkAddress networkAddress) {
+        try {
+            InetAddress host = InetAddress.getByName(networkAddress.host());
+            return host.getHostAddress() + ":" + networkAddress.port();
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
         }
     }
 
@@ -306,17 +236,6 @@ public class ItAuthenticationTest extends 
BaseIgniteAbstractTest {
         }
     }
 
-    @AfterEach
-    void tearDown() {
-        nodes.stream().parallel().forEach(RestNode::stop);
-    }
-
-    private static void waitForAllNodesStarted(List<RestNode> nodes) {
-        nodes.stream()
-                .map(RestNode::igniteNodeFuture)
-                .forEach(future -> assertThat(future, 
willCompleteSuccessfully()));
-    }
-
     private static String basicAuthenticationHeader(String username, String 
password) {
         String valueToEncode = username + ":" + password;
         return "Basic " + 
Base64.getEncoder().encodeToString(valueToEncode.getBytes());
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java
index 575d9cb772..f93581611d 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformTestNodeRunner.java
@@ -715,8 +715,9 @@ public class PlatformTestNodeRunner {
                                     if (enable) {
                                         
change.changeProviders().create("basic", authenticationProviderChange -> {
                                             
authenticationProviderChange.convert(BasicAuthenticationProviderChange.class)
-                                                    .changeUsername("user-1")
-                                                    
.changePassword("password-1");
+                                                    .changeUsers(users ->
+                                                            
users.create("user-1", user -> user.changePassword("password-1"))
+                                                    );
                                         });
                                     }
                                 }
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationProviderEqualityVerifier.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationProviderEqualityVerifier.java
index 8d8673d5e3..59f1a316ea 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationProviderEqualityVerifier.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticationProviderEqualityVerifier.java
@@ -17,7 +17,10 @@
 
 package org.apache.ignite.internal.security.authentication;
 
+import java.util.Objects;
+import org.apache.ignite.configuration.NamedListView;
 import 
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderView;
+import org.apache.ignite.internal.security.authentication.basic.BasicUserView;
 import 
org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderView;
 import org.jetbrains.annotations.Nullable;
 
@@ -61,6 +64,23 @@ public class AuthenticationProviderEqualityVerifier {
     }
 
     private static boolean areEqual(BasicAuthenticationProviderView o1, 
BasicAuthenticationProviderView o2) {
-        return o1.username().equals(o2.username()) && 
o1.password().equals(o2.password());
+        NamedListView<? extends BasicUserView> users1 = o1.users();
+        NamedListView<? extends BasicUserView> users2 = o2.users();
+        if (users1.size() != users2.size()) {
+            return false;
+        }
+
+        for (BasicUserView basicUser1View : users1) {
+            BasicUserView basicUser2View = 
users2.get(basicUser1View.username());
+            if (basicUser2View == null) {
+                return false;
+            }
+            if (!Objects.equals(basicUser1View.username(), 
basicUser2View.username())
+                    || !Objects.equals(basicUser1View.password(), 
basicUser2View.password())) {
+                return false;
+            }
+        }
+
+        return true;
     }
 }
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticatorFactory.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticatorFactory.java
index 441e703cfa..d9da4fc7b8 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticatorFactory.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/AuthenticatorFactory.java
@@ -17,8 +17,10 @@
 
 package org.apache.ignite.internal.security.authentication;
 
+import java.util.stream.Collectors;
 import 
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderView;
 import 
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticator;
+import org.apache.ignite.internal.security.authentication.basic.BasicUser;
 import 
org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderView;
 import org.apache.ignite.security.AuthenticationType;
 
@@ -28,7 +30,11 @@ class AuthenticatorFactory {
         AuthenticationType type = AuthenticationType.parse(view.type());
         if (type == AuthenticationType.BASIC) {
             BasicAuthenticationProviderView basicAuthProviderView = 
(BasicAuthenticationProviderView) view;
-            return new BasicAuthenticator(view.name(), 
basicAuthProviderView.username(), basicAuthProviderView.password());
+            return new BasicAuthenticator(
+                    view.name(),
+                    basicAuthProviderView.users().stream().map(basicUserView 
-> new BasicUser(basicUserView.username(),
+                    basicUserView.password())).collect(Collectors.toList())
+            );
         } else {
             throw new IllegalArgumentException("Unexpected authentication 
type: " + type);
         }
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModule.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModule.java
index 31c5800b96..3b4a4cb7fc 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModule.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModule.java
@@ -28,7 +28,7 @@ import 
org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.validation.Validator;
 import 
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderChange;
 import 
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderConfigurationSchema;
-import 
org.apache.ignite.internal.security.authentication.configuration.validator.AuthenticationProvidersValidatorImpl;
+import 
org.apache.ignite.internal.security.authentication.validator.AuthenticationProvidersValidatorImpl;
 import org.apache.ignite.internal.security.configuration.SecurityConfiguration;
 
 /**
@@ -36,7 +36,7 @@ import 
org.apache.ignite.internal.security.configuration.SecurityConfiguration;
  */
 @AutoService(ConfigurationModule.class)
 public class SecurityConfigurationModule implements ConfigurationModule {
-    private static final String DEFAULT_PROVIDER_NAME = "default";
+    public static final String DEFAULT_PROVIDER_NAME = "default";
 
     private static final String DEFAULT_USERNAME = "ignite";
 
@@ -66,11 +66,12 @@ public class SecurityConfigurationModule implements 
ConfigurationModule {
     public void patchConfigurationWithDynamicDefaults(SuperRootChange 
rootChange) {
         
rootChange.changeRoot(SecurityConfiguration.KEY).changeAuthentication(authenticationChange
 -> {
             if (authenticationChange.changeProviders().size() == 0) {
-                
authenticationChange.changeProviders().create(DEFAULT_PROVIDER_NAME, change -> {
-                    change.convert(BasicAuthenticationProviderChange.class)
-                            .changeUsername(DEFAULT_USERNAME)
-                            .changePassword(DEFAULT_PASSWORD);
-                });
+                
authenticationChange.changeProviders().create(DEFAULT_PROVIDER_NAME, change ->
+                        change.convert(BasicAuthenticationProviderChange.class)
+                                .changeUsers(users -> 
users.create(DEFAULT_USERNAME, user ->
+                                        user.changePassword(DEFAULT_PASSWORD))
+                                )
+                );
             }
         });
     }
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java
index de0f410d7c..762509754a 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java
@@ -17,23 +17,13 @@
 
 package org.apache.ignite.internal.security.authentication.basic;
 
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
-import org.apache.ignite.configuration.annotation.Secret;
-import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.configuration.validation.NotBlank;
 import 
org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderConfigurationSchema;
 
 /** Basic authentication configuration. */
 
@PolymorphicConfigInstance(AuthenticationProviderConfigurationSchema.TYPE_BASIC)
 public class BasicAuthenticationProviderConfigurationSchema extends 
AuthenticationProviderConfigurationSchema {
-    /** Username. */
-    @NotBlank
-    @Value
-    public String username;
-
-    /** Password. */
-    @Secret
-    @NotBlank
-    @Value
-    public String password;
+    @NamedConfigValue(syntheticKeyName = "username")
+    public BasicUserConfigurationSchema users;
 }
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticator.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticator.java
index 9372867b61..f004b8d1ee 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticator.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticator.java
@@ -17,6 +17,11 @@
 
 package org.apache.ignite.internal.security.authentication.basic;
 
+import static java.util.function.Function.identity;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 import 
org.apache.ignite.internal.security.authentication.AuthenticationRequest;
 import org.apache.ignite.internal.security.authentication.Authenticator;
 import org.apache.ignite.internal.security.authentication.UserDetails;
@@ -26,23 +31,19 @@ import 
org.apache.ignite.security.exception.UnsupportedAuthenticationTypeExcepti
 
 /** Implementation of basic authenticator. */
 public class BasicAuthenticator implements Authenticator {
-    private final String authenticatorName;
-
-    private final String username;
+    private final String providerName;
 
-    private final String password;
+    private final Map<String, BasicUser> users;
 
     /**
      * Constructor.
      *
-     * @param authenticatorName Authenticator name.
-     * @param username Username.
-     * @param password Password.
+     * @param providerName Provider name.
+     * @param users List of registered users.
      */
-    public BasicAuthenticator(String authenticatorName, String username, 
String password) {
-        this.authenticatorName = authenticatorName;
-        this.username = username;
-        this.password = password;
+    public BasicAuthenticator(String providerName, List<BasicUser> users) {
+        this.providerName = providerName;
+        this.users = users.stream().collect(Collectors.toMap(BasicUser::name, 
identity()));
     }
 
     @Override
@@ -53,10 +54,16 @@ public class BasicAuthenticator implements Authenticator {
             );
         }
 
-        if (username.equals(authenticationRequest.getIdentity()) && 
password.equals(authenticationRequest.getSecret())) {
-            return new UserDetails(username, authenticatorName);
-        } else {
-            throw new InvalidCredentialsException("Invalid credentials");
+        Object requestUsername = authenticationRequest.getIdentity();
+        Object requestPassword = authenticationRequest.getSecret();
+
+        BasicUser basicUser = users.get(requestUsername);
+        if (basicUser != null) {
+            if (basicUser.password().equals(requestPassword)) {
+                return new UserDetails(basicUser.name(), providerName);
+            }
         }
+
+        throw new InvalidCredentialsException("Invalid credentials");
     }
 }
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUser.java
similarity index 53%
copy from 
modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java
copy to 
modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUser.java
index de0f410d7c..2b8b0900a5 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUser.java
@@ -17,23 +17,24 @@
 
 package org.apache.ignite.internal.security.authentication.basic;
 
-import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
-import org.apache.ignite.configuration.annotation.Secret;
-import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.configuration.validation.NotBlank;
-import 
org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderConfigurationSchema;
+/**
+ * Data class for user information.
+ */
+public class BasicUser {
+    private final String name;
+
+    private final String password;
+
+    public BasicUser(String name, String password) {
+        this.name = name;
+        this.password = password;
+    }
 
-/** Basic authentication configuration. */
-@PolymorphicConfigInstance(AuthenticationProviderConfigurationSchema.TYPE_BASIC)
-public class BasicAuthenticationProviderConfigurationSchema extends 
AuthenticationProviderConfigurationSchema {
-    /** Username. */
-    @NotBlank
-    @Value
-    public String username;
+    public String name() {
+        return name;
+    }
 
-    /** Password. */
-    @Secret
-    @NotBlank
-    @Value
-    public String password;
+    public String password() {
+        return password;
+    }
 }
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUserConfigurationSchema.java
similarity index 72%
copy from 
modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java
copy to 
modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUserConfigurationSchema.java
index de0f410d7c..69b18faecf 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchema.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/basic/BasicUserConfigurationSchema.java
@@ -17,18 +17,19 @@
 
 package org.apache.ignite.internal.security.authentication.basic;
 
-import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.InjectedName;
 import org.apache.ignite.configuration.annotation.Secret;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.configuration.validation.NotBlank;
-import 
org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderConfigurationSchema;
 
-/** Basic authentication configuration. */
-@PolymorphicConfigInstance(AuthenticationProviderConfigurationSchema.TYPE_BASIC)
-public class BasicAuthenticationProviderConfigurationSchema extends 
AuthenticationProviderConfigurationSchema {
+/**
+ * Basic user configuration schema.
+ */
+@Config
+public class BasicUserConfigurationSchema {
     /** Username. */
-    @NotBlank
-    @Value
+    @InjectedName
     public String username;
 
     /** Password. */
diff --git 
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/configuration/validator/AuthenticationProvidersValidatorImpl.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/validator/AuthenticationProvidersValidatorImpl.java
similarity index 52%
rename from 
modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/configuration/validator/AuthenticationProvidersValidatorImpl.java
rename to 
modules/security/src/main/java/org/apache/ignite/internal/security/authentication/validator/AuthenticationProvidersValidatorImpl.java
index 84f6ccff3c..b6de765161 100644
--- 
a/modules/security-api/src/main/java/org/apache/ignite/internal/security/authentication/configuration/validator/AuthenticationProvidersValidatorImpl.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/security/authentication/validator/AuthenticationProvidersValidatorImpl.java
@@ -15,13 +15,18 @@
  * limitations under the License.
  */
 
-package 
org.apache.ignite.internal.security.authentication.configuration.validator;
+package org.apache.ignite.internal.security.authentication.validator;
+
+import static 
org.apache.ignite.internal.security.authentication.SecurityConfigurationModule.DEFAULT_PROVIDER_NAME;
 
 import org.apache.ignite.configuration.NamedListView;
 import org.apache.ignite.configuration.validation.ValidationContext;
 import org.apache.ignite.configuration.validation.ValidationIssue;
 import org.apache.ignite.configuration.validation.Validator;
+import 
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderConfiguration;
+import 
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderView;
 import 
org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderView;
+import 
org.apache.ignite.internal.security.authentication.configuration.validator.AuthenticationProvidersValidator;
 import org.apache.ignite.internal.security.configuration.SecurityConfiguration;
 
 /**
@@ -39,8 +44,36 @@ public class AuthenticationProvidersValidatorImpl implements
         boolean enabled = ctx.getNewRoot(SecurityConfiguration.KEY).enabled();
         NamedListView<? extends AuthenticationProviderView> view = 
ctx.getNewValue();
 
-        if (enabled && view.size() == 0) {
-            ctx.addIssue(new ValidationIssue(ctx.currentKey(), "Providers must 
be present, if security is enabled"));
+        BasicAuthenticationProviderView basicProvider = 
(BasicAuthenticationProviderView) view.get(DEFAULT_PROVIDER_NAME);
+        if (basicProvider == null) {
+            ctx.addIssue(new ValidationIssue(ctx.currentKey(), "Default 
provider " + DEFAULT_PROVIDER_NAME + " is not removable."));
+            return;
+        }
+
+        if (!checkOnlyOneBasicProvider(ctx, view)) {
+            return;
+        }
+
+        if (enabled && view.size() == 1 && basicProvider.users().size() == 0) {
+            ctx.addIssue(new ValidationIssue(ctx.currentKey(), "Basic provider 
must have at least one user "
+                    + "in case when no other providers present."));
+        }
+    }
+
+    private static boolean checkOnlyOneBasicProvider(
+            ValidationContext<NamedListView<? extends 
AuthenticationProviderView>> ctx,
+            NamedListView<? extends AuthenticationProviderView> view
+    ) {
+        boolean basicAlreadyFound = false;
+        for (AuthenticationProviderView authenticationProviderView : view) {
+            if (authenticationProviderView instanceof 
BasicAuthenticationProviderConfiguration) {
+                if (basicAlreadyFound) {
+                    ctx.addIssue(new ValidationIssue(ctx.currentKey(), "Only 
one basic provider supported."));
+                    return false;
+                }
+                basicAlreadyFound = true;
+            }
         }
+        return true;
     }
 }
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 92b1e21c06..5a941be645 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
@@ -187,8 +187,9 @@ class AuthenticationManagerImplTest extends 
BaseIgniteAbstractTest {
                 securityConfiguration, change -> {
                     change.changeAuthentication().changeProviders(providers -> 
providers.update(PROVIDER, provider -> {
                         
provider.convert(BasicAuthenticationProviderChange.class)
-                                .changeUsername(USERNAME)
-                                .changePassword("new-password");
+                                .changeUsers(users ->
+                                        users.update(USERNAME, user -> 
user.changePassword("new-password"))
+                                );
                     }));
                 })
                 .value();
@@ -244,8 +245,9 @@ class AuthenticationManagerImplTest extends 
BaseIgniteAbstractTest {
                 securityConfiguration, change -> {
                     change.changeAuthentication().changeProviders(providers -> 
providers.create(PROVIDER, provider -> {
                         
provider.convert(BasicAuthenticationProviderChange.class)
-                                .changeUsername(USERNAME)
-                                .changePassword(PASSWORD);
+                                .changeUsers(users ->
+                                        users.create(USERNAME, user -> 
user.changePassword(PASSWORD))
+                                );
                     }));
                     change.changeEnabled(true);
                 })
diff --git 
a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationProvidersValidatorImplTest.java
 
b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationProvidersValidatorImplTest.java
index 522d793a0b..5f0fccea18 100644
--- 
a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationProvidersValidatorImplTest.java
+++ 
b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/AuthenticationProvidersValidatorImplTest.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.security.authentication;
 
 import static 
org.apache.ignite.internal.configuration.validation.TestValidationUtil.mockValidationContext;
 import static 
org.apache.ignite.internal.configuration.validation.TestValidationUtil.validate;
+import static 
org.apache.ignite.internal.security.authentication.SecurityConfigurationModule.DEFAULT_PROVIDER_NAME;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
@@ -30,7 +31,7 @@ import 
org.apache.ignite.internal.configuration.testframework.ConfigurationExten
 import 
org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
 import 
org.apache.ignite.internal.security.authentication.configuration.AuthenticationProviderView;
 import 
org.apache.ignite.internal.security.authentication.configuration.validator.AuthenticationProvidersValidator;
-import 
org.apache.ignite.internal.security.authentication.configuration.validator.AuthenticationProvidersValidatorImpl;
+import 
org.apache.ignite.internal.security.authentication.validator.AuthenticationProvidersValidatorImpl;
 import org.apache.ignite.internal.security.configuration.SecurityChange;
 import org.apache.ignite.internal.security.configuration.SecurityConfiguration;
 import org.apache.ignite.internal.security.configuration.SecurityView;
@@ -62,7 +63,7 @@ class AuthenticationProvidersValidatorImplTest extends 
BaseIgniteAbstractTest {
                 AuthenticationProvidersValidatorImpl.INSTANCE,
                 mock(AuthenticationProvidersValidator.class),
                 ctx,
-                "Providers must be present, if security is enabled"
+                "Default provider " + DEFAULT_PROVIDER_NAME + " is not 
removable"
         );
     }
 
diff --git 
a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModuleTest.java
 
b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModuleTest.java
index ec51859953..17b354270b 100644
--- 
a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModuleTest.java
+++ 
b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/SecurityConfigurationModuleTest.java
@@ -34,7 +34,7 @@ import 
org.apache.ignite.internal.configuration.SuperRootChangeImpl;
 import 
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderChange;
 import 
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderConfigurationSchema;
 import 
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderView;
-import 
org.apache.ignite.internal.security.authentication.configuration.validator.AuthenticationProvidersValidatorImpl;
+import 
org.apache.ignite.internal.security.authentication.validator.AuthenticationProvidersValidatorImpl;
 import org.apache.ignite.internal.security.configuration.SecurityConfiguration;
 import org.apache.ignite.internal.security.configuration.SecurityView;
 import org.junit.jupiter.api.AfterEach;
@@ -107,8 +107,8 @@ class SecurityConfigurationModuleTest {
                 .get(0);
 
         assertAll(
-                () -> assertThat(defaultProvider.username(), 
equalTo("ignite")),
-                () -> assertThat(defaultProvider.password(), equalTo("ignite"))
+                () -> assertThat(defaultProvider.users().get(0).username(), 
equalTo("ignite")),
+                () -> assertThat(defaultProvider.users().get(0).password(), 
equalTo("ignite"))
         );
     }
 
@@ -117,8 +117,7 @@ class SecurityConfigurationModuleTest {
         
rootChange.changeRoot(SecurityConfiguration.KEY).changeAuthentication(authenticationChange
 -> {
             authenticationChange.changeProviders().create("basic", change -> {
                 change.convert(BasicAuthenticationProviderChange.class)
-                        .changeUsername("admin")
-                        .changePassword("password");
+                        .changeUsers(users -> users.create("admin", user -> 
user.changePassword("password")));
             });
         });
 
@@ -133,8 +132,8 @@ class SecurityConfigurationModuleTest {
                 .get(0);
 
         assertAll(
-                () -> assertThat(defaultProvider.username(), equalTo("admin")),
-                () -> assertThat(defaultProvider.password(), 
equalTo("password"))
+                () -> assertThat(defaultProvider.users().get(0).username(), 
equalTo("admin")),
+                () -> assertThat(defaultProvider.users().get(0).password(), 
equalTo("password"))
         );
     }
 }
diff --git 
a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchemaTest.java
 
b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchemaTest.java
index 3a2aca2b78..78f616799b 100644
--- 
a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchemaTest.java
+++ 
b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticationProviderConfigurationSchemaTest.java
@@ -21,25 +21,25 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.lang.reflect.Field;
 import java.util.Arrays;
+import org.apache.ignite.configuration.annotation.InjectedName;
 import org.apache.ignite.configuration.annotation.Secret;
 import org.apache.ignite.configuration.validation.NotBlank;
-import 
org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderConfigurationSchema;
 import org.junit.jupiter.api.Test;
 
 class BasicAuthenticationProviderConfigurationSchemaTest {
     @Test
-    public void usernameIsNotBlank() {
-        Field username = 
Arrays.stream(BasicAuthenticationProviderConfigurationSchema.class.getDeclaredFields())
+    public void usernameIsInjected() {
+        Field username = 
Arrays.stream(BasicUserConfigurationSchema.class.getDeclaredFields())
                 .filter(it -> it.getName().equals("username"))
                 .findFirst()
                 .orElseThrow();
 
-        assertTrue(username.isAnnotationPresent(NotBlank.class));
+        assertTrue(username.isAnnotationPresent(InjectedName.class));
     }
 
     @Test
     public void passwordIsSecretAndNotBlank() {
-        Field password = 
Arrays.stream(BasicAuthenticationProviderConfigurationSchema.class.getDeclaredFields())
+        Field password = 
Arrays.stream(BasicUserConfigurationSchema.class.getDeclaredFields())
                 .filter(it -> it.getName().equals("password"))
                 .findFirst()
                 .orElseThrow();
diff --git 
a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticatorTest.java
 
b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticatorTest.java
index 7fe9cce0bd..2f4bce91c5 100644
--- 
a/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticatorTest.java
+++ 
b/modules/security/src/test/java/org/apache/ignite/internal/security/authentication/basic/BasicAuthenticatorTest.java
@@ -20,6 +20,7 @@ package 
org.apache.ignite.internal.security.authentication.basic;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
+import java.util.List;
 import org.apache.ignite.internal.security.authentication.AnonymousRequest;
 import org.apache.ignite.internal.security.authentication.UserDetails;
 import 
org.apache.ignite.internal.security.authentication.UsernamePasswordRequest;
@@ -28,7 +29,10 @@ import 
org.apache.ignite.security.exception.UnsupportedAuthenticationTypeExcepti
 import org.junit.jupiter.api.Test;
 
 class BasicAuthenticatorTest {
-    private final BasicAuthenticator authenticator = new 
BasicAuthenticator("basic", "admin", "password");
+    private final BasicAuthenticator authenticator = new BasicAuthenticator(
+            "basic",
+            List.of(new BasicUser("admin", "password"))
+    );
 
     @Test
     void authenticate() {

Reply via email to