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

jinwoo pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 4e93d2c482 Testcases for Server-Only TLS with Application-Layer 
Authentication (#7987)
4e93d2c482 is described below

commit 4e93d2c48296c01cc45e15ca328fd78ead8d9d41
Author: Jinwoo Hwang <[email protected]>
AuthorDate: Wed Mar 4 07:29:24 2026 -0500

    Testcases for Server-Only TLS with Application-Layer Authentication (#7987)
---
 .../ssl/P2PServerOnlyTLSWithAuthDUnitTest.java     | 382 +++++++++++++++
 .../cache/ssl/ServerOnlyTLSWithAuthDUnitTest.java  | 513 +++++++++++++++++++++
 .../ssl/ServerOnlyTLSWithAuthNegativeTest.java     | 481 +++++++++++++++++++
 .../geode/security/templates/TokenAuthInit.java    |  68 +++
 .../test/junit/rules/ServerOnlyTLSTestFixture.java | 253 ++++++++++
 5 files changed, 1697 insertions(+)

diff --git 
a/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/P2PServerOnlyTLSWithAuthDUnitTest.java
 
b/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/P2PServerOnlyTLSWithAuthDUnitTest.java
new file mode 100644
index 0000000000..0424c6bd79
--- /dev/null
+++ 
b/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/P2PServerOnlyTLSWithAuthDUnitTest.java
@@ -0,0 +1,382 @@
+/*
+ * 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.geode.cache.ssl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.security.GemFireSecurityException;
+import org.apache.geode.test.dunit.IgnoredException;
+import org.apache.geode.test.dunit.rules.ClusterStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.junit.categories.SecurityTest;
+import org.apache.geode.test.junit.rules.ServerOnlyTLSTestFixture;
+
+/**
+ * Distributed tests for Peer-to-Peer (P2P) Cache Topology using Server-only 
TLS with
+ * Application-Layer Authentication (Approach 3).
+ *
+ * <p>
+ * This test demonstrates that in a P2P cache configuration (where all members 
are peers, no
+ * client/server distinction), Approach 3 works correctly:
+ * <ul>
+ * <li><strong>TLS Encryption:</strong> All peer-to-peer connections use TLS 
for transport
+ * encryption</li>
+ * <li><strong>No Certificate Authentication:</strong> Peers do NOT exchange 
certificates during
+ * TLS handshake (ssl-require-authentication=false)</li>
+ * <li><strong>Application-Layer Authentication:</strong> Peers authenticate 
using
+ * username/password via SecurityManager</li>
+ * <li><strong>Authorization:</strong> SecurityManager enforces CLUSTER:MANAGE 
permission for peer
+ * join</li>
+ * </ul>
+ *
+ * <p>
+ * <strong>Key Difference from Client/Server:</strong> In P2P topology, all 
members are equal peers
+ * that communicate directly. Each peer presents a server certificate for TLS 
encryption, but
+ * authentication happens at the application layer using credentials validated 
by SecurityManager.
+ *
+ * <p>
+ * This approach solves the public CA clientAuth EKU sunset problem for P2P 
topologies by:
+ * <ol>
+ * <li>Eliminating the need for client certificates entirely</li>
+ * <li>Maintaining full TLS encryption for all transport</li>
+ * <li>Using existing authentication infrastructure (LDAP, database, 
tokens)</li>
+ * </ol>
+ *
+ * @see ServerOnlyTLSWithAuthDUnitTest for client/server topology tests
+ */
+@Category({SecurityTest.class})
+public class P2PServerOnlyTLSWithAuthDUnitTest {
+
+  private static final String REGION_NAME = "testRegion";
+
+  @Rule
+  public ClusterStartupRule cluster = new ClusterStartupRule();
+
+  private ServerOnlyTLSTestFixture fixture;
+
+  @Before
+  public void setUp() throws Exception {
+    fixture = new ServerOnlyTLSTestFixture();
+
+    // Add ignored exceptions for SSL-related cleanup warnings
+    IgnoredException.addIgnoredException("javax.net.ssl.SSLException");
+    IgnoredException.addIgnoredException("java.io.IOException");
+    IgnoredException.addIgnoredException("Authentication failed");
+    IgnoredException.addIgnoredException("Security check failed");
+  }
+
+  /**
+   * Test basic P2P cluster formation with server-only TLS and 
application-layer authentication.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Locator and servers present TLS certificates (server cert from public 
or private CA)</li>
+   * <li>Peers do NOT present client certificates during TLS handshake</li>
+   * <li>All peers authenticate using username/password</li>
+   * <li>SecurityManager validates credentials and requires CLUSTER:MANAGE 
permission</li>
+   * <li>Cluster forms successfully with encrypted peer-to-peer 
communication</li>
+   * </ul>
+   */
+  @Test
+  public void testP2PClusterFormationWithServerOnlyTLSAndAppAuth() throws 
Exception {
+    // Create certificate stores using fixture
+    // All peers use the same certificate for TLS (server cert)
+    CertStores clusterStores = fixture.createClusterStores();
+
+    // Configure locator with:
+    // - Server-only TLS (ssl-require-authentication=false)
+    // - Security manager for application-layer authentication
+    // - Peer authentication credentials
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+
+    // Start locator - it will authenticate itself when joining
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Configure first server with same setup
+    Properties server1Props = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(server1Props);
+    fixture.addPeerAuthProperties(server1Props, "cluster", "cluster");
+
+    // Start first server - it joins via application-layer auth, not 
certificate auth
+    MemberVM server1 = cluster.startServerVM(1, server1Props, locatorPort);
+
+    // Verify server1 successfully joined the cluster
+    server1.invoke(() -> {
+      assertThat(ClusterStartupRule.getCache()).isNotNull();
+      
assertThat(ClusterStartupRule.getCache().getDistributedSystem().getAllOtherMembers())
+          .hasSize(1); // locator
+    });
+
+    // Configure second server with same setup
+    Properties server2Props = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(server2Props);
+    fixture.addPeerAuthProperties(server2Props, "cluster", "cluster");
+
+    // Start second server
+    MemberVM server2 = cluster.startServerVM(2, server2Props, locatorPort);
+
+    // Verify server2 successfully joined and sees all peers
+    server2.invoke(() -> {
+      assertThat(ClusterStartupRule.getCache()).isNotNull();
+      
assertThat(ClusterStartupRule.getCache().getDistributedSystem().getAllOtherMembers())
+          .hasSize(2); // locator + server1
+    });
+
+    // Verify all peers see each other (peer-to-peer mesh formed)
+    server1.invoke(() -> {
+      
assertThat(ClusterStartupRule.getCache().getDistributedSystem().getAllOtherMembers())
+          .hasSize(2); // locator + server2
+    });
+  }
+
+  /**
+   * Test P2P data replication across encrypted peer connections without 
certificate authentication.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Data replicates across peers using TLS-encrypted connections</li>
+   * <li>No certificate authentication is used (application credentials 
only)</li>
+   * <li>All operations succeed over server-only TLS</li>
+   * </ul>
+   */
+  @Test
+  public void testP2PDataReplicationOverServerOnlyTLS() throws Exception {
+    CertStores clusterStores = fixture.createClusterStores();
+
+    // Configure and start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server1 with replicated region
+    Properties server1Props = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(server1Props);
+    fixture.addPeerAuthProperties(server1Props, "cluster", "cluster");
+    MemberVM server1 = cluster.startServerVM(1, server1Props, locatorPort);
+
+    server1.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Start server2 with same replicated region
+    Properties server2Props = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(server2Props);
+    fixture.addPeerAuthProperties(server2Props, "cluster", "cluster");
+    MemberVM server2 = cluster.startServerVM(2, server2Props, locatorPort);
+
+    server2.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Put data on server1
+    server1.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .getRegion(REGION_NAME)
+          .put("key1", "value1");
+    });
+
+    // Verify data replicated to server2 over TLS-encrypted peer connection
+    server2.invoke(() -> {
+      Object value = ClusterStartupRule.getCache()
+          .getRegion(REGION_NAME)
+          .get("key1");
+      assertThat(value).isEqualTo("value1");
+    });
+
+    // Put data on server2
+    server2.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .getRegion(REGION_NAME)
+          .put("key2", "value2");
+    });
+
+    // Verify data replicated to server1
+    server1.invoke(() -> {
+      Object value = ClusterStartupRule.getCache()
+          .getRegion(REGION_NAME)
+          .get("key2");
+      assertThat(value).isEqualTo("value2");
+    });
+  }
+
+  /**
+   * Test that peer with invalid credentials cannot join P2P cluster.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>TLS handshake succeeds (server cert validation)</li>
+   * <li>Application-layer authentication fails with wrong password</li>
+   * <li>Peer is rejected and cannot join cluster</li>
+   * </ul>
+   */
+  @Test
+  public void testP2PPeerRejectedWithInvalidCredentials() throws Exception {
+    // Add ignored exception for authentication failure messages
+    IgnoredException.addIgnoredException("Authentication FAILED");
+    CertStores clusterStores = fixture.createClusterStores();
+
+    // Configure and start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Try to start server with INVALID credentials
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "wrongPassword"); // 
INVALID
+
+    // Server should fail to join due to authentication failure
+    // Note: Root cause is SecurityException, not GemFireSecurityException
+    assertThatThrownBy(() -> cluster.startServerVM(1, serverProps, 
locatorPort))
+        .hasRootCauseInstanceOf(SecurityException.class)
+        .hasStackTraceContaining("invalid username/password");
+  }
+
+  /**
+   * Test that peer without CLUSTER:MANAGE permission cannot join P2P cluster.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>TLS handshake succeeds</li>
+   * <li>Application-layer authentication succeeds (valid 
username/password)</li>
+   * <li>Authorization fails (lacks CLUSTER:MANAGE permission)</li>
+   * <li>Peer is rejected and cannot join cluster</li>
+   * </ul>
+   */
+  @Test
+  public void testP2PPeerRejectedWithoutClusterManagePermission() throws 
Exception {
+    CertStores clusterStores = fixture.createClusterStores();
+
+    // Configure and start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Try to start server with user that has valid credentials but no 
CLUSTER:MANAGE
+    // SimpleSecurityManager allows authentication when username == password
+    // but "data" user does NOT have CLUSTER:MANAGE permission
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "data", "data"); // Valid 
creds, insufficient perms
+
+    // Server should fail to join due to authorization failure
+    // Note: Root cause is SecurityException, not GemFireSecurityException
+    assertThatThrownBy(() -> cluster.startServerVM(1, serverProps, 
locatorPort))
+        .hasRootCauseInstanceOf(SecurityException.class)
+        .hasStackTraceContaining("not authorized for CLUSTER:MANAGE");
+  }
+
+  /**
+   * Test that peer with no credentials cannot join P2P cluster.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>TLS handshake succeeds (encryption established)</li>
+   * <li>Application-layer authentication fails (no credentials provided)</li>
+   * <li>Peer is rejected</li>
+   * </ul>
+   */
+  @Test
+  public void testP2PPeerRejectedWithNoCredentials() throws Exception {
+    CertStores clusterStores = fixture.createClusterStores();
+
+    // Configure and start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Try to start server WITHOUT any authentication credentials
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    // NO peer auth properties added - missing credentials
+
+    // Server should fail to join due to missing credentials
+    assertThatThrownBy(() -> cluster.startServerVM(1, serverProps, 
locatorPort))
+        .hasRootCauseInstanceOf(GemFireSecurityException.class);
+  }
+
+  /**
+   * Test multiple peers joining with different valid credentials.
+   *
+   * <p>
+   * Verifies that the cluster supports heterogeneous peer credentials as long 
as all have
+   * CLUSTER:MANAGE permission. This demonstrates flexibility in credential 
management where
+   * different services/teams can use different credentials.
+   */
+  @Test
+  public void testMultiplePeersWithDifferentCredentials() throws Exception {
+    CertStores clusterStores = fixture.createClusterStores();
+
+    // Start locator with "cluster" credentials
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server1 with "clusterManage" credentials
+    // SimpleSecurityManager grants CLUSTER:MANAGE to "cluster" and 
"clusterManage" users
+    Properties server1Props = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(server1Props);
+    fixture.addPeerAuthProperties(server1Props, "clusterManage", 
"clusterManage");
+    MemberVM server1 = cluster.startServerVM(1, server1Props, locatorPort);
+
+    // Start server2 with "cluster" credentials (same as locator)
+    Properties server2Props = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(server2Props);
+    fixture.addPeerAuthProperties(server2Props, "cluster", "cluster");
+    MemberVM server2 = cluster.startServerVM(2, server2Props, locatorPort);
+
+    // Verify all peers joined successfully
+    server1.invoke(() -> {
+      
assertThat(ClusterStartupRule.getCache().getDistributedSystem().getAllOtherMembers())
+          .hasSize(2); // locator + server2
+    });
+
+    server2.invoke(() -> {
+      
assertThat(ClusterStartupRule.getCache().getDistributedSystem().getAllOtherMembers())
+          .hasSize(2); // locator + server1
+    });
+  }
+}
diff --git 
a/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/ServerOnlyTLSWithAuthDUnitTest.java
 
