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

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


The following commit(s) were added to refs/heads/develop by this push:
     new bee586bcd0 Improve ACL 2.0 authentication and authorization handling 
(#10997)
bee586bcd0 is described below

commit bee586bcd08833e969aaa7b81171569c02102a7a
Author: dingshuangxi888 <[email protected]>
AuthorDate: Thu Sep 3 15:36:26 2026 +0800

    Improve ACL 2.0 authentication and authorization handling (#10997)
    
    * fix(auth): require topic for view message authorization
    
    * fix(auth): restrict user password responses
    
    * fix(auth): trust only authenticated gRPC subject
    
    * fix(auth): complete ACL follow-up handling
---
 .../strategy/AbstractAuthenticationStrategy.java   |  12 +-
 .../strategy/StatefulAuthenticationStrategy.java   |   4 +
 .../authorization/AuthorizationCompatibility.java  |   3 +-
 .../DefaultAuthorizationContextBuilder.java        |   6 +-
 .../apache/rocketmq/auth/config/AuthConfig.java    |  54 +++++-
 .../StatefulAuthenticationStrategyTest.java        |  58 +++++++
 .../authorization/AuthorizationEvaluatorTest.java  |  29 +++-
 .../DefaultAuthorizationContextBuilderTest.java    |  20 ++-
 .../rocketmq/auth/config/AuthConfigTest.java       |  73 ++++++++
 .../broker/auth/converter/UserConverter.java       |   1 -
 .../broker/processor/AdminBrokerProcessor.java     |  66 +++++++-
 .../broker/processor/EndTransactionProcessor.java  |   8 +
 .../broker/processor/QueryMessageProcessor.java    |   1 +
 .../AbstractTransactionalMessageCheckListener.java |   2 +-
 .../TransactionalMessageRocksDBService.java        |   2 +-
 .../broker/auth/converter/UserConverterTest.java   |  47 ++++++
 .../auth/pipeline/AuthorizationPipelineTest.java   |  14 ++
 .../AdminBrokerProcessorConfigSanitizeTest.java    |  55 +++++++
 .../broker/processor/AdminBrokerProcessorTest.java | 183 ++++++++++++++++++++-
 .../processor/EndTransactionProcessorTest.java     |  39 ++++-
 .../processor/QueryMessageProcessorTest.java       |   3 +-
 ...faultTransactionalMessageCheckListenerTest.java |  30 ++++
 .../TransactionalMessageRocksDBServiceTest.java    | 100 +++++++++++
 .../selector/DefaultMQProducerImplTest.java        |  42 ++++-
 .../container/BrokerContainerProcessor.java        |  34 +++-
 ...BrokerContainerProcessorConfigSanitizeTest.java |  55 +++++++
 .../rocketmq/proxy/common/utils/GrpcUtils.java     |  13 ++
 .../proxy/grpc/interceptor/HeaderInterceptor.java  |  11 +-
 .../grpc/pipeline/AuthenticationPipeline.java      |  23 ++-
 .../activity/AbstractRemotingActivity.java         |   5 +-
 .../grpc/interceptor/HeaderInterceptorTest.java    | 150 +++++++++++++++++
 .../grpc/pipeline/AuthenticationPipelineTest.java  | 111 +++++++++++++
 .../activity/AbstractRemotingActivityTest.java     |  31 +++-
 .../metadata/ClusterMetadataServiceTest.java       |  13 ++
 .../ResumeCheckHalfMessageRequestHeader.java       |   2 +
 .../tools/command/auth/ListUserSubCommand.java     |   7 +-
 36 files changed, 1245 insertions(+), 62 deletions(-)

diff --git 
a/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AbstractAuthenticationStrategy.java
 
b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AbstractAuthenticationStrategy.java
index bc7052014d..ab185909c1 100644
--- 
a/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AbstractAuthenticationStrategy.java
+++ 
b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AbstractAuthenticationStrategy.java
@@ -16,10 +16,7 @@
  */
 package org.apache.rocketmq.auth.authentication.strategy;
 
-import java.util.HashSet;
-import java.util.Set;
 import java.util.function.Supplier;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
 import 
org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
 import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
@@ -30,7 +27,6 @@ import org.apache.rocketmq.common.utils.ExceptionUtils;
 public abstract class AbstractAuthenticationStrategy implements 
AuthenticationStrategy {
 
     protected final AuthConfig authConfig;
-    protected final Set<String> authenticationWhiteSet = new HashSet<>();
     protected final AuthenticationProvider<AuthenticationContext> 
authenticationProvider;
 
     public AbstractAuthenticationStrategy(AuthConfig authConfig, Supplier<?> 
metadataService) {
@@ -39,12 +35,6 @@ public abstract class AbstractAuthenticationStrategy 
implements AuthenticationSt
         if (this.authenticationProvider != null) {
             this.authenticationProvider.initialize(authConfig, 
metadataService);
         }
-        if (StringUtils.isNotBlank(authConfig.getAuthenticationWhitelist())) {
-            String[] whitelist = 
StringUtils.split(authConfig.getAuthenticationWhitelist(), ",");
-            for (String rpcCode : whitelist) {
-                this.authenticationWhiteSet.add(StringUtils.trim(rpcCode));
-            }
-        }
     }
 
     protected void doEvaluate(AuthenticationContext context) {
@@ -57,7 +47,7 @@ public abstract class AbstractAuthenticationStrategy 
implements AuthenticationSt
         if (this.authenticationProvider == null) {
             return;
         }
-        if (this.authenticationWhiteSet.contains(context.getRpcCode())) {
+        if (!authConfig.isAuthenticationRequired(context.getRpcCode())) {
             return;
         }
         try {
diff --git 
a/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatefulAuthenticationStrategy.java
 
b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatefulAuthenticationStrategy.java
index 914d99ac2e..74fc179147 100644
--- 
a/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatefulAuthenticationStrategy.java
+++ 
b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatefulAuthenticationStrategy.java
@@ -42,6 +42,10 @@ public class StatefulAuthenticationStrategy extends 
AbstractAuthenticationStrate
 
     @Override
     public void evaluate(AuthenticationContext context) {
+        if (!this.authConfig.isAuthenticationRequired(context.getRpcCode())) {
+            this.doEvaluate(context);
+            return;
+        }
         if (StringUtils.isBlank(context.getChannelId())) {
             this.doEvaluate(context);
             return;
diff --git 
a/auth/src/main/java/org/apache/rocketmq/auth/authorization/AuthorizationCompatibility.java
 
b/auth/src/main/java/org/apache/rocketmq/auth/authorization/AuthorizationCompatibility.java
index 75fe11bf74..803ecf63ae 100644
--- 
a/auth/src/main/java/org/apache/rocketmq/auth/authorization/AuthorizationCompatibility.java
+++ 
b/auth/src/main/java/org/apache/rocketmq/auth/authorization/AuthorizationCompatibility.java
@@ -44,7 +44,6 @@ final class AuthorizationCompatibility {
                 case RequestCode.UNREGISTER_CLIENT:
                     return isProducerUnregister(request);
                 case RequestCode.END_TRANSACTION:
-                case RequestCode.VIEW_MESSAGE_BY_ID:
                     return isHistoricalTopicAbsent(request);
                 default:
                     return false;
@@ -104,7 +103,7 @@ final class AuthorizationCompatibility {
     }
 
     /**
-     * Historical END_TRANSACTION and VIEW_MESSAGE_BY_ID requests carry no 
topic field.
+     * Historical END_TRANSACTION requests carry no topic field.
      */
     private static boolean isHistoricalTopicAbsent(RemotingCommand request) {
         return request.getExtFields() != null && 
StringUtils.isBlank(getExtField(request, "topic"));
diff --git 
a/auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilder.java
 
b/auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilder.java
index 61aac9af28..af090b5f0f 100644
--- 
a/auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilder.java
+++ 
b/auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilder.java
@@ -257,10 +257,8 @@ public class DefaultAuthorizationContextBuilder implements 
AuthorizationContextB
                     }
                     break;
                 case RequestCode.VIEW_MESSAGE_BY_ID:
-                    if (StringUtils.isNotBlank(fields.get(TOPIC))) {
-                        topic = Resource.ofTopic(fields.get(TOPIC));
-                        result.add(DefaultAuthorizationContext.of(subject, 
topic, Action.GET, sourceIp));
-                    }
+                    topic = 
Resource.ofTopic(requireResource(fields.get(TOPIC), "topic"));
+                    result.add(DefaultAuthorizationContext.of(subject, topic, 
Action.GET, sourceIp));
                     break;
                 case RequestCode.CONSUMER_SEND_MSG_BACK:
                     group = 
Resource.ofGroup(requireResource(fields.get(GROUP), "consumer group"));
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/config/AuthConfig.java 
b/auth/src/main/java/org/apache/rocketmq/auth/config/AuthConfig.java
index ed294c8ecb..239972b454 100644
--- a/auth/src/main/java/org/apache/rocketmq/auth/config/AuthConfig.java
+++ b/auth/src/main/java/org/apache/rocketmq/auth/config/AuthConfig.java
@@ -16,6 +16,11 @@
  */
 package org.apache.rocketmq.auth.config;
 
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+
 public class AuthConfig implements Cloneable {
 
     private String configName;
@@ -32,7 +37,7 @@ public class AuthConfig implements Cloneable {
 
     private String authenticationStrategy;
 
-    private String authenticationWhitelist;
+    private volatile WhitelistSnapshot authenticationWhitelist;
 
     private String initAuthenticationUser;
 
@@ -79,6 +84,48 @@ public class AuthConfig implements Cloneable {
         }
     }
 
+    public boolean isAuthenticationRequired(String rpcCode) {
+        return authenticationEnabled && !contains(authenticationWhitelist, 
rpcCode);
+    }
+
+    private static boolean contains(WhitelistSnapshot snapshot, String 
rpcCode) {
+        return snapshot != null && StringUtils.isNotEmpty(rpcCode)
+            && snapshot.entries.contains(rpcCode);
+    }
+
+    private static Set<String> parseWhitelist(String whitelist) {
+        if (StringUtils.isBlank(whitelist)) {
+            return Collections.emptySet();
+        }
+        Set<String> result = new LinkedHashSet<>();
+        for (String rpcCode : StringUtils.split(whitelist, ',')) {
+            String value = StringUtils.trim(rpcCode);
+            if (StringUtils.isNotEmpty(value)) {
+                result.add(value);
+            }
+        }
+        return Collections.unmodifiableSet(result);
+    }
+
+    private static final class WhitelistSnapshot {
+        private final String value;
+        private final Set<String> entries;
+
+        private WhitelistSnapshot(String value) {
+            this.value = value;
+            this.entries = parseWhitelist(value);
+        }
+
+        private static WhitelistSnapshot of(String value) {
+            return value == null ? null : new WhitelistSnapshot(value);
+        }
+
+        @Override
+        public String toString() {
+            return value;
+        }
+    }
+
     public String getConfigName() {
         return configName;
     }
@@ -136,11 +183,12 @@ public class AuthConfig implements Cloneable {
     }
 
     public String getAuthenticationWhitelist() {
-        return authenticationWhitelist;
+        WhitelistSnapshot snapshot = authenticationWhitelist;
+        return snapshot == null ? null : snapshot.value;
     }
 
     public void setAuthenticationWhitelist(String authenticationWhitelist) {
-        this.authenticationWhitelist = authenticationWhitelist;
+        this.authenticationWhitelist = 
WhitelistSnapshot.of(authenticationWhitelist);
     }
 
     public String getInitAuthenticationUser() {
diff --git 
a/auth/src/test/java/org/apache/rocketmq/auth/authentication/strategy/StatefulAuthenticationStrategyTest.java
 
b/auth/src/test/java/org/apache/rocketmq/auth/authentication/strategy/StatefulAuthenticationStrategyTest.java
new file mode 100644
index 0000000000..c9480a22f7
--- /dev/null
+++ 
b/auth/src/test/java/org/apache/rocketmq/auth/authentication/strategy/StatefulAuthenticationStrategyTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.rocketmq.auth.authentication.strategy;
+
+import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
+import 
org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
+import 
org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
+
+public class StatefulAuthenticationStrategyTest {
+
+    @Test
+    public void authenticationWhitelistBypassIsNotCachedAsSuccess() {
+        AuthConfig authConfig = new AuthConfig();
+        authConfig.setConfigName("stateful-whitelist-" + System.nanoTime());
+        authConfig.setAuthenticationEnabled(true);
+        authConfig.setAuthenticationWhitelist("PUBLIC_RPC");
+
+        StatefulAuthenticationStrategy strategy = spy(
+            new StatefulAuthenticationStrategy(authConfig, null));
+        strategy.evaluate(context("PUBLIC_RPC"));
+
+        doThrow(new AuthenticationException("signature is invalid"))
+            .when(strategy).doEvaluate(any(AuthenticationContext.class));
+
+        assertThatThrownBy(() -> strategy.evaluate(context("PROTECTED_RPC")))
+            .isInstanceOf(AuthenticationException.class)
+            .hasMessageContaining("signature is invalid");
+    }
+
+    private DefaultAuthenticationContext context(String rpcCode) {
+        DefaultAuthenticationContext context = new 
DefaultAuthenticationContext();
+        context.setChannelId("channel-id");
+        context.setRpcCode(rpcCode);
+        context.setUsername("claimed-super-user");
+        return context;
+    }
+}
diff --git 
a/auth/src/test/java/org/apache/rocketmq/auth/authorization/AuthorizationEvaluatorTest.java
 
b/auth/src/test/java/org/apache/rocketmq/auth/authorization/AuthorizationEvaluatorTest.java
index 8d49e965cb..231b5e8529 100644
--- 
a/auth/src/test/java/org/apache/rocketmq/auth/authorization/AuthorizationEvaluatorTest.java
+++ 
b/auth/src/test/java/org/apache/rocketmq/auth/authorization/AuthorizationEvaluatorTest.java
@@ -54,6 +54,7 @@ import org.apache.rocketmq.common.sysflag.MessageSysFlag;
 import org.apache.rocketmq.remoting.protocol.RemotingCommand;
 import org.apache.rocketmq.remoting.protocol.RequestCode;
 import 
org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader;
+import 
org.apache.rocketmq.remoting.protocol.header.ResumeCheckHalfMessageRequestHeader;
 import 
org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader;
 import org.apache.rocketmq.remoting.protocol.header.ViewMessageRequestHeader;
 import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData;
@@ -523,15 +524,28 @@ public class AuthorizationEvaluatorTest {
         requestEvaluator.evaluate(endTransaction(
             null, 1L, 2L, null, "messageId"), Collections.emptyList());
 
-        requestEvaluator.evaluate(viewMessage(0L), Collections.emptyList());
-        requestEvaluator.evaluate(viewMessage(-1L), Collections.emptyList());
-        requestEvaluator.evaluate(viewMessage(null), Collections.emptyList());
+        Assert.assertThrows(AuthorizationException.class,
+            () -> requestEvaluator.evaluate(viewMessage(0L), 
Collections.emptyList()));
+        Assert.assertThrows(AuthorizationException.class,
+            () -> requestEvaluator.evaluate(viewMessage(-1L), 
Collections.emptyList()));
+        Assert.assertThrows(AuthorizationException.class,
+            () -> requestEvaluator.evaluate(viewMessage(null), 
Collections.emptyList()));
         Assert.assertThrows(AuthorizationException.class,
             () -> requestEvaluator.evaluate(
                 
RemotingCommand.createRequestCommand(RequestCode.VIEW_MESSAGE_BY_ID, null),
                 Collections.emptyList()));
         Assert.assertThrows(AuthorizationException.class,
             () -> requestEvaluator.evaluate(viewMessage("topic", 0L), 
Collections.emptyList()));
+
+        Assert.assertThrows(AuthorizationException.class,
+            () -> requestEvaluator.evaluate(resumeCheckHalfMessage(null, 
"messageId"),
+                Collections.emptyList()));
+        Assert.assertThrows(AuthorizationException.class,
+            () -> requestEvaluator.evaluate(resumeCheckHalfMessage(" ", 
"messageId"),
+                Collections.emptyList()));
+        Assert.assertThrows(AuthorizationException.class,
+            () -> requestEvaluator.evaluate(resumeCheckHalfMessage("topic", 
"messageId"),
+                Collections.emptyList()));
         Assert.assertThrows(AuthorizationException.class,
             () -> 
requestEvaluator.evaluate(RemotingCommand.createRequestCommand(-1, null),
                 Collections.emptyList()));
@@ -643,6 +657,15 @@ public class AuthorizationEvaluatorTest {
         return request;
     }
 
+    private RemotingCommand resumeCheckHalfMessage(String topic, String 
messageId) {
+        ResumeCheckHalfMessageRequestHeader header = new 
ResumeCheckHalfMessageRequestHeader();
+        header.setTopic(topic);
+        header.setMsgId(messageId);
+        RemotingCommand request = 
RemotingCommand.createRequestCommand(RequestCode.RESUME_CHECK_HALF_MESSAGE, 
header);
+        request.makeCustomHeaderToNet();
+        return request;
+    }
+
     private RemotingCommand remotingRequest(int requestCode, byte[] body) {
         RemotingCommand request = 
RemotingCommand.createRequestCommand(requestCode, null);
         request.setBody(body);
diff --git 
a/auth/src/test/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilderTest.java
 
b/auth/src/test/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilderTest.java
index dbad9e9d01..f90352ef09 100644
--- 
a/auth/src/test/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilderTest.java
+++ 
b/auth/src/test/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilderTest.java
@@ -99,6 +99,7 @@ import 
org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader;
 import 
org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader;
 import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader;
 import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader;
+import 
org.apache.rocketmq.remoting.protocol.header.ResumeCheckHalfMessageRequestHeader;
 import org.apache.rocketmq.remoting.protocol.header.SearchOffsetRequestHeader;
 import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader;
 import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2;
@@ -409,7 +410,6 @@ public class DefaultAuthorizationContextBuilderTest {
         request.makeCustomHeaderToNet();
         RemotingCommand endTransactionWithoutTopic = request;
         Assert.assertTrue(builder.build(channelHandlerContext, 
endTransactionWithoutTopic).isEmpty());
-
         ConsumerSendMsgBackRequestHeader consumerSendMsgBackRequestHeader = 
new ConsumerSendMsgBackRequestHeader();
         consumerSendMsgBackRequestHeader.setGroup("group");
         request = 
RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, 
consumerSendMsgBackRequestHeader);
@@ -604,6 +604,14 @@ public class DefaultAuthorizationContextBuilderTest {
                 () -> builder.build(channelHandlerContext, request));
         }
 
+        ResumeCheckHalfMessageRequestHeader resumeHeader = new 
ResumeCheckHalfMessageRequestHeader();
+        resumeHeader.setMsgId("messageId");
+        Assert.assertThrows(AuthorizationException.class, () -> 
builder.build(channelHandlerContext,
+            remotingRequest(RequestCode.RESUME_CHECK_HALF_MESSAGE, 
resumeHeader, null)));
+        resumeHeader.setTopic(" ");
+        Assert.assertThrows(AuthorizationException.class, () -> 
builder.build(channelHandlerContext,
+            remotingRequest(RequestCode.RESUME_CHECK_HALF_MESSAGE, 
resumeHeader, null)));
+
         RemotingCommand sendBackRequest = 
remotingRequest(RequestCode.CONSUMER_SEND_MSG_BACK, null, null);
         sendBackRequest.addExtField("group", " ");
         Assert.assertThrows(AuthorizationException.class,
@@ -1044,11 +1052,13 @@ public class DefaultAuthorizationContextBuilderTest {
 
         ViewMessageRequestHeader viewMessageHeader = new 
ViewMessageRequestHeader();
         viewMessageHeader.setOffset(0L);
-        Assert.assertTrue(builder.build(channelHandlerContext,
-            remotingRequest(RequestCode.VIEW_MESSAGE_BY_ID, viewMessageHeader, 
null)).isEmpty());
+        Assert.assertThrows(AuthorizationException.class,
+            () -> builder.build(channelHandlerContext,
+                remotingRequest(RequestCode.VIEW_MESSAGE_BY_ID, 
viewMessageHeader, null)));
         viewMessageHeader.setTopic(" ");
-        Assert.assertTrue(builder.build(channelHandlerContext,
-            remotingRequest(RequestCode.VIEW_MESSAGE_BY_ID, viewMessageHeader, 
null)).isEmpty());
+        Assert.assertThrows(AuthorizationException.class,
+            () -> builder.build(channelHandlerContext,
+                remotingRequest(RequestCode.VIEW_MESSAGE_BY_ID, 
viewMessageHeader, null)));
     }
 
     @Test
diff --git 
a/auth/src/test/java/org/apache/rocketmq/auth/config/AuthConfigTest.java 
b/auth/src/test/java/org/apache/rocketmq/auth/config/AuthConfigTest.java
new file mode 100644
index 0000000000..1257fdc1ff
--- /dev/null
+++ b/auth/src/test/java/org/apache/rocketmq/auth/config/AuthConfigTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.rocketmq.auth.config;
+
+import com.alibaba.fastjson2.JSON;
+import java.util.Properties;
+import org.apache.rocketmq.common.MixAll;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AuthConfigTest {
+
+    @Test
+    public void authenticationRequiredTracksEnabledStateAndWhitelistUpdates() {
+        AuthConfig authConfig = new AuthConfig();
+
+        assertThat(authConfig.isAuthenticationRequired("10")).isFalse();
+
+        authConfig.setAuthenticationEnabled(true);
+        authConfig.setAuthenticationWhitelist(" 10, 11,10 ");
+        assertThat(authConfig.isAuthenticationRequired("10")).isFalse();
+        assertThat(authConfig.isAuthenticationRequired("11")).isFalse();
+        assertThat(authConfig.isAuthenticationRequired("12")).isTrue();
+        assertThat(authConfig.isAuthenticationRequired("")).isTrue();
+        assertThat(authConfig.isAuthenticationRequired(null)).isTrue();
+
+        authConfig.setAuthenticationWhitelist("12");
+        assertThat(authConfig.isAuthenticationRequired("10")).isTrue();
+        assertThat(authConfig.isAuthenticationRequired("12")).isFalse();
+    }
+
+    @Test
+    public void 
authenticationWhitelistPreservesConfigBindingAndCloneIsolation() {
+        String whitelist = " AUTH_A,AUTH_B,AUTH_A ";
+        AuthConfig authConfig = new AuthConfig();
+        authConfig.setAuthenticationEnabled(true);
+        authConfig.setAuthenticationWhitelist(whitelist);
+
+        Properties properties = MixAll.object2Properties(authConfig);
+        assertThat(properties)
+            .containsEntry("authenticationWhitelist", whitelist)
+            .doesNotContainKeys("entries", "value");
+
+        AuthConfig propertiesCopy = new AuthConfig();
+        MixAll.properties2Object(properties, propertiesCopy);
+        
assertThat(propertiesCopy.getAuthenticationWhitelist()).isEqualTo(whitelist.trim());
+        
assertThat(propertiesCopy.isAuthenticationRequired("AUTH_A")).isFalse();
+
+        AuthConfig jsonCopy = JSON.parseObject(JSON.toJSONString(authConfig), 
AuthConfig.class);
+        assertThat(jsonCopy.getAuthenticationWhitelist()).isEqualTo(whitelist);
+        assertThat(jsonCopy.isAuthenticationRequired("AUTH_B")).isFalse();
+
+        AuthConfig cloned = authConfig.clone();
+        authConfig.setAuthenticationWhitelist("AUTH_C");
+        assertThat(cloned.isAuthenticationRequired("AUTH_A")).isFalse();
+        assertThat(cloned.isAuthenticationRequired("AUTH_C")).isTrue();
+    }
+}
diff --git 
a/broker/src/main/java/org/apache/rocketmq/broker/auth/converter/UserConverter.java
 
b/broker/src/main/java/org/apache/rocketmq/broker/auth/converter/UserConverter.java
index 12756d8fd3..587c473c50 100644
--- 
a/broker/src/main/java/org/apache/rocketmq/broker/auth/converter/UserConverter.java
+++ 
b/broker/src/main/java/org/apache/rocketmq/broker/auth/converter/UserConverter.java
@@ -33,7 +33,6 @@ public class UserConverter {
     public static UserInfo convertUser(User user) {
         UserInfo result = new UserInfo();
         result.setUsername(user.getUsername());
-        result.setPassword(user.getPassword());
         if (user.getUserType() != null) {
             result.setUserType(user.getUserType().getName());
         }
diff --git 
a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
 
b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
index 876a72fa29..de90396064 100644
--- 
a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
+++ 
b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
@@ -35,6 +35,7 @@ import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Properties;
 import java.util.Set;
@@ -1177,8 +1178,15 @@ public class AdminBrokerProcessor implements 
NettyRequestProcessor {
         final RemotingCommand response = 
RemotingCommand.createResponseCommand(GetBrokerConfigResponseHeader.class);
         final GetBrokerConfigResponseHeader responseHeader = 
(GetBrokerConfigResponseHeader) response.readCustomHeader();
 
-        String content = 
this.brokerController.getConfiguration().getAllConfigsFormatString();
-        if (content != null && content.length() > 0) {
+        String content = 
sanitizeConfigForResponse(this.brokerController.getConfiguration().getAllConfigsFormatString());
+        if (content == null) {
+            LOGGER.error("AdminBrokerProcessor#getBrokerConfig: failed to 
sanitize broker config, caller={}",
+                RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
+            response.setCode(ResponseCode.SYSTEM_ERROR);
+            response.setRemark("Failed to sanitize broker config");
+            return response;
+        }
+        if (content.length() > 0) {
             try {
                 content = MixAll.adjustConfigForPlatform(content);
                 response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET));
@@ -1199,6 +1207,30 @@ public class AdminBrokerProcessor implements 
NettyRequestProcessor {
         return response;
     }
 
+    // sensitive config keys that must never be returned by config query 
interfaces
+    private static final String[] SENSITIVE_CONFIG_KEYS = new String[] {
+        "initAuthenticationUser", "innerClientAuthenticationCredentials"
+    };
+
+    /**
+     * Remove sensitive entries from the exported config content. Returns null 
when the
+     * content cannot be parsed, so callers must fail closed instead of 
returning the
+     * original content.
+     */
+    static String sanitizeConfigForResponse(String content) {
+        if (content == null || content.isEmpty()) {
+            return content;
+        }
+        Properties properties = MixAll.string2Properties(content);
+        if (properties == null) {
+            return null;
+        }
+        for (String key : SENSITIVE_CONFIG_KEYS) {
+            properties.remove(key);
+        }
+        return MixAll.properties2String(properties, true);
+    }
+
     private RemotingCommand 
rewriteRequestForStaticTopic(SearchOffsetRequestHeader requestHeader,
         TopicQueueMappingContext mappingContext) {
         try {
@@ -3031,6 +3063,12 @@ public class AdminBrokerProcessor implements 
NettyRequestProcessor {
             selectMappedBufferResult = this.brokerController.getMessageStore()
                 .selectOneMessageByOffset(messageId.getOffset());
             MessageExt msg = 
MessageDecoder.decode(selectMappedBufferResult.getByteBuffer(), true, false);
+            if (!Objects.equals(requestHeader.getTopic(),
+                msg.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC))) {
+                response.setCode(ResponseCode.NO_PERMISSION);
+                response.setRemark("The topic does not match the transaction 
message");
+                return response;
+            }
             msg.putUserProperty(MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES, 
String.valueOf(0));
             PutMessageResult putMessageResult = 
this.brokerController.getMessageStore()
                 .putMessage(toMessageExtBrokerInner(msg));
@@ -3349,6 +3387,9 @@ public class AdminBrokerProcessor implements 
NettyRequestProcessor {
                 response.setCode(ResponseCode.SUCCESS);
                 if (user != null) {
                     UserInfo userInfo = UserConverter.convertUser(user);
+                    if (canReadUserPassword(request, 
requestHeader.getUsername())) {
+                        userInfo.setPassword(user.getPassword());
+                    }
                     
response.setBody(JSON.toJSONString(userInfo).getBytes(StandardCharsets.UTF_8));
                 }
             })
@@ -3524,6 +3565,27 @@ public class AdminBrokerProcessor implements 
NettyRequestProcessor {
             .isSuperUser(accessKey).join();
     }
 
+    private boolean canReadUserPassword(RemotingCommand request, String 
username) {
+        if (this.brokerController.getAuthConfig() == null
+            || !this.brokerController.getAuthConfig()
+            .isAuthenticationRequired(String.valueOf(request.getCode()))) {
+            return false;
+        }
+        String accessKey = getAccessKey(request);
+        if (StringUtils.isEmpty(accessKey)) {
+            return false;
+        }
+        if (StringUtils.equals(accessKey, username)) {
+            return true;
+        }
+        return this.brokerController.getAuthenticationMetadataManager()
+            .isSuperUser(accessKey).join();
+    }
+
+    private String getAccessKey(RemotingCommand request) {
+        return request.getExtFields() == null ? null : 
request.getExtFields().get("AccessKey");
+    }
+
     private Void handleAuthException(RemotingCommand response, Throwable ex) {
         Throwable throwable = ExceptionUtils.getRealException(ex);
         if (throwable instanceof AuthenticationException || throwable 
instanceof AuthorizationException) {
diff --git 
a/broker/src/main/java/org/apache/rocketmq/broker/processor/EndTransactionProcessor.java
 
b/broker/src/main/java/org/apache/rocketmq/broker/processor/EndTransactionProcessor.java
index a792f80456..c568f1e4bf 100644
--- 
a/broker/src/main/java/org/apache/rocketmq/broker/processor/EndTransactionProcessor.java
+++ 
b/broker/src/main/java/org/apache/rocketmq/broker/processor/EndTransactionProcessor.java
@@ -17,6 +17,7 @@
 package org.apache.rocketmq.broker.processor;
 
 import io.netty.channel.ChannelHandlerContext;
+import java.util.Objects;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.rocketmq.broker.BrokerController;
 
@@ -236,6 +237,13 @@ public class EndTransactionProcessor implements 
NettyRequestProcessor {
     private RemotingCommand checkPrepareMessage(MessageExt msgExt, 
EndTransactionRequestHeader requestHeader) {
         final RemotingCommand response = 
RemotingCommand.createResponseCommand(null);
         if (msgExt != null) {
+            if (StringUtils.isNotBlank(requestHeader.getTopic())
+                && !Objects.equals(requestHeader.getTopic(),
+                msgExt.getProperty(MessageConst.PROPERTY_REAL_TOPIC))) {
+                response.setCode(ResponseCode.NO_PERMISSION);
+                response.setRemark("The topic does not match the transaction 
message");
+                return response;
+            }
             final String pgroupRead = 
msgExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP);
             if (!pgroupRead.equals(requestHeader.getProducerGroup())) {
                 response.setCode(ResponseCode.SYSTEM_ERROR);
diff --git 
a/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryMessageProcessor.java
 
b/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryMessageProcessor.java
index cb1c4287ce..1791782685 100644
--- 
a/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryMessageProcessor.java
+++ 
b/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryMessageProcessor.java
@@ -61,6 +61,7 @@ public class QueryMessageProcessor implements 
NettyRequestProcessor {
         TopicValidator.RMQ_SYS_SCHEDULE_TOPIC,
         TimerMessageStore.TIMER_TOPIC,
         TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC,
+        TopicValidator.RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC,
         TopicValidator.RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC));
     private final BrokerController brokerController;
 
diff --git 
a/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java
 
b/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java
index d1b77355b0..1084ac2081 100644
--- 
a/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java
+++ 
b/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java
@@ -50,7 +50,7 @@ public abstract class 
AbstractTransactionalMessageCheckListener {
 
     public void sendCheckMessage(MessageExt msgExt) throws Exception {
         CheckTransactionStateRequestHeader checkTransactionStateRequestHeader 
= new CheckTransactionStateRequestHeader();
-        checkTransactionStateRequestHeader.setTopic(msgExt.getTopic());
+        
checkTransactionStateRequestHeader.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC));
         
checkTransactionStateRequestHeader.setCommitLogOffset(msgExt.getCommitLogOffset());
         checkTransactionStateRequestHeader.setOffsetMsgId(msgExt.getMsgId());
         
checkTransactionStateRequestHeader.setMsgId(msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));
diff --git 
a/broker/src/main/java/org/apache/rocketmq/broker/transaction/rocksdb/TransactionalMessageRocksDBService.java
 
b/broker/src/main/java/org/apache/rocketmq/broker/transaction/rocksdb/TransactionalMessageRocksDBService.java
index dbd3575d69..d7940a726a 100644
--- 
a/broker/src/main/java/org/apache/rocketmq/broker/transaction/rocksdb/TransactionalMessageRocksDBService.java
+++ 
b/broker/src/main/java/org/apache/rocketmq/broker/transaction/rocksdb/TransactionalMessageRocksDBService.java
@@ -223,7 +223,7 @@ public class TransactionalMessageRocksDBService {
         }
         try {
             CheckTransactionStateRequestHeader 
checkTransactionStateRequestHeader = new CheckTransactionStateRequestHeader();
-            checkTransactionStateRequestHeader.setTopic(msgExt.getTopic());
+            
checkTransactionStateRequestHeader.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC));
             
checkTransactionStateRequestHeader.setCommitLogOffset(msgExt.getCommitLogOffset());
             
checkTransactionStateRequestHeader.setOffsetMsgId(msgExt.getMsgId());
             
checkTransactionStateRequestHeader.setMsgId(MessageClientIDSetter.getUniqID(msgExt));
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/auth/converter/UserConverterTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/auth/converter/UserConverterTest.java
new file mode 100644
index 0000000000..a514248bda
--- /dev/null
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/auth/converter/UserConverterTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.rocketmq.broker.auth.converter;
+
+import org.apache.rocketmq.auth.authentication.enums.UserType;
+import org.apache.rocketmq.auth.authentication.model.User;
+import org.apache.rocketmq.remoting.protocol.body.UserInfo;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UserConverterTest {
+
+    @Test
+    public void outboundConversionOmitsPasswordByDefault() {
+        UserInfo result = UserConverter.convertUser(
+            User.of("user", "secret", UserType.NORMAL));
+
+        assertThat(result.getUsername()).isEqualTo("user");
+        assertThat(result.getPassword()).isNull();
+        assertThat(result.getUserType()).isEqualTo("Normal");
+    }
+
+    @Test
+    public void inboundConversionRetainsPasswordForAuthentication() {
+        User result = UserConverter.convertUser(
+            UserInfo.of("user", "secret", "Normal"));
+
+        assertThat(result.getUsername()).isEqualTo("user");
+        assertThat(result.getPassword()).isEqualTo("secret");
+        assertThat(result.getUserType()).isEqualTo(UserType.NORMAL);
+    }
+}
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/auth/pipeline/AuthorizationPipelineTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/auth/pipeline/AuthorizationPipelineTest.java
index 0fa2ceb49e..b452e1f93e 100644
--- 
a/broker/src/test/java/org/apache/rocketmq/broker/auth/pipeline/AuthorizationPipelineTest.java
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/auth/pipeline/AuthorizationPipelineTest.java
@@ -25,6 +25,7 @@ import org.apache.rocketmq.common.AbortProcessException;
 import org.apache.rocketmq.remoting.protocol.RemotingCommand;
 import org.apache.rocketmq.remoting.protocol.RequestCode;
 import org.apache.rocketmq.remoting.protocol.ResponseCode;
+import org.apache.rocketmq.remoting.protocol.header.ViewMessageRequestHeader;
 import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData;
 import org.apache.rocketmq.remoting.protocol.heartbeat.ProducerData;
 import org.junit.Assert;
@@ -57,6 +58,19 @@ public class AuthorizationPipelineTest {
         Assert.assertEquals(ResponseCode.NO_PERMISSION, 
exception.getResponseCode());
     }
 
+    @Test
+    public void rejectsTopicLessViewMessageWithEmptyContexts() {
+        AuthorizationPipeline pipeline = createPipeline();
+        ViewMessageRequestHeader header = new ViewMessageRequestHeader();
+        header.setOffset(0L);
+        RemotingCommand request = 
RemotingCommand.createRequestCommand(RequestCode.VIEW_MESSAGE_BY_ID, header);
+        request.makeCustomHeaderToNet();
+
+        AbortProcessException exception = 
Assert.assertThrows(AbortProcessException.class,
+            () -> pipeline.execute(null, request));
+        Assert.assertEquals(ResponseCode.NO_PERMISSION, 
exception.getResponseCode());
+    }
+
     private AuthorizationPipeline createPipeline() {
         AuthConfig authConfig = new AuthConfig();
         authConfig.setConfigName("broker-authorization-pipeline-test");
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorConfigSanitizeTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorConfigSanitizeTest.java
new file mode 100644
index 0000000000..be78a8908f
--- /dev/null
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorConfigSanitizeTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.rocketmq.broker.processor;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class AdminBrokerProcessorConfigSanitizeTest {
+
+    @Test
+    public void testSanitizeRemovesSensitiveKeys() {
+        String content = "brokerName=broker-a\n"
+            + 
"initAuthenticationUser={\"username\":\"rocketmq\",\"password\":\"secret\"}\n"
+            + 
"innerClientAuthenticationCredentials={\"accessKey\":\"ak\",\"secretKey\":\"sk\"}\n"
+            + "listenPort=10911\n";
+
+        String sanitized = 
AdminBrokerProcessor.sanitizeConfigForResponse(content);
+
+        assertFalse(sanitized.contains("initAuthenticationUser"));
+        
assertFalse(sanitized.contains("innerClientAuthenticationCredentials"));
+        assertFalse(sanitized.contains("secret"));
+        assertTrue(sanitized.contains("brokerName=broker-a"));
+        assertTrue(sanitized.contains("listenPort=10911"));
+    }
+
+    @Test
+    public void testSanitizeFailsClosedOnUnparsableContent() {
+        // malformed unicode escape makes Properties.load throw
+        
assertNull(AdminBrokerProcessor.sanitizeConfigForResponse("key=\\uZZZZ\n"));
+    }
+
+    @Test
+    public void testSanitizePassesThroughNullOrEmpty() {
+        assertNull(AdminBrokerProcessor.sanitizeConfigForResponse(null));
+        assertEquals("", AdminBrokerProcessor.sanitizeConfigForResponse(""));
+    }
+}
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java
index 6a32adbe1d..ddadfae410 100644
--- 
a/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java
@@ -33,6 +33,7 @@ import 
org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManag
 import org.apache.rocketmq.auth.authorization.model.Acl;
 import org.apache.rocketmq.auth.authorization.model.Environment;
 import org.apache.rocketmq.auth.authorization.model.Resource;
+import org.apache.rocketmq.auth.config.AuthConfig;
 import org.apache.rocketmq.broker.BrokerController;
 import org.apache.rocketmq.broker.client.ClientChannelInfo;
 import org.apache.rocketmq.broker.client.ConsumerGroupInfo;
@@ -62,7 +63,9 @@ import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
 import org.apache.rocketmq.common.lite.LiteUtil;
 import org.apache.rocketmq.common.message.MessageAccessor;
 import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.message.MessageDecoder;
 import org.apache.rocketmq.common.message.MessageExt;
+import org.apache.rocketmq.common.message.MessageExtBrokerInner;
 import org.apache.rocketmq.common.topic.TopicValidator;
 import org.apache.rocketmq.remoting.exception.RemotingCommandException;
 import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
@@ -131,6 +134,8 @@ import 
org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfi
 import org.apache.rocketmq.store.CommitLog;
 import org.apache.rocketmq.store.DefaultMessageStore;
 import org.apache.rocketmq.store.MessageStore;
+import org.apache.rocketmq.store.PutMessageResult;
+import org.apache.rocketmq.store.PutMessageStatus;
 import org.apache.rocketmq.store.SelectMappedBufferResult;
 import org.apache.rocketmq.store.config.MessageStoreConfig;
 import org.apache.rocketmq.store.logfile.DefaultMappedFile;
@@ -144,6 +149,7 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnitRunner;
@@ -157,6 +163,7 @@ import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -184,6 +191,7 @@ import static org.mockito.ArgumentMatchers.anySet;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -199,9 +207,11 @@ public class AdminBrokerProcessorTest {
     @Mock
     private Channel channel;
 
+    private final AuthConfig authConfig = new AuthConfig();
+
     @Spy
     private BrokerController brokerController = new BrokerController(new 
BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(),
-        new MessageStoreConfig(), null);
+        new MessageStoreConfig(), authConfig);
 
     @Mock
     private MessageStore messageStore;
@@ -254,6 +264,8 @@ public class AdminBrokerProcessorTest {
 
     @Before
     public void init() throws Exception {
+        authConfig.setAuthenticationEnabled(true);
+        authConfig.setAuthenticationWhitelist(null);
         brokerController.setMessageStore(messageStore);
         
brokerController.setAuthenticationMetadataManager(authenticationMetadataManager);
         
brokerController.setAuthorizationMetadataManager(authorizationMetadataManager);
@@ -338,6 +350,36 @@ public class AdminBrokerProcessorTest {
         assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR);
     }
 
+    @Test
+    public void testResumeCheckHalfMessageRejectsMismatchedTopic() throws 
Exception {
+        RemotingCommand request = createResumeCheckHalfMessageCommand();
+        when(messageStore.selectOneMessageByOffset(any(Long.class)))
+            .thenReturn(createSelectMappedBufferResult("otherTopic"));
+
+        RemotingCommand response = 
adminBrokerProcessor.processRequest(handlerContext, request);
+
+        assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION);
+        assertThat(response.getRemark()).isEqualTo("The topic does not match 
the transaction message");
+        verify(messageStore, never()).putMessage(any());
+    }
+
+    @Test
+    public void testResumeCheckHalfMessageAcceptsMatchingTopic() throws 
Exception {
+        RemotingCommand request = createResumeCheckHalfMessageCommand();
+        when(messageStore.selectOneMessageByOffset(any(Long.class)))
+            .thenReturn(createSelectMappedBufferResult("topic"));
+        when(messageStore.putMessage(any(MessageExtBrokerInner.class)))
+            .thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, null));
+
+        RemotingCommand response = 
adminBrokerProcessor.processRequest(handlerContext, request);
+
+        assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
+        ArgumentCaptor<MessageExtBrokerInner> messageCaptor = 
ArgumentCaptor.forClass(MessageExtBrokerInner.class);
+        verify(messageStore).putMessage(messageCaptor.capture());
+        
assertThat(messageCaptor.getValue().getProperty(MessageConst.PROPERTY_REAL_TOPIC)).isEqualTo("topic");
+        
assertThat(messageCaptor.getValue().getProperty(MessageConst.PROPERTY_TRANSACTION_CHECK_TIMES)).isEqualTo("0");
+    }
+
     @Test
     public void testUpdateAndCreateTopicInRocksdb() throws Exception {
         if (notToBeExecuted()) {
@@ -1282,7 +1324,7 @@ public class AdminBrokerProcessorTest {
         getUserRequestHeader.setUsername("abc");
         RemotingCommand request = 
RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_USER, 
getUserRequestHeader);
         request.setVersion(441);
-        request.addExtField("AccessKey", "rocketmq");
+        request.addExtField("AccessKey", "abc");
         request.makeCustomHeaderToNet();
         RemotingCommand response = 
adminBrokerProcessor.processRequest(handlerContext, request);
         assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
@@ -1292,9 +1334,54 @@ public class AdminBrokerProcessorTest {
         assertThat(userInfo.getUserType()).isEqualTo("Normal");
     }
 
+    @Test
+    public void testGetUserBySuperUser() throws RemotingCommandException {
+        when(authenticationMetadataManager.isSuperUser(eq("rocketmq")))
+            .thenReturn(CompletableFuture.completedFuture(true));
+        when(authenticationMetadataManager.getUser(eq("abc")))
+            .thenReturn(CompletableFuture.completedFuture(
+                User.of("abc", "normal-user-secret", UserType.NORMAL)));
+
+        GetUserRequestHeader requestHeader = new GetUserRequestHeader("abc");
+        RemotingCommand request = 
RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_USER, requestHeader);
+        request.addExtField("AccessKey", "rocketmq");
+        request.makeCustomHeaderToNet();
+
+        RemotingCommand response = 
adminBrokerProcessor.processRequest(handlerContext, request);
+        assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
+        UserInfo userInfo = JSON.parseObject(
+            new String(response.getBody(), StandardCharsets.UTF_8), 
UserInfo.class);
+        assertThat(userInfo.getPassword()).isEqualTo("normal-user-secret");
+    }
+
+    @Test
+    public void testGetUserByOtherNormalUserDoesNotReturnPassword() throws 
RemotingCommandException {
+        when(authenticationMetadataManager.isSuperUser(eq("other")))
+            .thenReturn(CompletableFuture.completedFuture(false));
+        when(authenticationMetadataManager.getUser(eq("abc")))
+            .thenReturn(CompletableFuture.completedFuture(
+                User.of("abc", "normal-user-secret", UserType.NORMAL)));
+
+        GetUserRequestHeader requestHeader = new GetUserRequestHeader("abc");
+        RemotingCommand request = 
RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_USER, requestHeader);
+        request.addExtField("AccessKey", "other");
+        request.makeCustomHeaderToNet();
+
+        RemotingCommand response = 
adminBrokerProcessor.processRequest(handlerContext, request);
+        assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
+        String content = new String(response.getBody(), 
StandardCharsets.UTF_8);
+        UserInfo userInfo = JSON.parseObject(content, UserInfo.class);
+        assertThat(userInfo.getUsername()).isEqualTo("abc");
+        assertThat(userInfo.getPassword()).isNull();
+        assertThat(content).doesNotContain("password", "normal-user-secret");
+    }
+
     @Test
     public void testListUser() throws RemotingCommandException {
-        
when(authenticationMetadataManager.listUser(eq("abc"))).thenReturn(CompletableFuture.completedFuture(Arrays.asList(User.of("abc",
 "123", UserType.NORMAL))));
+        when(authenticationMetadataManager.listUser(eq("abc"))).thenReturn(
+            CompletableFuture.completedFuture(Arrays.asList(
+                User.of("abc", "normal-user-secret", UserType.NORMAL),
+                User.of("super", "super-user-secret", UserType.SUPER))));
 
         ListUsersRequestHeader listUserRequestHeader = new 
ListUsersRequestHeader();
         listUserRequestHeader.setFilter("abc");
@@ -1304,10 +1391,81 @@ public class AdminBrokerProcessorTest {
         request.makeCustomHeaderToNet();
         RemotingCommand response = 
adminBrokerProcessor.processRequest(handlerContext, request);
         assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
-        List<UserInfo> userInfo = JSON.parseArray(new 
String(response.getBody()), UserInfo.class);
-        assertThat(userInfo.get(0).getUsername()).isEqualTo("abc");
-        assertThat(userInfo.get(0).getPassword()).isEqualTo("123");
-        assertThat(userInfo.get(0).getUserType()).isEqualTo("Normal");
+        String content = new String(response.getBody(), 
StandardCharsets.UTF_8);
+        List<UserInfo> users = JSON.parseArray(content, UserInfo.class);
+        assertThat(users.get(0).getUsername()).isEqualTo("abc");
+        assertThat(users.get(0).getUserType()).isEqualTo("Normal");
+        assertThat(users).allMatch(user -> user.getPassword() == null);
+        assertThat(content).doesNotContain("password", "normal-user-secret", 
"super-user-secret");
+    }
+
+    @Test
+    public void testListUserByNormalUserDoesNotReturnPassword() throws 
RemotingCommandException {
+        when(authenticationMetadataManager.listUser(eq("abc")))
+            
.thenReturn(CompletableFuture.completedFuture(Collections.singletonList(
+                User.of("abc", "normal-user-secret", UserType.NORMAL))));
+
+        ListUsersRequestHeader requestHeader = new 
ListUsersRequestHeader("abc");
+        RemotingCommand request = 
RemotingCommand.createRequestCommand(RequestCode.AUTH_LIST_USER, requestHeader);
+        request.addExtField("AccessKey", "abc");
+        request.makeCustomHeaderToNet();
+
+        RemotingCommand response = 
adminBrokerProcessor.processRequest(handlerContext, request);
+        String content = new String(response.getBody(), 
StandardCharsets.UTF_8);
+        List<UserInfo> users = JSON.parseArray(content, UserInfo.class);
+        assertThat(users).hasSize(1);
+        assertThat(users.get(0).getPassword()).isNull();
+        assertThat(content).doesNotContain("password", "normal-user-secret");
+        verify(authenticationMetadataManager, 
never()).isSuperUser(anyString());
+    }
+
+    @Test
+    public void testGetUserWithoutAuthenticatedIdentityDoesNotReturnPassword() 
throws RemotingCommandException {
+        when(authenticationMetadataManager.getUser(eq("abc")))
+            .thenReturn(CompletableFuture.completedFuture(
+                User.of("abc", "normal-user-secret", UserType.NORMAL)));
+        GetUserRequestHeader requestHeader = new GetUserRequestHeader("abc");
+        RemotingCommand request = 
RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_USER, requestHeader);
+        request.makeCustomHeaderToNet();
+
+        RemotingCommand response = 
adminBrokerProcessor.processRequest(handlerContext, request);
+        String content = new String(response.getBody(), 
StandardCharsets.UTF_8);
+        assertThat(JSON.parseObject(content, 
UserInfo.class).getPassword()).isNull();
+        assertThat(content).doesNotContain("password", "normal-user-secret");
+    }
+
+    @Test
+    public void testGetUserDoesNotTrustAccessKeyWhenAuthenticationDisabled() 
throws RemotingCommandException {
+        authConfig.setAuthenticationEnabled(false);
+        when(authenticationMetadataManager.getUser(eq("abc")))
+            .thenReturn(CompletableFuture.completedFuture(
+                User.of("abc", "normal-user-secret", UserType.NORMAL)));
+        GetUserRequestHeader requestHeader = new GetUserRequestHeader("abc");
+        RemotingCommand request = 
RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_USER, requestHeader);
+        request.addExtField("AccessKey", "abc");
+        request.makeCustomHeaderToNet();
+
+        RemotingCommand response = 
adminBrokerProcessor.processRequest(handlerContext, request);
+        String content = new String(response.getBody(), 
StandardCharsets.UTF_8);
+        assertThat(JSON.parseObject(content, 
UserInfo.class).getPassword()).isNull();
+        assertThat(content).doesNotContain("password", "normal-user-secret");
+    }
+
+    @Test
+    public void 
testGetUserDoesNotTrustAccessKeyWhenAuthenticationIsWhitelisted() throws 
RemotingCommandException {
+        
authConfig.setAuthenticationWhitelist(String.valueOf(RequestCode.AUTH_GET_USER));
+        when(authenticationMetadataManager.getUser(eq("abc")))
+            .thenReturn(CompletableFuture.completedFuture(
+                User.of("abc", "normal-user-secret", UserType.NORMAL)));
+        GetUserRequestHeader requestHeader = new GetUserRequestHeader("abc");
+        RemotingCommand request = 
RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_USER, requestHeader);
+        request.addExtField("AccessKey", "abc");
+        request.makeCustomHeaderToNet();
+
+        RemotingCommand response = 
adminBrokerProcessor.processRequest(handlerContext, request);
+        String content = new String(response.getBody(), 
StandardCharsets.UTF_8);
+        assertThat(JSON.parseObject(content, 
UserInfo.class).getPassword()).isNull();
+        assertThat(content).doesNotContain("password", "normal-user-secret");
     }
 
     @Test
@@ -1895,6 +2053,17 @@ public class AdminBrokerProcessorTest {
         return result;
     }
 
+    private SelectMappedBufferResult createSelectMappedBufferResult(String 
realTopic) throws Exception {
+        MessageExt message = createDefaultMessageExt();
+        message.setBody("body".getBytes(StandardCharsets.UTF_8));
+        message.setTopic(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC);
+        message.setBornHost(new InetSocketAddress("127.0.0.1", 10911));
+        message.setStoreHost(new InetSocketAddress("127.0.0.1", 10911));
+        MessageAccessor.putProperty(message, MessageConst.PROPERTY_REAL_TOPIC, 
realTopic);
+        ByteBuffer buffer = ByteBuffer.wrap(MessageDecoder.encode(message, 
false));
+        return new SelectMappedBufferResult(0, buffer, buffer.remaining(), 
null);
+    }
+
     private ResumeCheckHalfMessageRequestHeader 
createResumeCheckHalfMessageRequestHeader() {
         ResumeCheckHalfMessageRequestHeader header = new 
ResumeCheckHalfMessageRequestHeader();
         header.setTopic("topic");
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/processor/EndTransactionProcessorTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/processor/EndTransactionProcessorTest.java
index 42af2dbe47..7b2d4bdf26 100644
--- 
a/broker/src/test/java/org/apache/rocketmq/broker/processor/EndTransactionProcessorTest.java
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/processor/EndTransactionProcessorTest.java
@@ -29,6 +29,7 @@ import org.apache.rocketmq.common.message.MessageExt;
 import org.apache.rocketmq.common.message.MessageExtBrokerInner;
 import org.apache.rocketmq.common.stats.Stats;
 import org.apache.rocketmq.common.sysflag.MessageSysFlag;
+import org.apache.rocketmq.common.topic.TopicValidator;
 import org.apache.rocketmq.remoting.exception.RemotingCommandException;
 import org.apache.rocketmq.remoting.netty.NettyClientConfig;
 import org.apache.rocketmq.remoting.netty.NettyServerConfig;
@@ -53,6 +54,8 @@ import java.nio.charset.StandardCharsets;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -123,6 +126,33 @@ public class EndTransactionProcessorTest {
         
assertThat(brokerController.getBrokerStatsManager().getStatsItem(Stats.TOPIC_PUT_SIZE,
 TOPIC).getValue().sum()).isEqualTo(1L);
     }
 
+    @Test
+    public void testProcessRequestRejectsMismatchedTopic() throws 
RemotingCommandException {
+        
when(transactionMsgService.commitMessage(any(EndTransactionRequestHeader.class)))
+            .thenReturn(createResponse(ResponseCode.SUCCESS));
+        RemotingCommand request = createEndTransactionMsgCommand(
+            MessageSysFlag.TRANSACTION_COMMIT_TYPE, false, "allowedTopic");
+
+        RemotingCommand response = 
endTransactionProcessor.processRequest(handlerContext, request);
+
+        assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION);
+        verify(messageStore, 
never()).putMessage(any(MessageExtBrokerInner.class));
+        verify(transactionMsgService, 
never()).deletePrepareMessage(any(MessageExt.class));
+    }
+
+    @Test
+    public void testProcessRequestRejectsMismatchedTopicOnRollback() throws 
RemotingCommandException {
+        
when(transactionMsgService.rollbackMessage(any(EndTransactionRequestHeader.class)))
+            .thenReturn(createResponse(ResponseCode.SUCCESS));
+        RemotingCommand request = createEndTransactionMsgCommand(
+            MessageSysFlag.TRANSACTION_ROLLBACK_TYPE, false, "allowedTopic");
+
+        RemotingCommand response = 
endTransactionProcessor.processRequest(handlerContext, request);
+
+        assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION);
+        verify(transactionMsgService, 
never()).deletePrepareMessage(any(MessageExt.class));
+    }
+
     @Test
     public void testProcessRequest_NotType() throws RemotingCommandException {
         RemotingCommand request = 
createEndTransactionMsgCommand(MessageSysFlag.TRANSACTION_NOT_TYPE, true);
@@ -175,6 +205,7 @@ public class EndTransactionProcessorTest {
     private MessageExt createDefaultMessageExt() {
         MessageExt messageExt = new MessageExt();
         messageExt.setMsgId("12345678");
+        messageExt.setTopic(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC);
         messageExt.setQueueId(0);
         messageExt.setCommitLogOffset(123456789L);
         messageExt.setQueueOffset(1234);
@@ -199,7 +230,12 @@ public class EndTransactionProcessorTest {
     }
 
     private RemotingCommand createEndTransactionMsgCommand(int status, boolean 
isCheckMsg) {
+        return createEndTransactionMsgCommand(status, isCheckMsg, TOPIC);
+    }
+
+    private RemotingCommand createEndTransactionMsgCommand(int status, boolean 
isCheckMsg, String topic) {
         EndTransactionRequestHeader header = 
createEndTransactionRequestHeader(status, isCheckMsg);
+        header.setTopic(topic);
         RemotingCommand request = 
RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, header);
         request.makeCustomHeaderToNet();
         return request;
@@ -215,6 +251,7 @@ public class EndTransactionProcessorTest {
     private MessageExt createRejectMessageExt() {
         MessageExt messageExt = new MessageExt();
         messageExt.setMsgId("12345678");
+        messageExt.setTopic(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC);
         messageExt.setQueueId(0);
         messageExt.setCommitLogOffset(123456789L);
         messageExt.setQueueOffset(1234);
@@ -223,7 +260,7 @@ public class EndTransactionProcessorTest {
         MessageAccessor.putProperty(messageExt, 
MessageConst.PROPERTY_REAL_QUEUE_ID, "0");
         MessageAccessor.putProperty(messageExt, 
MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
         MessageAccessor.putProperty(messageExt, 
MessageConst.PROPERTY_PRODUCER_GROUP, "testTransactionGroup");
-        MessageAccessor.putProperty(messageExt, 
MessageConst.PROPERTY_REAL_TOPIC, "TEST");
+        MessageAccessor.putProperty(messageExt, 
MessageConst.PROPERTY_REAL_TOPIC, TOPIC);
         MessageAccessor.putProperty(messageExt, 
MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS, "60");
         return messageExt;
     }
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/processor/QueryMessageProcessorTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/processor/QueryMessageProcessorTest.java
index f842e8da6b..bc77a3a0cb 100644
--- 
a/broker/src/test/java/org/apache/rocketmq/broker/processor/QueryMessageProcessorTest.java
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/processor/QueryMessageProcessorTest.java
@@ -189,13 +189,14 @@ public class QueryMessageProcessorTest {
             TopicValidator.RMQ_SYS_SCHEDULE_TOPIC,
             TimerMessageStore.TIMER_TOPIC,
             TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC,
+            TopicValidator.RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC,
             TopicValidator.RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC)) {
             when(messageStore.selectOneMessageByOffset(0L))
                 .thenReturn(messageResult(storedTopic, "actualTopic"));
             
Assert.assertNull(queryMessageProcessor.processRequest(handlerContext, 
request));
         }
 
-        verify(channel, times(4)).writeAndFlush(any());
+        verify(channel, times(5)).writeAndFlush(any());
     }
 
     @Test
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListenerTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListenerTest.java
index 986b15aa09..3b5cf09282 100644
--- 
a/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListenerTest.java
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/transaction/queue/DefaultTransactionalMessageCheckListenerTest.java
@@ -16,8 +16,11 @@
  */
 package org.apache.rocketmq.broker.transaction.queue;
 
+import io.netty.channel.Channel;
 import java.net.InetSocketAddress;
 import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.client.ProducerManager;
+import org.apache.rocketmq.broker.client.net.Broker2Client;
 import org.apache.rocketmq.common.BrokerConfig;
 import org.apache.rocketmq.common.message.MessageAccessor;
 import org.apache.rocketmq.common.message.MessageConst;
@@ -26,22 +29,35 @@ import 
org.apache.rocketmq.common.message.MessageExtBrokerInner;
 import org.apache.rocketmq.common.topic.TopicValidator;
 import org.apache.rocketmq.remoting.netty.NettyClientConfig;
 import org.apache.rocketmq.remoting.netty.NettyServerConfig;
+import 
org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader;
 import org.apache.rocketmq.store.MessageStore;
 import org.apache.rocketmq.store.config.MessageStoreConfig;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 @RunWith(MockitoJUnitRunner.class)
 public class DefaultTransactionalMessageCheckListenerTest {
 
     private DefaultTransactionalMessageCheckListener listener;
     @Mock
     private MessageStore messageStore;
+    @Mock
+    private ProducerManager producerManager;
+    @Mock
+    private Broker2Client broker2Client;
+    @Mock
+    private Channel channel;
 
     @Spy
     private BrokerController brokerController = new BrokerController(new 
BrokerConfig(),
@@ -69,7 +85,21 @@ public class DefaultTransactionalMessageCheckListenerTest {
     @Test
     public void testSendCheckMessage() throws Exception {
         MessageExt messageExt = createMessageExt();
+        String producerGroup = "producerGroup";
+        messageExt.setTopic(TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC);
+        MessageAccessor.putProperty(messageExt, 
MessageConst.PROPERTY_PRODUCER_GROUP, producerGroup);
+        
when(brokerController.getProducerManager()).thenReturn(producerManager);
+        
when(producerManager.getAvailableChannel(producerGroup)).thenReturn(channel);
+        when(brokerController.getBroker2Client()).thenReturn(broker2Client);
+
         listener.sendCheckMessage(messageExt);
+
+        ArgumentCaptor<CheckTransactionStateRequestHeader> headerCaptor =
+            ArgumentCaptor.forClass(CheckTransactionStateRequestHeader.class);
+        verify(broker2Client).checkProducerTransactionState(
+            eq(producerGroup), eq(channel), headerCaptor.capture(), 
eq(messageExt));
+        assertThat(headerCaptor.getValue().getTopic()).isEqualTo("realTopic");
+        assertThat(messageExt.getTopic()).isEqualTo("realTopic");
     }
 
     @Test
diff --git 
a/broker/src/test/java/org/apache/rocketmq/broker/transaction/rocksdb/TransactionalMessageRocksDBServiceTest.java
 
b/broker/src/test/java/org/apache/rocketmq/broker/transaction/rocksdb/TransactionalMessageRocksDBServiceTest.java
new file mode 100644
index 0000000000..c8940fc85d
--- /dev/null
+++ 
b/broker/src/test/java/org/apache/rocketmq/broker/transaction/rocksdb/TransactionalMessageRocksDBServiceTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.rocketmq.broker.transaction.rocksdb;
+
+import io.netty.channel.Channel;
+import java.lang.reflect.Method;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.client.ProducerManager;
+import org.apache.rocketmq.broker.client.net.Broker2Client;
+import org.apache.rocketmq.common.BrokerConfig;
+import org.apache.rocketmq.common.message.MessageAccessor;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.message.MessageExt;
+import org.apache.rocketmq.common.topic.TopicValidator;
+import 
org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader;
+import org.apache.rocketmq.store.MessageStore;
+import org.apache.rocketmq.store.rocksdb.MessageRocksDBStorage;
+import org.apache.rocketmq.store.transaction.TransMessageRocksDBStore;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TransactionalMessageRocksDBServiceTest {
+    @Mock
+    private MessageStore messageStore;
+    @Mock
+    private TransMessageRocksDBStore transMessageRocksDBStore;
+    @Mock
+    private MessageRocksDBStorage messageRocksDBStorage;
+    @Mock
+    private BrokerController brokerController;
+    @Mock
+    private BrokerConfig brokerConfig;
+    @Mock
+    private ProducerManager producerManager;
+    @Mock
+    private Broker2Client broker2Client;
+    @Mock
+    private Channel channel;
+
+    private TransactionalMessageRocksDBService service;
+
+    @Before
+    public void setUp() {
+        
when(messageStore.getTransMessageRocksDBStore()).thenReturn(transMessageRocksDBStore);
+        
when(transMessageRocksDBStore.getMessageRocksDBStorage()).thenReturn(messageRocksDBStorage);
+        service = new TransactionalMessageRocksDBService(messageStore, 
brokerController);
+    }
+
+    @Test
+    public void testSendCheckMessageUsesRealTopicInRequestHeader() throws 
Exception {
+        String realTopic = "realTopic";
+        String producerGroup = "producerGroup";
+        MessageExt messageExt = new MessageExt();
+        messageExt.setTopic(TopicValidator.RMQ_SYS_ROCKSDB_TRANS_HALF_TOPIC);
+        MessageAccessor.putProperty(messageExt, 
MessageConst.PROPERTY_REAL_TOPIC, realTopic);
+        MessageAccessor.putProperty(messageExt, 
MessageConst.PROPERTY_REAL_QUEUE_ID, "1");
+        MessageAccessor.putProperty(messageExt, 
MessageConst.PROPERTY_PRODUCER_GROUP, producerGroup);
+        MessageAccessor.putProperty(messageExt, 
MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "messageId");
+        when(brokerController.getBrokerConfig()).thenReturn(brokerConfig);
+        
when(brokerController.getProducerManager()).thenReturn(producerManager);
+        
when(producerManager.getAvailableChannel(producerGroup)).thenReturn(channel);
+        when(brokerController.getBroker2Client()).thenReturn(broker2Client);
+
+        Method sendCheckMessage = TransactionalMessageRocksDBService.class
+            .getDeclaredMethod("sendCheckMessage", MessageExt.class);
+        sendCheckMessage.setAccessible(true);
+        sendCheckMessage.invoke(service, messageExt);
+
+        ArgumentCaptor<CheckTransactionStateRequestHeader> headerCaptor =
+            ArgumentCaptor.forClass(CheckTransactionStateRequestHeader.class);
+        verify(broker2Client).checkProducerTransactionState(
+            eq(producerGroup), eq(channel), headerCaptor.capture(), 
eq(messageExt));
+        assertThat(headerCaptor.getValue().getTopic()).isEqualTo(realTopic);
+        assertThat(messageExt.getTopic()).isEqualTo(realTopic);
+    }
+}
diff --git 
a/client/src/test/java/org/apache/rocketmq/client/producer/selector/DefaultMQProducerImplTest.java
 
b/client/src/test/java/org/apache/rocketmq/client/producer/selector/DefaultMQProducerImplTest.java
index 77a83af19c..bb61f9afd9 100644
--- 
a/client/src/test/java/org/apache/rocketmq/client/producer/selector/DefaultMQProducerImplTest.java
+++ 
b/client/src/test/java/org/apache/rocketmq/client/producer/selector/DefaultMQProducerImplTest.java
@@ -27,6 +27,7 @@ import org.apache.rocketmq.client.impl.MQClientAPIImpl;
 import org.apache.rocketmq.client.impl.factory.MQClientInstance;
 import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl;
 import org.apache.rocketmq.client.impl.producer.TopicPublishInfo;
+import org.apache.rocketmq.client.producer.LocalTransactionState;
 import org.apache.rocketmq.client.producer.MessageQueueSelector;
 import org.apache.rocketmq.client.producer.RequestCallback;
 import org.apache.rocketmq.client.producer.SendCallback;
@@ -36,15 +37,18 @@ import 
org.apache.rocketmq.client.producer.TransactionMQProducer;
 import org.apache.rocketmq.common.MixAll;
 import org.apache.rocketmq.common.ServiceState;
 import org.apache.rocketmq.common.message.Message;
+import org.apache.rocketmq.common.message.MessageAccessor;
 import org.apache.rocketmq.common.message.MessageConst;
 import org.apache.rocketmq.common.message.MessageExt;
 import org.apache.rocketmq.common.message.MessageQueue;
 import org.apache.rocketmq.common.producer.RecallMessageHandle;
 import org.apache.rocketmq.remoting.exception.RemotingException;
 import 
org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader;
+import 
org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
@@ -66,10 +70,13 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.AdditionalMatchers.or;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -145,6 +152,39 @@ public class DefaultMQProducerImplTest {
         defaultMQProducerImpl.checkTransactionState(defaultBrokerAddr, 
mock(MessageExt.class), mock(CheckTransactionStateRequestHeader.class));
     }
 
+    @Test
+    public void testCheckTransactionStateEchoesTopicToEndTransaction() throws 
Exception {
+        String realTopic = "realTopic";
+        MessageExt messageExt = new MessageExt();
+        messageExt.setMsgId("messageId");
+        MessageAccessor.putProperty(
+            messageExt, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, 
"messageId");
+        CheckTransactionStateRequestHeader checkHeader = new 
CheckTransactionStateRequestHeader();
+        checkHeader.setTopic(realTopic);
+        checkHeader.setCommitLogOffset(123L);
+        checkHeader.setTranStateTableOffset(456L);
+        TransactionListener transactionListener = 
mock(TransactionListener.class);
+        when(transactionListener.checkLocalTransaction(messageExt))
+            .thenReturn(LocalTransactionState.COMMIT_MESSAGE);
+        ((TransactionMQProducer) defaultMQProducerImpl.getDefaultMQProducer())
+            .setTransactionListener(transactionListener);
+        ExecutorService checkExecutor = mock(ExecutorService.class);
+        doAnswer(invocation -> {
+            invocation.<Runnable>getArgument(0).run();
+            return null;
+        }).when(checkExecutor).submit(any(Runnable.class));
+        setField(defaultMQProducerImpl, "checkExecutor", checkExecutor);
+
+        defaultMQProducerImpl.checkTransactionState(defaultBrokerAddr, 
messageExt, checkHeader);
+
+        ArgumentCaptor<EndTransactionRequestHeader> endHeaderCaptor =
+            ArgumentCaptor.forClass(EndTransactionRequestHeader.class);
+        verify(mQClientAPIImpl).endTransactionOneway(
+            eq(defaultBrokerAddr), endHeaderCaptor.capture(), isNull(), 
eq(3000L));
+        assertEquals(realTopic, endHeaderCaptor.getValue().getTopic());
+        assertTrue(endHeaderCaptor.getValue().getFromTransactionCheck());
+    }
+
     @Test
     public void testCreateTopic() throws MQClientException {
         defaultMQProducerImpl.createTopic("key", defaultTopic, 0);
diff --git 
a/container/src/main/java/org/apache/rocketmq/container/BrokerContainerProcessor.java
 
b/container/src/main/java/org/apache/rocketmq/container/BrokerContainerProcessor.java
index cfedb24aee..5d534965fe 100644
--- 
a/container/src/main/java/org/apache/rocketmq/container/BrokerContainerProcessor.java
+++ 
b/container/src/main/java/org/apache/rocketmq/container/BrokerContainerProcessor.java
@@ -280,6 +280,30 @@ public class BrokerContainerProcessor implements 
NettyRequestProcessor {
         return response;
     }
 
+    // sensitive config keys that must never be returned by config query 
interfaces
+    private static final String[] SENSITIVE_CONFIG_KEYS = new String[] {
+        "initAuthenticationUser", "innerClientAuthenticationCredentials"
+    };
+
+    /**
+     * Remove sensitive entries from the exported config content. Returns null 
when the
+     * content cannot be parsed, so callers must fail closed instead of 
returning the
+     * original content.
+     */
+    static String sanitizeConfigForResponse(String content) {
+        if (content == null || content.isEmpty()) {
+            return content;
+        }
+        Properties properties = MixAll.string2Properties(content);
+        if (properties == null) {
+            return null;
+        }
+        for (String key : SENSITIVE_CONFIG_KEYS) {
+            properties.remove(key);
+        }
+        return MixAll.properties2String(properties, true);
+    }
+
     private boolean validateBlackListConfigExist(Properties properties) {
         for (String blackConfig : configBlackList) {
             if (properties.containsKey(blackConfig)) {
@@ -294,8 +318,14 @@ public class BrokerContainerProcessor implements 
NettyRequestProcessor {
         final RemotingCommand response = 
RemotingCommand.createResponseCommand(GetBrokerConfigResponseHeader.class);
         final GetBrokerConfigResponseHeader responseHeader = 
(GetBrokerConfigResponseHeader) response.readCustomHeader();
 
-        String content = 
this.brokerContainer.getConfiguration().getAllConfigsFormatString();
-        if (content != null && content.length() > 0) {
+        String content = 
sanitizeConfigForResponse(this.brokerContainer.getConfiguration().getAllConfigsFormatString());
+        if (content == null) {
+            LOGGER.error("BrokerContainerProcessor#getBrokerConfig: failed to 
sanitize broker config");
+            response.setCode(ResponseCode.SYSTEM_ERROR);
+            response.setRemark("Failed to sanitize broker config");
+            return response;
+        }
+        if (content.length() > 0) {
             try {
                 content = MixAll.adjustConfigForPlatform(content);
                 response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET));
diff --git 
a/container/src/test/java/org/apache/rocketmq/container/BrokerContainerProcessorConfigSanitizeTest.java
 
b/container/src/test/java/org/apache/rocketmq/container/BrokerContainerProcessorConfigSanitizeTest.java
new file mode 100644
index 0000000000..0aaa6d2c69
--- /dev/null
+++ 
b/container/src/test/java/org/apache/rocketmq/container/BrokerContainerProcessorConfigSanitizeTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.rocketmq.container;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class BrokerContainerProcessorConfigSanitizeTest {
+
+    @Test
+    public void testSanitizeRemovesSensitiveKeys() {
+        String content = "brokerName=broker-a\n"
+            + 
"initAuthenticationUser={\"username\":\"rocketmq\",\"password\":\"secret\"}\n"
+            + 
"innerClientAuthenticationCredentials={\"accessKey\":\"ak\",\"secretKey\":\"sk\"}\n"
+            + "listenPort=10911\n";
+
+        String sanitized = 
BrokerContainerProcessor.sanitizeConfigForResponse(content);
+
+        assertFalse(sanitized.contains("initAuthenticationUser"));
+        
assertFalse(sanitized.contains("innerClientAuthenticationCredentials"));
+        assertFalse(sanitized.contains("secret"));
+        assertTrue(sanitized.contains("brokerName=broker-a"));
+        assertTrue(sanitized.contains("listenPort=10911"));
+    }
+
+    @Test
+    public void testSanitizeFailsClosedOnUnparsableContent() {
+        // malformed unicode escape makes Properties.load throw
+        
assertNull(BrokerContainerProcessor.sanitizeConfigForResponse("key=\\uZZZZ\n"));
+    }
+
+    @Test
+    public void testSanitizePassesThroughNullOrEmpty() {
+        assertNull(BrokerContainerProcessor.sanitizeConfigForResponse(null));
+        assertEquals("", 
BrokerContainerProcessor.sanitizeConfigForResponse(""));
+    }
+}
diff --git 
a/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/GrpcUtils.java 
b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/GrpcUtils.java
index 5c50de4426..0e4a72d26f 100644
--- a/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/GrpcUtils.java
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/GrpcUtils.java
@@ -35,6 +35,19 @@ public class GrpcUtils {
         }
     }
 
+    /**
+     * Replaces the current value for a metadata key. A null value clears the 
key.
+     */
+    public static <T> void putHeader(Metadata headers, Metadata.Key<T> key, T 
value) {
+        if (headers == null) {
+            return;
+        }
+        headers.discardAll(key);
+        if (value != null) {
+            headers.put(key, value);
+        }
+    }
+
     public static <R, W, T> T getAttribute(ServerCall<R, W> call, 
Attributes.Key<T> key) {
         Attributes attributes = call.getAttributes();
         if (attributes == null) {
diff --git 
a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptor.java
 
b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptor.java
index e3e7884155..cd3db34f73 100644
--- 
a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptor.java
+++ 
b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptor.java
@@ -40,16 +40,19 @@ public class HeaderInterceptor implements ServerInterceptor 
{
         Metadata headers,
         ServerCallHandler<R, W> next
     ) {
+        // The authorization subject must only come from verified 
authentication context.
+        GrpcUtils.putHeader(headers, GrpcConstants.AUTHORIZATION_AK, null);
+
         String remoteAddress = getProxyProtocolAddress(call.getAttributes());
         if (StringUtils.isBlank(remoteAddress)) {
             SocketAddress remoteSocketAddress = 
call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
             remoteAddress = parseSocketAddress(remoteSocketAddress);
         }
-        GrpcUtils.putHeaderIfNotExist(headers, GrpcConstants.REMOTE_ADDRESS, 
remoteAddress);
+        GrpcUtils.putHeader(headers, GrpcConstants.REMOTE_ADDRESS, 
remoteAddress);
 
         SocketAddress localSocketAddress = 
call.getAttributes().get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR);
         String localAddress = parseSocketAddress(localSocketAddress);
-        GrpcUtils.putHeaderIfNotExist(headers, GrpcConstants.LOCAL_ADDRESS, 
localAddress);
+        GrpcUtils.putHeader(headers, GrpcConstants.LOCAL_ADDRESS, 
localAddress);
 
         for (Attributes.Key<?> key : call.getAttributes().keys()) {
             if (!StringUtils.startsWith(key.toString(), 
HAProxyConstants.PROXY_PROTOCOL_PREFIX)) {
@@ -58,12 +61,12 @@ public class HeaderInterceptor implements ServerInterceptor 
{
             Metadata.Key<String> headerKey
                     = Metadata.Key.of(key.toString(), 
Metadata.ASCII_STRING_MARSHALLER);
             String headerValue = String.valueOf(call.getAttributes().get(key));
-            GrpcUtils.putHeaderIfNotExist(headers, headerKey, headerValue);
+            GrpcUtils.putHeader(headers, headerKey, headerValue);
         }
 
         String channelId = call.getAttributes().get(AttributeKeys.CHANNEL_ID);
         if (StringUtils.isNotBlank(channelId)) {
-            GrpcUtils.putHeaderIfNotExist(headers, GrpcConstants.CHANNEL_ID, 
channelId);
+            GrpcUtils.putHeader(headers, GrpcConstants.CHANNEL_ID, channelId);
         }
 
         return next.startCall(call, headers);
diff --git 
a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/pipeline/AuthenticationPipeline.java
 
b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/pipeline/AuthenticationPipeline.java
index e317b48f1e..2720d19796 100644
--- 
a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/pipeline/AuthenticationPipeline.java
+++ 
b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/pipeline/AuthenticationPipeline.java
@@ -17,7 +17,6 @@
 package org.apache.rocketmq.proxy.grpc.pipeline;
 
 import com.google.protobuf.GeneratedMessageV3;
-import io.grpc.Context;
 import io.grpc.Metadata;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.rocketmq.auth.authentication.AuthenticationEvaluator;
@@ -46,13 +45,17 @@ public class AuthenticationPipeline implements 
RequestPipeline {
 
     @Override
     public void execute(ProxyContext context, Metadata headers, 
GeneratedMessageV3 request) {
+        GrpcUtils.putHeader(headers, GrpcConstants.AUTHORIZATION_AK, null);
         if (!authConfig.isAuthenticationEnabled()) {
             return;
         }
         try {
-            Metadata metadata = GrpcConstants.METADATA.get(Context.current());
-            AuthenticationContext authenticationContext = newContext(context, 
metadata, request);
+            AuthenticationContext authenticationContext = newContext(context, 
headers, request);
             authenticationEvaluator.evaluate(authenticationContext);
+            if (authenticationContext != null
+                && 
authConfig.isAuthenticationRequired(authenticationContext.getRpcCode())) {
+                putAuthenticatedIdentity(headers, authenticationContext);
+            }
         } catch (AuthenticationException ex) {
             throw ex;
         } catch (Throwable ex) {
@@ -70,13 +73,17 @@ public class AuthenticationPipeline implements 
RequestPipeline {
      * @return
      */
     protected AuthenticationContext newContext(ProxyContext context, Metadata 
headers, GeneratedMessageV3 request) {
-        AuthenticationContext result = 
AuthenticationFactory.newContext(authConfig, headers, request);
-        if (result instanceof DefaultAuthenticationContext) {
-            DefaultAuthenticationContext defaultAuthenticationContext = 
(DefaultAuthenticationContext) result;
+        return AuthenticationFactory.newContext(authConfig, headers, request);
+    }
+
+    private void putAuthenticatedIdentity(Metadata metadata, 
AuthenticationContext authenticationContext) {
+        if (authenticationContext instanceof DefaultAuthenticationContext) {
+            DefaultAuthenticationContext defaultAuthenticationContext =
+                (DefaultAuthenticationContext) authenticationContext;
             if 
(StringUtils.isNotBlank(defaultAuthenticationContext.getUsername())) {
-                GrpcUtils.putHeaderIfNotExist(headers, 
GrpcConstants.AUTHORIZATION_AK, defaultAuthenticationContext.getUsername());
+                GrpcUtils.putHeader(metadata, GrpcConstants.AUTHORIZATION_AK,
+                    defaultAuthenticationContext.getUsername());
             }
         }
-        return result;
     }
 }
diff --git 
a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivity.java
 
b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivity.java
index 2d09c39429..ae574994a3 100644
--- 
a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivity.java
+++ 
b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivity.java
@@ -21,6 +21,8 @@ import io.netty.channel.ChannelHandlerContext;
 import java.util.HashMap;
 import java.util.Map;
 import org.apache.rocketmq.acl.common.AclException;
+import 
org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
+import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
 import org.apache.rocketmq.client.exception.MQBrokerException;
 import org.apache.rocketmq.client.exception.MQClientException;
 import org.apache.rocketmq.common.constant.LoggerName;
@@ -129,7 +131,8 @@ public abstract class AbstractRemotingActivity implements 
NettyRequestProcessor
         } else if (t instanceof MQBrokerException) {
             MQBrokerException e = (MQBrokerException) t;
             writeResponse(ctx, context, request, 
RemotingCommand.createResponseCommand(e.getResponseCode(), 
e.getErrorMessage()), t);
-        } else if (t instanceof AclException) {
+        } else if (t instanceof AclException || t instanceof 
AuthenticationException
+            || t instanceof AuthorizationException) {
             writeResponse(ctx, context, request, 
RemotingCommand.createResponseCommand(ResponseCode.NO_PERMISSION, 
t.getMessage()), t);
         } else {
             writeResponse(ctx, context, request,
diff --git 
a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptorTest.java
 
b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptorTest.java
new file mode 100644
index 0000000000..f22df0a349
--- /dev/null
+++ 
b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptorTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.rocketmq.proxy.grpc.interceptor;
+
+import io.grpc.Attributes;
+import io.grpc.Grpc;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import java.net.InetSocketAddress;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.rocketmq.common.constant.GrpcConstants;
+import org.apache.rocketmq.common.constant.HAProxyConstants;
+import org.apache.rocketmq.proxy.grpc.constant.AttributeKeys;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class HeaderInterceptorTest {
+
+    private static final InetSocketAddress REAL_REMOTE = new 
InetSocketAddress("10.0.0.7", 5000);
+    private static final InetSocketAddress REAL_LOCAL = new 
InetSocketAddress("10.0.0.1", 8081);
+
+    private Metadata intercept(Metadata inboundHeaders) {
+        final Attributes attributes = Attributes.newBuilder()
+            .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, REAL_REMOTE)
+            .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, REAL_LOCAL)
+            .build();
+        return intercept(inboundHeaders, attributes);
+    }
+
+    private Metadata intercept(Metadata inboundHeaders, Attributes attributes) 
{
+        HeaderInterceptor interceptor = new HeaderInterceptor();
+
+        ServerCall<Object, Object> call = new ServerCall<Object, Object>() {
+            @Override
+            public Attributes getAttributes() {
+                return attributes;
+            }
+
+            @Override
+            public void request(int numMessages) {
+            }
+
+            @Override
+            public void sendHeaders(Metadata headers) {
+            }
+
+            @Override
+            public void sendMessage(Object message) {
+            }
+
+            @Override
+            public void close(io.grpc.Status status, Metadata trailers) {
+            }
+
+            @Override
+            public boolean isCancelled() {
+                return false;
+            }
+
+            @Override
+            public MethodDescriptor<Object, Object> getMethodDescriptor() {
+                return null;
+            }
+        };
+
+        final AtomicReference<Metadata> forwarded = new AtomicReference<>();
+        ServerCallHandler<Object, Object> next = (c, headers) -> {
+            forwarded.set(headers);
+            return null;
+        };
+
+        interceptor.interceptCall(call, inboundHeaders, next);
+        return forwarded.get();
+    }
+
+    @Test
+    public void shouldSetConnectionAddresses() {
+        Metadata forwarded = intercept(new Metadata());
+
+        
assertThat(forwarded.get(GrpcConstants.REMOTE_ADDRESS)).isEqualTo("10.0.0.7:5000");
+        
assertThat(forwarded.get(GrpcConstants.LOCAL_ADDRESS)).isEqualTo("10.0.0.1:8081");
+    }
+
+    @Test
+    public void shouldReplaceExistingAddressHeaders() {
+        Metadata headers = new Metadata();
+        headers.put(GrpcConstants.REMOTE_ADDRESS, "127.0.0.1:9999");
+        headers.put(GrpcConstants.LOCAL_ADDRESS, "127.0.0.1:1");
+
+        Metadata forwarded = intercept(headers);
+
+        
assertThat(forwarded.getAll(GrpcConstants.REMOTE_ADDRESS)).containsExactly("10.0.0.7:5000");
+        
assertThat(forwarded.getAll(GrpcConstants.LOCAL_ADDRESS)).containsExactly("10.0.0.1:8081");
+    }
+
+    @Test
+    public void shouldRemoveClientAuthorizationSubject() {
+        Metadata headers = new Metadata();
+        headers.put(GrpcConstants.AUTHORIZATION_AK, "admin");
+        headers.put(GrpcConstants.AUTHORIZATION_AK, "another-admin");
+
+        Metadata forwarded = intercept(headers);
+
+        
assertThat(forwarded.containsKey(GrpcConstants.AUTHORIZATION_AK)).isFalse();
+    }
+
+    @Test
+    public void shouldUseProxyProtocolSourceAndReplaceConnectionMetadata() {
+        Metadata.Key<String> proxyAddress = Metadata.Key.of(
+            HAProxyConstants.PROXY_PROTOCOL_ADDR, 
Metadata.ASCII_STRING_MARSHALLER);
+        Metadata headers = new Metadata();
+        headers.put(GrpcConstants.REMOTE_ADDRESS, "127.0.0.1:9999");
+        headers.put(GrpcConstants.LOCAL_ADDRESS, "127.0.0.1:1");
+        headers.put(GrpcConstants.CHANNEL_ID, "client-channel");
+        headers.put(proxyAddress, "127.0.0.1");
+
+        Attributes attributes = Attributes.newBuilder()
+            .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, REAL_REMOTE)
+            .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, REAL_LOCAL)
+            .set(AttributeKeys.PROXY_PROTOCOL_ADDR, "203.0.113.7")
+            .set(AttributeKeys.PROXY_PROTOCOL_PORT, "7000")
+            .set(AttributeKeys.CHANNEL_ID, "server-channel")
+            .build();
+
+        Metadata forwarded = intercept(headers, attributes);
+
+        
assertThat(forwarded.getAll(GrpcConstants.REMOTE_ADDRESS)).containsExactly("203.0.113.7:7000");
+        
assertThat(forwarded.getAll(GrpcConstants.LOCAL_ADDRESS)).containsExactly("10.0.0.1:8081");
+        
assertThat(forwarded.getAll(GrpcConstants.CHANNEL_ID)).containsExactly("server-channel");
+        
assertThat(forwarded.getAll(proxyAddress)).containsExactly("203.0.113.7");
+    }
+}
diff --git 
a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/pipeline/AuthenticationPipelineTest.java
 
b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/pipeline/AuthenticationPipelineTest.java
new file mode 100644
index 0000000000..f31a268169
--- /dev/null
+++ 
b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/pipeline/AuthenticationPipelineTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.rocketmq.proxy.grpc.pipeline;
+
+import apache.rocketmq.v2.TelemetryCommand;
+import io.grpc.Metadata;
+import java.lang.reflect.Field;
+import org.apache.rocketmq.auth.authentication.AuthenticationEvaluator;
+import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
+import 
org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.constant.GrpcConstants;
+import org.apache.rocketmq.proxy.common.ProxyContext;
+import org.apache.rocketmq.proxy.processor.MessagingProcessor;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class AuthenticationPipelineTest {
+
+    @Test
+    public void removesAuthorizationSubjectWhenAuthenticationIsDisabled() {
+        AuthConfig authConfig = new AuthConfig();
+        authConfig.setConfigName("grpc-authentication-disabled-test");
+        AuthenticationPipeline pipeline = new AuthenticationPipeline(
+            authConfig, mock(MessagingProcessor.class));
+        Metadata metadata = new Metadata();
+        metadata.put(GrpcConstants.AUTHORIZATION_AK, "forged-user");
+
+        pipeline.execute(ProxyContext.create(), metadata, 
TelemetryCommand.getDefaultInstance());
+
+        
assertThat(metadata.containsKey(GrpcConstants.AUTHORIZATION_AK)).isFalse();
+    }
+
+    @Test
+    public void replacesAuthorizationSubjectAfterAuthentication() throws 
Exception {
+        AuthConfig authConfig = new AuthConfig();
+        authConfig.setConfigName("grpc-authentication-pipeline-test");
+        authConfig.setAuthenticationEnabled(true);
+        DefaultAuthenticationContext authenticationContext = new 
DefaultAuthenticationContext();
+        authenticationContext.setRpcCode("authenticated-rpc");
+        authenticationContext.setUsername("verified-user");
+        AuthenticationPipeline pipeline = new AuthenticationPipeline(
+            authConfig, mock(MessagingProcessor.class)) {
+            @Override
+            protected AuthenticationContext newContext(ProxyContext context, 
Metadata headers,
+                com.google.protobuf.GeneratedMessageV3 request) {
+                return authenticationContext;
+            }
+        };
+        AuthenticationEvaluator authenticationEvaluator = 
mock(AuthenticationEvaluator.class);
+        Field evaluatorField = 
AuthenticationPipeline.class.getDeclaredField("authenticationEvaluator");
+        evaluatorField.setAccessible(true);
+        evaluatorField.set(pipeline, authenticationEvaluator);
+
+        Metadata metadata = new Metadata();
+        metadata.put(GrpcConstants.AUTHORIZATION_AK, "forged-user");
+        metadata.put(GrpcConstants.AUTHORIZATION_AK, "another-forged-user");
+        pipeline.execute(ProxyContext.create(), metadata, 
TelemetryCommand.getDefaultInstance());
+
+        verify(authenticationEvaluator).evaluate(authenticationContext);
+        
assertThat(metadata.getAll(GrpcConstants.AUTHORIZATION_AK)).containsExactly("verified-user");
+    }
+
+    @Test
+    public void doesNotPublishAuthorizationSubjectForWhitelistedRequest() 
throws Exception {
+        String rpcCode = TelemetryCommand.getDescriptor().getFullName();
+        AuthConfig authConfig = new AuthConfig();
+        authConfig.setConfigName("grpc-authentication-whitelist-test");
+        authConfig.setAuthenticationEnabled(true);
+        authConfig.setAuthenticationWhitelist("other-rpc, " + rpcCode);
+        DefaultAuthenticationContext authenticationContext = new 
DefaultAuthenticationContext();
+        authenticationContext.setRpcCode(rpcCode);
+        authenticationContext.setUsername("unverified-user");
+        AuthenticationPipeline pipeline = new AuthenticationPipeline(
+            authConfig, mock(MessagingProcessor.class)) {
+            @Override
+            protected AuthenticationContext newContext(ProxyContext context, 
Metadata headers,
+                com.google.protobuf.GeneratedMessageV3 request) {
+                return authenticationContext;
+            }
+        };
+        AuthenticationEvaluator authenticationEvaluator = 
mock(AuthenticationEvaluator.class);
+        Field evaluatorField = 
AuthenticationPipeline.class.getDeclaredField("authenticationEvaluator");
+        evaluatorField.setAccessible(true);
+        evaluatorField.set(pipeline, authenticationEvaluator);
+
+        Metadata metadata = new Metadata();
+        metadata.put(GrpcConstants.AUTHORIZATION_AK, "forged-user");
+        pipeline.execute(ProxyContext.create(), metadata, 
TelemetryCommand.getDefaultInstance());
+
+        verify(authenticationEvaluator).evaluate(authenticationContext);
+        
assertThat(metadata.containsKey(GrpcConstants.AUTHORIZATION_AK)).isFalse();
+    }
+}
diff --git 
a/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivityTest.java
 
b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivityTest.java
index 11dd6bc40c..a698ef343b 100644
--- 
a/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivityTest.java
+++ 
b/proxy/src/test/java/org/apache/rocketmq/proxy/remoting/activity/AbstractRemotingActivityTest.java
@@ -23,6 +23,8 @@ import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelPromise;
 import java.util.concurrent.CompletableFuture;
 import org.apache.rocketmq.acl.common.AclException;
+import 
org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
+import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
 import org.apache.rocketmq.client.exception.MQBrokerException;
 import org.apache.rocketmq.client.exception.MQClientException;
 import org.apache.rocketmq.common.MQVersion;
@@ -184,6 +186,33 @@ public class AbstractRemotingActivityTest extends 
InitConfigTest {
         
assertThat(captor.getValue().getCode()).isEqualTo(ResponseCode.NO_PERMISSION);
     }
 
+    @Test
+    public void testRequestAuthExceptions() throws Exception {
+        ArgumentCaptor<RemotingCommand> captor = 
ArgumentCaptor.forClass(RemotingCommand.class);
+        String brokerName = "broker";
+        CompletableFuture<RemotingCommand> authenticationFuture = new 
CompletableFuture<>();
+        authenticationFuture.completeExceptionally(new 
AuthenticationException("authentication failed"));
+        CompletableFuture<RemotingCommand> authorizationFuture = new 
CompletableFuture<>();
+        authorizationFuture.completeExceptionally(new 
AuthorizationException("authorization failed"));
+        when(messagingProcessorMock.request(any(), eq(brokerName), any(), 
anyLong()))
+            .thenReturn(authenticationFuture, authorizationFuture);
+        RemotingCommand request = 
RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null);
+        request.setOpaque(123);
+        request.addExtField(AbstractRemotingActivity.BROKER_NAME_FIELD, 
brokerName);
+
+        remotingActivity.request(ctx, request, null, 10000);
+        remotingActivity.request(ctx, request, null, 10000);
+
+        verify(ctx, times(2)).writeAndFlush(captor.capture());
+        assertThat(captor.getAllValues()).allSatisfy(response -> {
+            
assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION);
+            assertThat(response.getOpaque()).isEqualTo(123);
+            assertThat(response.isResponseType()).isTrue();
+        });
+        
assertThat(captor.getAllValues().get(0).getRemark()).isEqualTo("authentication 
failed");
+        
assertThat(captor.getAllValues().get(1).getRemark()).isEqualTo("authorization 
failed");
+    }
+
     @Test
     public void testRequestDefaultException() throws Exception {
         ArgumentCaptor<RemotingCommand> captor = 
ArgumentCaptor.forClass(RemotingCommand.class);
@@ -199,4 +228,4 @@ public class AbstractRemotingActivityTest extends 
InitConfigTest {
         verify(ctx, times(1)).writeAndFlush(captor.capture());
         
assertThat(captor.getValue().getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR);
     }
-}
\ No newline at end of file
+}
diff --git 
a/proxy/src/test/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataServiceTest.java
 
b/proxy/src/test/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataServiceTest.java
index 5894f87199..04f4cd6a79 100644
--- 
a/proxy/src/test/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataServiceTest.java
+++ 
b/proxy/src/test/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataServiceTest.java
@@ -21,6 +21,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
+import org.apache.rocketmq.auth.authentication.model.User;
 import org.apache.rocketmq.common.MixAll;
 import org.apache.rocketmq.common.attribute.TopicMessageType;
 import org.apache.rocketmq.proxy.common.ProxyContext;
@@ -28,6 +29,7 @@ import org.apache.rocketmq.proxy.config.ConfigurationManager;
 import org.apache.rocketmq.proxy.service.BaseServiceTest;
 import org.apache.rocketmq.proxy.service.route.MessageQueueView;
 import org.apache.rocketmq.remoting.protocol.route.BrokerData;
+import org.apache.rocketmq.remoting.protocol.body.UserInfo;
 import 
org.apache.rocketmq.remoting.protocol.statictopic.TopicConfigAndQueueMapping;
 import 
org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
 import org.junit.Before;
@@ -90,6 +92,17 @@ public class ClusterMetadataServiceTest extends 
BaseServiceTest {
         assertEquals(1, 
this.clusterMetadataService.subscriptionGroupConfigCache.asMap().size());
     }
 
+    @Test
+    public void testGetUserRetainsCredentialForAuthentication() throws 
Exception {
+        when(this.mqClientAPIExt.getUser(anyString(), eq("abc"), anyLong()))
+            .thenReturn(UserInfo.of("abc", "user-secret-for-proxy", "Normal"));
+
+        User user = this.clusterMetadataService.getUser(ProxyContext.create(), 
"abc").join();
+
+        assertNotNull(user);
+        assertEquals("user-secret-for-proxy", user.getPassword());
+    }
+
     @Test
     public void findOneBroker() {
 
diff --git 
a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResumeCheckHalfMessageRequestHeader.java
 
b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResumeCheckHalfMessageRequestHeader.java
index 923fd37ea6..eff55847b8 100644
--- 
a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResumeCheckHalfMessageRequestHeader.java
+++ 
b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ResumeCheckHalfMessageRequestHeader.java
@@ -22,6 +22,7 @@ import org.apache.rocketmq.common.action.RocketMQAction;
 import org.apache.rocketmq.common.resource.ResourceType;
 import org.apache.rocketmq.common.resource.RocketMQResource;
 import org.apache.rocketmq.remoting.CommandCustomHeader;
+import org.apache.rocketmq.remoting.annotation.CFNotNull;
 import org.apache.rocketmq.remoting.annotation.CFNullable;
 import org.apache.rocketmq.remoting.exception.RemotingCommandException;
 import org.apache.rocketmq.remoting.protocol.RequestCode;
@@ -30,6 +31,7 @@ import org.apache.rocketmq.remoting.protocol.RequestCode;
 public class ResumeCheckHalfMessageRequestHeader implements 
CommandCustomHeader {
 
     @RocketMQResource(ResourceType.TOPIC)
+    @CFNotNull
     private String topic;
     @CFNullable
     private String msgId;
diff --git 
a/tools/src/main/java/org/apache/rocketmq/tools/command/auth/ListUserSubCommand.java
 
b/tools/src/main/java/org/apache/rocketmq/tools/command/auth/ListUserSubCommand.java
index 24eb62427f..d84c43d01e 100644
--- 
a/tools/src/main/java/org/apache/rocketmq/tools/command/auth/ListUserSubCommand.java
+++ 
b/tools/src/main/java/org/apache/rocketmq/tools/command/auth/ListUserSubCommand.java
@@ -34,7 +34,7 @@ import org.apache.rocketmq.tools.command.SubCommandException;
 
 public class ListUserSubCommand implements SubCommand {
 
-    private static final String FORMAT = "%-16s  %-22s  %-22s  %-22s%n";
+    private static final String FORMAT = "%-16s  %-22s  %-22s%n";
 
     @Override
     public String commandName() {
@@ -115,7 +115,8 @@ public class ListUserSubCommand implements SubCommand {
     }
 
     private void printUsers(List<UserInfo> users) {
-        System.out.printf(FORMAT, "#UserName", "#Password", "#UserType", 
"#UserStatus");
-        users.forEach(user -> System.out.printf(FORMAT, user.getUsername(), 
user.getPassword(), user.getUserType(), user.getUserStatus()));
+        System.out.printf(FORMAT, "#UserName", "#UserType", "#UserStatus");
+        users.forEach(user -> System.out.printf(FORMAT,
+            user.getUsername(), user.getUserType(), user.getUserStatus()));
     }
 }

Reply via email to