b/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/ServerOnlyTLSWithAuthDUnitTest.java
new file mode 100644
index 0000000000..04305b0a0f
--- /dev/null
+++ 
b/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/ServerOnlyTLSWithAuthDUnitTest.java
@@ -0,0 +1,513 @@
+/*
+ * 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.geode.cache.ssl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.cache.client.ClientCache;
+import org.apache.geode.cache.client.ClientCacheFactory;
+import org.apache.geode.cache.client.ClientRegionShortcut;
+import org.apache.geode.examples.SimpleSecurityManager;
+import org.apache.geode.test.dunit.IgnoredException;
+import org.apache.geode.test.dunit.rules.ClientVM;
+import org.apache.geode.test.dunit.rules.ClusterStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.junit.categories.SecurityTest;
+import org.apache.geode.test.junit.rules.ServerOnlyTLSTestFixture;
+
+/**
+ * Distributed tests for Server-only TLS with Alternative Client 
Authentication.
+ *
+ * <p>
+ * These tests verify that:
+ * <ul>
+ * <li>Servers present certificates and clients verify them</li>
+ * <li>Clients do NOT present certificates 
(ssl-require-authentication=false)</li>
+ * <li>All transport is TLS-encrypted in both directions</li>
+ * <li>Clients authenticate using application-layer credentials 
(username/password or tokens)</li>
+ * <li>Authorization is enforced through SecurityManager</li>
+ * </ul>
+ */
+@Category({SecurityTest.class})
+public class ServerOnlyTLSWithAuthDUnitTest {
+
+  private static final String REGION_NAME = "testRegion";
+
+  @Rule
+  public ClusterStartupRule cluster = new ClusterStartupRule();
+
+  private ServerOnlyTLSTestFixture fixture;
+
+  @Before
+  public void setUp() throws Exception {
+    fixture = new ServerOnlyTLSTestFixture();
+
+    // Add ignored exceptions for SSL-related cleanup warnings
+    IgnoredException.addIgnoredException("javax.net.ssl.SSLException");
+    IgnoredException.addIgnoredException("java.io.IOException");
+  }
+
+  /**
+   * Test basic client connection with TLS transport encryption and 
username/password
+   * authentication.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Server presents certificate, client verifies it</li>
+   * <li>Client does NOT present certificate</li>
+   * <li>Client authenticates with valid username/password</li>
+   * <li>Transport is encrypted</li>
+   * </ul>
+   */
+  @Test
+  public void testBasicConnectionWithUsernamePassword() throws Exception {
+    // Create certificates and stores using fixture
+    // Note: Use createClusterStores() for both locator and server peer SSL 
communication
+    CertStores clusterStores = fixture.createClusterStores();
+    CertStores clientStores = fixture.createClientStores();
+
+    // Configure locator with server-only TLS (require-authentication=false) 
and security manager
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+
+    // Start locator
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Configure server with server-only TLS and security manager
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+
+    // Start server
+    MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+    // Create region on server
+    server.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Configure client with server-only TLS (truststore only, no keystore)
+    Properties clientSSLProps = clientStores.propertiesWith("all", false, 
false);
+
+    // Add authentication properties (username/password)
+    // SimpleSecurityManager accepts username when username == password
+    Properties clientAuthProps = fixture.createClientAuthProperties("data", 
"data");
+    clientSSLProps.putAll(clientAuthProps);
+
+    // Connect client
+    ClientVM client = cluster.startClientVM(2, c -> c
+        .withProperties(clientSSLProps)
+        .withLocatorConnection(locatorPort));
+
+    // Verify client can perform operations
+    client.invoke(() -> {
+      ClientCache clientCache = ClusterStartupRule.getClientCache();
+      assertThat(clientCache).isNotNull();
+
+      Region<Object, Object> region = clientCache
+          .createClientRegionFactory(ClientRegionShortcut.PROXY)
+          .create(REGION_NAME);
+
+      // Perform basic operations
+      region.put("key1", "value1");
+      Object value = region.get("key1");
+      assertThat(value).isEqualTo("value1");
+    });
+  }
+
+  /**
+   * Test multiple clients connecting with different credentials.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Multiple clients can connect simultaneously</li>
+   * <li>Each client has its own identity (username)</li>
+   * <li>All clients use TLS transport encryption</li>
+   * <li>None of the clients present certificates</li>
+   * </ul>
+   */
+  @Test
+  public void testMultipleClientsWithDifferentCredentials() throws Exception {
+    // Create certificates and stores
+    CertStores locatorStores = fixture.createLocatorStores();
+    CertStores clusterStores = fixture.createClusterStores();
+    CertStores client1Stores = fixture.createClientStores();
+    CertStores client2Stores = fixture.createClientStores();
+    CertStores client3Stores = fixture.createClientStores();
+
+    // Start locator
+    Properties locatorProps = locatorStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+    MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+    // Create region
+    server.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Connect client 1 with user "dataRead"
+    Properties client1Props = client1Stores.propertiesWith("all", false, 
false);
+    client1Props.putAll(fixture.createClientAuthProperties("data", "data"));
+    ClientVM client1 = cluster.startClientVM(2, c -> c
+        .withProperties(client1Props)
+        .withLocatorConnection(locatorPort));
+
+    // Connect client 2 with user "dataWrite"
+    Properties client2Props = client2Stores.propertiesWith("all", false, 
false);
+    client2Props.putAll(fixture.createClientAuthProperties("dataWrite", 
"dataWrite"));
+    ClientVM client2 = cluster.startClientVM(3, c -> c
+        .withProperties(client2Props)
+        .withLocatorConnection(locatorPort));
+
+    // Connect client 3 with user "dataManage"
+    Properties client3Props = client3Stores.propertiesWith("all", false, 
false);
+    client3Props.putAll(fixture.createClientAuthProperties("dataManage", 
"dataManage"));
+    ClientVM client3 = cluster.startClientVM(4, c -> c
+        .withProperties(client3Props)
+        .withLocatorConnection(locatorPort));
+
+    // Verify all clients can access region
+    for (ClientVM client : new ClientVM[] {client1, client2, client3}) {
+      client.invoke(() -> {
+        Region<Object, Object> region = ClusterStartupRule.getClientCache()
+            .createClientRegionFactory(ClientRegionShortcut.PROXY)
+            .create(REGION_NAME);
+        assertThat(region).isNotNull();
+      });
+    }
+  }
+
+  /**
+   * Test token-based authentication instead of username/password.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Clients can authenticate using bearer tokens</li>
+   * <li>Token authentication works with TLS transport</li>
+   * <li>No client certificates are required</li>
+   * </ul>
+   */
+  @Test
+  public void testTokenBasedAuthentication() throws Exception {
+    // Create certificates and stores
+    // Note: Locator and server must use same CertStores for peer SSL 
communication
+    CertStores clusterStores = fixture.createClusterStores();
+    CertStores clientStores = fixture.createClientStores();
+
+    // Start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+    MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+    // Create region
+    server.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Configure client with token authentication
+    Properties clientProps = clientStores.propertiesWith("all", false, false);
+    Properties tokenAuthProps = fixture.createClientTokenAuthProperties(
+        SimpleSecurityManager.VALID_TOKEN);
+    clientProps.putAll(tokenAuthProps);
+
+    // Connect client
+    ClientVM client = cluster.startClientVM(2, c -> c
+        .withProperties(clientProps)
+        .withLocatorConnection(locatorPort));
+
+    // Verify client can perform operations
+    client.invoke(() -> {
+      Region<Object, Object> region = ClusterStartupRule.getClientCache()
+          .createClientRegionFactory(ClientRegionShortcut.PROXY)
+          .create(REGION_NAME);
+
+      region.put("tokenKey", "tokenValue");
+      assertThat(region.get("tokenKey")).isEqualTo("tokenValue");
+    });
+  }
+
+  /**
+   * Test server restart with client reconnection.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Client can reconnect after server restart</li>
+   * <li>TLS session is re-established</li>
+   * <li>Client re-authenticates successfully</li>
+   * </ul>
+   */
+  @Test
+  public void testServerRestartWithClientReconnection() throws Exception {
+    // Create certificates and stores
+    // Note: Locator and server must use same CertStores for peer SSL 
communication
+    CertStores clusterStores = fixture.createClusterStores();
+    CertStores clientStores = fixture.createClientStores();
+
+    // Start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+    MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+    // Create region
+    server.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Connect client
+    Properties clientProps = clientStores.propertiesWith("all", false, false);
+    clientProps.putAll(fixture.createClientAuthProperties("data", "data"));
+    ClientVM client = cluster.startClientVM(2, c -> c
+        .withProperties(clientProps)
+        .withLocatorConnection(locatorPort));
+
+    // Verify initial connection
+    client.invoke(() -> {
+      Region<Object, Object> region = ClusterStartupRule.getClientCache()
+          .createClientRegionFactory(ClientRegionShortcut.PROXY)
+          .create(REGION_NAME);
+      region.put("beforeRestart", "value1");
+    });
+
+    // Stop and restart server
+    cluster.stop(1);
+    server = cluster.startServerVM(1, serverProps, locatorPort);
+
+    // Recreate region on restarted server
+    server.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Verify client can reconnect and operate
+    client.invoke(() -> {
+      Region<Object, Object> region = 
ClusterStartupRule.getClientCache().getRegion(REGION_NAME);
+      region.put("afterRestart", "value2");
+      assertThat(region.get("afterRestart")).isEqualTo("value2");
+    });
+  }
+
+  /**
+   * Test concurrent client operations over TLS.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Multiple threads can perform concurrent operations over TLS</li>
+   * <li>TLS connection handles concurrent load</li>
+   * <li>Authentication works with concurrent operations</li>
+   * </ul>
+   */
+  @Test
+  public void testConcurrentClientConnections() throws Exception {
+    // Create certificates and stores
+    // Note: Use createClusterStores() for both locator and server peer SSL 
communication
+    CertStores clusterStores = fixture.createClusterStores();
+    CertStores clientStores = fixture.createClientStores();
+
+    // Start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+    MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+    // Create region
+    server.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Create single client cache with TLS
+    Properties clientProps = clientStores.propertiesWith("all", false, false);
+    clientProps.putAll(fixture.createClientAuthProperties("data", "data"));
+
+    ClientCacheFactory factory = new ClientCacheFactory(clientProps)
+        .addPoolLocator("localhost", locatorPort)
+        .setPoolSubscriptionEnabled(true);
+
+    try (ClientCache clientCache = factory.create()) {
+      Region<Object, Object> region = clientCache
+          .createClientRegionFactory(ClientRegionShortcut.PROXY)
+          .create(REGION_NAME);
+
+      // Perform concurrent operations from multiple threads
+      int numThreads = 10;
+      ExecutorService executor = Executors.newFixedThreadPool(numThreads);
+      CountDownLatch startLatch = new CountDownLatch(1);
+      CountDownLatch completionLatch = new CountDownLatch(numThreads);
+      List<Future<?>> futures = new ArrayList<>();
+
+      for (int i = 0; i < numThreads; i++) {
+        final int operationId = i;
+        Future<?> future = executor.submit(() -> {
+          try {
+            // Wait for all threads to be ready
+            startLatch.await();
+
+            // Perform operation
+            region.put("key" + operationId, "value" + operationId);
+            assertThat(region.get("key" + operationId)).isEqualTo("value" + 
operationId);
+
+            completionLatch.countDown();
+          } catch (Exception e) {
+            throw new RuntimeException("Operation " + operationId + " failed", 
e);
+          }
+        });
+        futures.add(future);
+      }
+
+      // Start all operations simultaneously
+      startLatch.countDown();
+
+      // Wait for all operations to complete
+      boolean completed = completionLatch.await(2, TimeUnit.MINUTES);
+
+      // Check for exceptions first to see what actually failed
+      for (Future<?> future : futures) {
+        future.get();
+      }
+
+      assertThat(completed).isTrue();
+
+      executor.shutdown();
+    }
+  }
+
+  /**
+   * Test region operations with authorization checks.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>SecurityManager enforces authorization based on principal</li>
+   * <li>Different users have different permissions</li>
+   * <li>Authorization works with TLS transport encryption</li>
+   * </ul>
+   */
+  @Test
+  public void testRegionOperationsWithAuthorization() throws Exception {
+    // Create certificates and stores
+    // Note: Locator and server must use same CertStores for peer SSL 
communication
+    CertStores clusterStores = fixture.createClusterStores();
+    CertStores clientStores = fixture.createClientStores();
+
+    // Start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+    MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+    // Create region
+    server.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Connect client with "data" credentials
+    // SimpleSecurityManager authorizes based on username prefix matching 
permission
+    Properties clientProps = clientStores.propertiesWith("all", false, false);
+    clientProps.putAll(fixture.createClientAuthProperties("data", "data"));
+    ClientVM client = cluster.startClientVM(2, c -> c
+        .withProperties(clientProps)
+        .withLocatorConnection(locatorPort));
+
+    // Verify authorized operations succeed
+    client.invoke(() -> {
+      Region<Object, Object> region = ClusterStartupRule.getClientCache()
+          .createClientRegionFactory(ClientRegionShortcut.PROXY)
+          .create(REGION_NAME);
+
+      // User "data" should be authorized for DATA:READ and DATA:WRITE
+      region.put("authKey", "authValue");
+      assertThat(region.get("authKey")).isEqualTo("authValue");
+    });
+  }
+}
diff --git 
a/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/ServerOnlyTLSWithAuthNegativeTest.java
 
b/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/ServerOnlyTLSWithAuthNegativeTest.java
new file mode 100644
index 0000000000..6c762d214d
--- /dev/null
+++ 
b/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/ServerOnlyTLSWithAuthNegativeTest.java
@@ -0,0 +1,481 @@
+/*
+ * 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.geode.cache.ssl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.cache.client.ClientCache;
+import org.apache.geode.cache.client.ClientRegionShortcut;
+import org.apache.geode.cache.client.ServerOperationException;
+import org.apache.geode.security.AuthenticationFailedException;
+import org.apache.geode.security.AuthenticationRequiredException;
+import org.apache.geode.security.NotAuthorizedException;
+import org.apache.geode.test.dunit.IgnoredException;
+import org.apache.geode.test.dunit.rules.ClientVM;
+import org.apache.geode.test.dunit.rules.ClusterStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.junit.categories.SecurityTest;
+import org.apache.geode.test.junit.rules.ServerOnlyTLSTestFixture;
+
+/**
+ * Negative tests for Server-only TLS with Alternative Client Authentication.
+ *
+ * <p>
+ * These tests verify that security violations are properly detected and 
rejected:
+ * <ul>
+ * <li>Invalid credentials are rejected</li>
+ * <li>Missing credentials are rejected</li>
+ * <li>Invalid tokens are rejected</li>
+ * <li>Unauthorized operations are blocked</li>
+ * <li>Missing or invalid server certificates are detected</li>
+ * </ul>
+ */
+@Category({SecurityTest.class})
+public class ServerOnlyTLSWithAuthNegativeTest {
+
+  private static final String REGION_NAME = "testRegion";
+
+  @Rule
+  public ClusterStartupRule cluster = new ClusterStartupRule();
+
+  private ServerOnlyTLSTestFixture fixture;
+
+  @Before
+  public void setUp() throws Exception {
+    fixture = new ServerOnlyTLSTestFixture();
+  }
+
+  @org.junit.After
+  public void tearDown() throws Exception {
+    // Remove ignored exceptions
+    IgnoredException.removeAllExpectedExceptions();
+    // Give VMs time to fully shut down
+    Thread.sleep(500);
+  }
+
+  /**
+   * Test that clients with invalid credentials are rejected.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Wrong password causes authentication failure</li>
+   * <li>TLS connection is established but authentication fails</li>
+   * </ul>
+   */
+  @Test
+  public void testClientWithInvalidCredentialsRejected() throws Exception {
+    
IgnoredException.addIgnoredException(AuthenticationFailedException.class.getName());
+    IgnoredException.addIgnoredException("Authentication FAILED");
+    IgnoredException.addIgnoredException("ServerOperationException");
+
+    // Create certificates and stores
+    // Note: Locator and server must use same CertStores for peer SSL 
communication
+    CertStores clusterStores = fixture.createClusterStores();
+    CertStores clientStores = fixture.createClientStores();
+
+    // Start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+    MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+    // Create region
+    server.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Configure client with WRONG password
+    Properties clientProps = clientStores.propertiesWith("all", false, false);
+    clientProps.putAll(fixture.createClientAuthProperties("testUser", 
"wrongPassword"));
+
+    // Client connection succeeds (TLS is established), but authentication 
fails on first operation
+    ClientVM client = cluster.startClientVM(2, c -> c
+        .withProperties(clientProps)
+        .withLocatorConnection(locatorPort));
+
+    // Verify authentication fails when attempting an operation
+    client.invoke(() -> {
+      ClientCache clientCache = ClusterStartupRule.getClientCache();
+      Region<Object, Object> region = clientCache
+          .createClientRegionFactory(ClientRegionShortcut.PROXY)
+          .create(REGION_NAME);
+
+      // Authentication should fail on first operation
+      assertThatThrownBy(() -> region.put("key", "value"))
+          .isInstanceOf(ServerOperationException.class)
+          .hasCauseInstanceOf(AuthenticationFailedException.class);
+    });
+  }
+
+  /**
+   * Test that clients without credentials are rejected.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Missing authentication credentials cause connection failure</li>
+   * <li>TLS connection might establish but authentication is required</li>
+   * </ul>
+   */
+  @Test
+  public void testClientWithMissingCredentialsRejected() throws Exception {
+    
IgnoredException.addIgnoredException(AuthenticationFailedException.class.getName());
+    IgnoredException.addIgnoredException("Authentication FAILED");
+    IgnoredException.addIgnoredException("AuthenticationRequiredException");
+    IgnoredException.addIgnoredException("No security credentials are 
provided");
+    IgnoredException.addIgnoredException("ServerOperationException");
+
+    // Create certificates and stores
+    // Note: Locator and server must use same CertStores for peer SSL 
communication
+    CertStores clusterStores = fixture.createClusterStores();
+    CertStores clientStores = fixture.createClientStores();
+
+    // Start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+    MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+    // Create region
+    server.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Configure client with TLS but NO authentication credentials
+    Properties clientProps = clientStores.propertiesWith("all", false, false);
+    // Do NOT add authentication properties
+
+    // Client connection succeeds (TLS is established), but authentication 
fails on first operation
+    ClientVM client = cluster.startClientVM(2, c -> c
+        .withProperties(clientProps)
+        .withLocatorConnection(locatorPort));
+
+    // Verify authentication fails when attempting an operation
+    client.invoke(() -> {
+      ClientCache clientCache = ClusterStartupRule.getClientCache();
+      Region<Object, Object> region = clientCache
+          .createClientRegionFactory(ClientRegionShortcut.PROXY)
+          .create(REGION_NAME);
+
+      // Authentication should fail on first operation due to missing 
credentials
+      assertThatThrownBy(() -> region.put("key", "value"))
+          .isInstanceOf(ServerOperationException.class)
+          .hasCauseInstanceOf(AuthenticationRequiredException.class);
+    });
+  }
+
+  /**
+   * Test that clients with invalid tokens are rejected.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Invalid bearer tokens cause authentication failure</li>
+   * <li>Token validation is enforced</li>
+   * </ul>
+   */
+  @Test
+  public void testClientWithInvalidTokenRejected() throws Exception {
+    
IgnoredException.addIgnoredException(AuthenticationFailedException.class.getName());
+    IgnoredException.addIgnoredException("Authentication FAILED");
+    IgnoredException.addIgnoredException("Token authentication FAILED");
+    IgnoredException.addIgnoredException("ServerOperationException");
+
+    // Create certificates and stores
+    // Note: Locator and server must use same CertStores for peer SSL 
communication
+    CertStores clusterStores = fixture.createClusterStores();
+    CertStores clientStores = fixture.createClientStores();
+
+    // Start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+    MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+    // Create region
+    server.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Configure client with INVALID token
+    Properties clientProps = clientStores.propertiesWith("all", false, false);
+    
clientProps.putAll(fixture.createClientTokenAuthProperties("INVALID_TOKEN"));
+
+    // Client connection succeeds (TLS is established), but authentication 
fails on first operation
+    ClientVM client = cluster.startClientVM(2, c -> c
+        .withProperties(clientProps)
+        .withLocatorConnection(locatorPort));
+
+    // Verify authentication fails when attempting an operation
+    client.invoke(() -> {
+      ClientCache clientCache = ClusterStartupRule.getClientCache();
+      Region<Object, Object> region = clientCache
+          .createClientRegionFactory(ClientRegionShortcut.PROXY)
+          .create(REGION_NAME);
+
+      // Authentication should fail on first operation due to invalid token
+      assertThatThrownBy(() -> region.put("key", "value"))
+          .isInstanceOf(ServerOperationException.class)
+          .hasCauseInstanceOf(AuthenticationFailedException.class);
+    });
+  }
+
+  /**
+   * Test that authenticated clients without authorization are blocked from 
operations.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Authentication succeeds</li>
+   * <li>Unauthorized operations are blocked by SecurityManager</li>
+   * </ul>
+   */
+  @Test
+  public void testClientUnauthorizedForOperation() throws Exception {
+    
IgnoredException.addIgnoredException(NotAuthorizedException.class.getName());
+
+    // Create certificates and stores
+    // Note: Locator and server must use same CertStores for peer SSL 
communication
+    CertStores clusterStores = fixture.createClusterStores();
+    CertStores clientStores = fixture.createClientStores();
+
+    // Start locator
+    Properties locatorProps = clusterStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server
+    Properties serverProps = clusterStores.propertiesWith("all", false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+    MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+    // Create region
+    server.invoke(() -> {
+      ClusterStartupRule.getCache()
+          .createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+    });
+
+    // Connect client with credentials that don't match required permissions
+    // SimpleSecurityManager authorizes based on principal matching permission 
string prefix
+    // User "readonly" will NOT be authorized for "DATA:WRITE" operations
+    Properties clientProps = clientStores.propertiesWith("all", false, false);
+    clientProps.putAll(fixture.createClientAuthProperties("readonly", 
"readonly"));
+
+    ClientVM client = cluster.startClientVM(2, c -> c
+        .withProperties(clientProps)
+        .withLocatorConnection(locatorPort));
+
+    // Verify client can connect but unauthorized operations fail
+    client.invoke(() -> {
+      Region<Object, Object> region = ClusterStartupRule.getClientCache()
+          .createClientRegionFactory(ClientRegionShortcut.PROXY)
+          .create(REGION_NAME);
+
+      // This operation should fail due to lack of authorization
+      assertThatThrownBy(() -> region.put("unauthorizedKey", "value"))
+          .hasCauseInstanceOf(NotAuthorizedException.class);
+    });
+  }
+
+  /**
+   * Test that clients reject connections to servers without certificates.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>TLS handshake fails when server doesn't present certificate</li>
+   * <li>Client-side verification is enforced</li>
+   * </ul>
+   */
+  @Test
+  public void testClientCannotConnectWithoutServerCert() throws Exception {
+    IgnoredException.addIgnoredException("SSLHandshakeException");
+    IgnoredException.addIgnoredException("Server expecting SSL handshake");
+
+    // This test would require starting a server WITHOUT SSL configuration
+    // which is complex in a proper test environment. The test verifies the 
concept
+    // that mixing SSL and non-SSL components fails.
+
+    // Create locator WITH SSL
+    CertStores locatorStores = fixture.createLocatorStores();
+
+    Properties locatorProps = locatorStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(2, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Start server WITHOUT SSL (simulating misconfiguration)
+    Properties serverProps = new Properties();
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+    // No SSL properties
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+
+    // Server should fail to join the cluster due to SSL mismatch
+    assertThatThrownBy(() -> {
+      cluster.startServerVM(3, serverProps, locatorPort);
+    }).getCause().getCause().getCause()
+        .isInstanceOf(javax.net.ssl.SSLHandshakeException.class);
+  }
+
+  /**
+   * Test that clients reject servers with invalid or untrusted certificates.
+   *
+   * <p>
+   * Verifies:
+   * <ul>
+   * <li>Client verifies server certificate against truststore</li>
+   * <li>Invalid server certificates are rejected</li>
+   * <li>PKIX path validation is enforced</li>
+   * </ul>
+   */
+  @Test
+  public void testClientRejectsServerWithInvalidCert() throws Exception {
+    IgnoredException.addIgnoredException("SSLHandshakeException");
+    IgnoredException.addIgnoredException("path");
+    IgnoredException.addIgnoredException("certificate");
+
+    // Create two separate CAs
+    CertificateMaterial validCA = fixture.getCA();
+
+    // Create a separate untrusted CA
+    CertificateMaterial untrustedCA = new CertificateBuilder()
+        .commonName("Untrusted-CA")
+        .isCA()
+        .generate();
+
+    // Create locator with valid CA
+    CertStores locatorStores = fixture.createLocatorStores();
+
+    Properties locatorProps = locatorStores.propertiesWith("all", false, 
false);
+    fixture.addSecurityManagerConfig(locatorProps);
+    fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+    MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+    int locatorPort = locator.getPort();
+
+    // Create server with certificate from UNTRUSTED CA
+    CertificateMaterial untrustedServerCert = new CertificateBuilder()
+        .commonName("untrusted-server")
+        .issuedBy(untrustedCA)
+        .sanDnsName("localhost")
+        .sanIpAddress("127.0.0.1")
+        .generate();
+
+    CertStores untrustedServerStores = CertStores.serverStore();
+    untrustedServerStores.withCertificate("server", untrustedServerCert);
+    untrustedServerStores.trust("untrustedCA", untrustedCA); // Server ONLY 
trusts untrusted CA, not
+                                                             // valid CA
+
+    Properties serverProps = untrustedServerStores.propertiesWith("all", 
false, false);
+    fixture.addSecurityManagerConfig(serverProps);
+    fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+    serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+    serverProps.setProperty("member-timeout", "5000"); // Fail fast if can't 
join cluster
+
+    // Server might start but cluster communication could fail due to cert 
mismatch
+    // OR server might fail to join cluster
+    // Either way, this demonstrates certificate validation
+
+    // Create client truststore with valid CA only (doesn't trust untrusted CA)
+    CertStores clientStores = CertStores.clientStore();
+    clientStores.trust("ca", validCA); // Client ONLY trusts valid CA
+
+    Properties clientProps = clientStores.propertiesWith("all", false, false);
+    clientProps.putAll(fixture.createClientAuthProperties("testUser", 
"testUser"));
+
+    // If server manages to start, client connection should fail due to 
untrusted certificate
+    try {
+      MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+      server.invoke(() -> {
+        ClusterStartupRule.getCache()
+            .createRegionFactory(RegionShortcut.REPLICATE)
+            .create(REGION_NAME);
+      });
+
+      // Client should reject server's untrusted certificate
+      assertThatThrownBy(() -> {
+        cluster.startClientVM(2, c -> c
+            .withProperties(clientProps)
+            .withLocatorConnection(locatorPort));
+      }).satisfiesAnyOf(
+          e -> assertThat(e).hasMessageContaining("PKIX"),
+          e -> assertThat(e).hasMessageContaining("certificate"),
+          e -> assertThat(e).hasMessageContaining("trust"));
+    } catch (Exception e) {
+      // Server startup failure due to certificate mismatch is expected
+      // The cause chain should contain SSL/certificate/handshake errors
+      Throwable cause = e;
+      boolean foundSSLError = false;
+      while (cause != null && !foundSSLError) {
+        String message = cause.getClass().getName() + ": " + 
cause.getMessage();
+        if (message.contains("SSL") || message.contains("certificate")
+            || message.contains("handshake")) {
+          foundSSLError = true;
+        }
+        cause = cause.getCause();
+      }
+      assertThat(foundSSLError).as("Should find SSL/certificate/handshake 
error in cause chain")
+          .isTrue();
+    }
+  }
+}
diff --git 
a/geode-junit/src/main/java/org/apache/geode/security/templates/TokenAuthInit.java
 
b/geode-junit/src/main/java/org/apache/geode/security/templates/TokenAuthInit.java
new file mode 100644
index 0000000000..a7aa7d3255
--- /dev/null
+++ 
b/geode-junit/src/main/java/org/apache/geode/security/templates/TokenAuthInit.java
@@ -0,0 +1,68 @@
+/*
+ * 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.geode.security.templates;
+
+import java.util.Properties;
+
+import org.apache.geode.LogWriter;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.security.AuthInitialize;
+import org.apache.geode.security.AuthenticationFailedException;
+import org.apache.geode.security.SecurityManager;
+
+/**
+ * An {@link AuthInitialize} implementation that obtains a bearer token as 
credentials from the
+ * given set of properties.
+ *
+ * To use this class the {@code security-client-auth-init} property should be 
set to the fully
+ * qualified name of the static {@code create} method viz.
+ * {@code org.apache.geode.security.templates.TokenAuthInit.create}
+ */
+public class TokenAuthInit implements AuthInitialize {
+
+  public static final String BEARER_TOKEN = "security-bearer-token";
+
+  protected LogWriter systemLogWriter;
+  protected LogWriter securityLogWriter;
+
+  public static AuthInitialize create() {
+    return new TokenAuthInit();
+  }
+
+  @Override
+  public void init(final LogWriter systemLogWriter, final LogWriter 
securityLogWriter)
+      throws AuthenticationFailedException {
+    this.systemLogWriter = systemLogWriter;
+    this.securityLogWriter = securityLogWriter;
+  }
+
+  @Override
+  public Properties getCredentials(final Properties securityProperties,
+      final DistributedMember server, final boolean isPeer) throws 
AuthenticationFailedException {
+    String token = securityProperties.getProperty(BEARER_TOKEN);
+    if (token == null) {
+      throw new AuthenticationFailedException(
+          "TokenAuthInit: bearer token property [" + BEARER_TOKEN + "] not 
set.");
+    }
+
+    Properties securityPropertiesCopy = new Properties();
+    // SecurityManager expects TOKEN property
+    securityPropertiesCopy.setProperty(SecurityManager.TOKEN, token);
+    return securityPropertiesCopy;
+  }
+
+  @Override
+  public void close() {}
+}
diff --git 
a/geode-junit/src/main/java/org/apache/geode/test/junit/rules/ServerOnlyTLSTestFixture.java
 
b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/ServerOnlyTLSTestFixture.java
new file mode 100644
index 0000000000..20ffc0924d
--- /dev/null
+++ 
b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/ServerOnlyTLSTestFixture.java
@@ -0,0 +1,253 @@
+/*
+ * 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.geode.test.junit.rules;
+
+import java.util.Properties;
+
+import org.apache.geode.cache.ssl.CertStores;
+import org.apache.geode.cache.ssl.CertificateBuilder;
+import org.apache.geode.cache.ssl.CertificateMaterial;
+import org.apache.geode.examples.SimpleSecurityManager;
+import org.apache.geode.security.templates.UserPasswordAuthInit;
+
+/**
+ * Test fixture for Server-only TLS with Alternative Client Authentication 
scenarios.
+ *
+ * <p>
+ * This fixture creates:
+ * <ul>
+ * <li>A Certificate Authority (CA)</li>
+ * <li>Server certificates signed by the CA (for locators and servers)</li>
+ * <li>NO client certificates - clients authenticate via username/password or 
tokens</li>
+ * <li>Server/locator keystores with server certificates</li>
+ * <li>Client truststores with CA certificate (to verify servers)</li>
+ * <li>Security configuration using SimpleSecurityManager</li>
+ * </ul>
+ *
+ * <p>
+ * Key characteristics:
+ * <ul>
+ * <li>ssl-require-authentication=false - servers don't require client 
certificates</li>
+ * <li>All transport is TLS-encrypted in both directions</li>
+ * <li>Clients authenticate using application-layer credentials 
(username/password or tokens)</li>
+ * </ul>
+ */
+public class ServerOnlyTLSTestFixture {
+
+  private static final String CA_CN = "Server-Only-TLS-CA";
+  private static final String SERVER_CN = "geode-server";
+  private static final String LOCATOR_CN = "geode-locator";
+
+  private final CertificateMaterial ca;
+  private final CertificateMaterial serverCertificate;
+  private final CertificateMaterial locatorCertificate;
+
+  public ServerOnlyTLSTestFixture() throws Exception {
+    // Create CA
+    ca = createCA();
+
+    // Create server and locator certificates (no client certificates needed)
+    serverCertificate = createServerCertificate(ca);
+    locatorCertificate = createLocatorCertificate(ca);
+  }
+
+  /**
+   * Creates a Certificate Authority for signing server certificates.
+   */
+  private CertificateMaterial createCA() throws Exception {
+    CertificateBuilder caBuilder = new CertificateBuilder()
+        .commonName(CA_CN)
+        .isCA();
+
+    return caBuilder.generate();
+  }
+
+  /**
+   * Creates a server certificate signed by the CA.
+   * Includes comprehensive Subject Alternative Names for server verification.
+   */
+  private CertificateMaterial createServerCertificate(CertificateMaterial ca) 
throws Exception {
+    CertificateBuilder serverBuilder = new CertificateBuilder()
+        .commonName(SERVER_CN)
+        .issuedBy(ca)
+        .sanDnsName("localhost")
+        .sanDnsName("server.localdomain")
+        .sanIpAddress("127.0.0.1")
+        .sanIpAddress("0.0.0.0");
+
+    return serverBuilder.generate();
+  }
+
+  /**
+   * Creates a locator certificate signed by the CA.
+   * Includes comprehensive Subject Alternative Names for locator verification.
+   */
+  private CertificateMaterial createLocatorCertificate(CertificateMaterial ca) 
throws Exception {
+    CertificateBuilder locatorBuilder = new CertificateBuilder()
+        .commonName(LOCATOR_CN)
+        .issuedBy(ca)
+        .sanDnsName("localhost")
+        .sanDnsName("locator.localdomain")
+        .sanIpAddress("127.0.0.1")
+        .sanIpAddress("0.0.0.0");
+
+    return locatorBuilder.generate();
+  }
+
+  /**
+   * Creates and returns server CertStores for server-only TLS.
+   *
+   * <p>
+   * Server truststore contains the CA certificate to verify peer connections.
+   * <p>
+   * Server keystore contains the server's certificate for presentation to 
clients.
+   *
+   * @return CertStores configured for server with server certificate
+   */
+  public CertStores createServerStores() {
+    CertStores certStores = CertStores.serverStore();
+    certStores.withCertificate("server", serverCertificate);
+    certStores.trust("ca", ca);
+    return certStores;
+  }
+
+  /**
+   * Creates and returns locator CertStores for server-only TLS.
+   *
+   * <p>
+   * Locator truststore contains the CA certificate to verify peer connections.
+   * <p>
+   * Locator keystore contains the locator's certificate for presentation to 
clients and peers.
+   *
+   * @return CertStores configured for locator with locator certificate
+   */
+  public CertStores createLocatorStores() {
+    CertStores certStores = CertStores.locatorStore();
+    certStores.withCertificate("locator", locatorCertificate);
+    certStores.trust("ca", ca);
+    return certStores;
+  }
+
+  /**
+   * Creates and returns cluster CertStores for both locator and server.
+   *
+   * <p>
+   * For peer SSL communication, both locator and server need compatible 
certificates.
+   * This method creates CertStores with the server certificate that works for 
both.
+   * The server certificate includes SANs for localhost/127.0.0.1 which work 
for both roles.
+   *
+   * @return CertStores configured with server certificate and CA trust
+   */
+  public CertStores createClusterStores() {
+    CertStores certStores = CertStores.serverStore();
+    // Use server certificate for both locator and server (SANs cover both 
roles)
+    certStores.withCertificate("server", serverCertificate);
+    certStores.trust("ca", ca);
+    return certStores;
+  }
+
+  /**
+   * Creates and returns client CertStores for server-only TLS.
+   *
+   * <p>
+   * Client truststore contains ONLY the CA certificate to verify server 
certificates.
+   * <p>
+   * NO keystore is created - clients do not present certificates.
+   *
+   * @return CertStores configured for client with only truststore (no client 
certificate)
+   */
+  public CertStores createClientStores() {
+    CertStores certStores = CertStores.clientStore();
+    // Client only needs truststore with CA to verify servers
+    certStores.trust("ca", ca);
+    // NO client keystore - clients don't present certificates
+    return certStores;
+  }
+
+  /**
+   * Adds security manager configuration to properties.
+   * Uses SimpleSecurityManager for authentication and authorization.
+   */
+  public Properties addSecurityManagerConfig(Properties props) {
+    props.setProperty("security-manager", 
SimpleSecurityManager.class.getName());
+    return props;
+  }
+
+  /**
+   * Adds peer authentication credentials for server/locator-to-locator 
connections.
+   * Required when security-manager is enabled.
+   *
+   * @param props the properties to add authentication to
+   * @param username the username for peer authentication
+   * @param password the password for peer authentication
+   * @return the properties with peer authentication configured
+   */
+  public Properties addPeerAuthProperties(Properties props, String username, 
String password) {
+    props.setProperty("security-username", username);
+    props.setProperty("security-password", password);
+    return props;
+  }
+
+  /**
+   * Creates client authentication properties with username and password.
+   *
+   * @param username the username
+   * @param password the password
+   * @return Properties with authentication configuration
+   */
+  public Properties createClientAuthProperties(String username, String 
password) {
+    Properties props = new Properties();
+    props.setProperty("security-client-auth-init",
+        UserPasswordAuthInit.class.getName() + ".create");
+    props.setProperty(UserPasswordAuthInit.USER_NAME, username);
+    props.setProperty(UserPasswordAuthInit.PASSWORD, password);
+    return props;
+  }
+
+  /**
+   * Creates client authentication properties with a bearer token.
+   *
+   * @param token the bearer token
+   * @return Properties with token authentication configuration
+   */
+  public Properties createClientTokenAuthProperties(String token) {
+    Properties props = new Properties();
+    props.setProperty("security-client-auth-init",
+        "org.apache.geode.security.templates.TokenAuthInit.create");
+    props.setProperty("security-bearer-token", token);
+    return props;
+  }
+
+  /**
+   * Gets the CA certificate material for testing purposes.
+   */
+  public CertificateMaterial getCA() {
+    return ca;
+  }
+
+  /**
+   * Gets the server certificate material for testing purposes.
+   */
+  public CertificateMaterial getServerCertificate() {
+    return serverCertificate;
+  }
+
+  /**
+   * Gets the locator certificate material for testing purposes.
+   */
+  public CertificateMaterial getLocatorCertificate() {
+    return locatorCertificate;
+  }
+}

Reply via email to