diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/UserCredentialsConnectionFactoryAdapter.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/UserCredentialsConnectionFactoryAdapter.cs
new file mode 100644
index 00000000..82029bcc
--- /dev/null
+++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/UserCredentialsConnectionFactoryAdapter.cs
@@ -0,0 +1,161 @@
+#region License
+
+/*
+ * Copyright 2002-2010 the original author or authors.
+ *
+ * Licensed 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.
+ */
+#endregion
+
+using System;
+using System.Threading;
+using Apache.NMS;
+
+namespace Spring.Messaging.Nms.Connections
+{
+ ///
+ /// An adapter for a target JMS {@link javax.jms.ConnectionFactory}, applying the
+ /// given user credentials to every standard CreateConnection() call,
+ /// that is, implicitly invoking CreateConnection(username, password)
+ /// on the target.All other methods simply delegate to the corresponding methods
+ /// of the target ConnectionFactory.
+ ///
+ ///
+ /// Can be used to proxy a target NMS ConnectionFactory that does not have user
+ /// credentials configured. Client code can work with the ConnectionFactory without
+ /// passing in username and password on every CreateConnection() call.
+ /// If the "Username" is empty, this proxy will simply delegate to the standard
+ /// CreateConnection() method of the target ConnectionFactory.
+ /// This can be used to keep a UserCredentialsConnectionFactoryAdapter
+ /// definition just for the option of implicitly passing in user credentials
+ /// if the particular target ConnectionFactory requires it.
+ ///
+ public class UserCredentialsConnectionFactoryAdapter:IConnectionFactory
+ {
+ private readonly IConnectionFactory _wrappedConnectionFactory;
+
+ private readonly ThreadLocal threadLocalCredentials = new ThreadLocal();
+
+ public UserCredentialsConnectionFactoryAdapter(IConnectionFactory wrappedConnectionFactory)
+ {
+ this._wrappedConnectionFactory = wrappedConnectionFactory;
+ }
+
+
+ ///
+ /// Set user credentials for this proxy and the current thread.
+ /// The given username and password will be applied to all subsequent
+ /// CreateConnection() calls on this ConnectionFactory proxy.
+ /// This will override any statically specified user credentials,
+ /// that is, values of the "username" and "password" properties.
+ ///
+ public void SetCredentialsForCurrentThread(string userName, string password)
+ {
+ this.threadLocalCredentials.Value = new NmsUserCredentials(userName, password);
+ }
+
+ ///
+ /// Remove any user credentials for this proxy from the current thread.
+ /// Statically specified user credentials apply again afterwards.
+ ///
+ public void RemoveCredentialsFromCurrentThread()
+ {
+ this.threadLocalCredentials.Value = null;
+ }
+
+
+ private string _userName;
+
+ ///
+ /// Set the username that this adapter should use for retrieving Connections.
+ ///
+ public string UserName
+ {
+ get => _userName;
+ set => _userName = string.IsNullOrWhiteSpace(value) ? null : value;
+ }
+
+ private string _password;
+
+ ///
+ /// Set the password that this adapter should use for retrieving Connections.
+ ///
+ public string Password
+ {
+ get => _password;
+ set => _password = string.IsNullOrEmpty(value) ? null : value;
+ }
+
+ public IConnection CreateConnection()
+ {
+ var credentialsForCurrentThread = this.threadLocalCredentials.Value;
+ if (credentialsForCurrentThread != null)
+ {
+ return CreateConnectionForSpecificCredentials(credentialsForCurrentThread.UserName, credentialsForCurrentThread.Password);
+ }
+
+ return CreateConnectionForSpecificCredentials(UserName, Password);
+ }
+
+ private IConnection CreateConnectionForSpecificCredentials(string userName, string password)
+ {
+ if (string.IsNullOrWhiteSpace(userName) == false)
+ {
+ return CreateConnection(userName, password);
+ }
+
+ return _wrappedConnectionFactory.CreateConnection();
+ }
+
+ public IConnection CreateConnection(string userName, string password)
+ {
+ return _wrappedConnectionFactory.CreateConnection(userName, password);
+ }
+
+ public Uri BrokerUri
+ {
+ get => _wrappedConnectionFactory.BrokerUri;
+ set => _wrappedConnectionFactory.BrokerUri = value;
+ }
+
+ public IRedeliveryPolicy RedeliveryPolicy
+ {
+ get => _wrappedConnectionFactory.RedeliveryPolicy;
+ set => _wrappedConnectionFactory.RedeliveryPolicy = value;
+ }
+
+ public ConsumerTransformerDelegate ConsumerTransformer
+ {
+ get => _wrappedConnectionFactory.ConsumerTransformer;
+ set => _wrappedConnectionFactory.ConsumerTransformer = value;
+ }
+
+ public ProducerTransformerDelegate ProducerTransformer
+ {
+ get => _wrappedConnectionFactory.ProducerTransformer;
+ set => _wrappedConnectionFactory.ProducerTransformer = value;
+ }
+
+ private class NmsUserCredentials
+ {
+ public string UserName { get; }
+ public string Password { get; }
+
+ public NmsUserCredentials(string userName, string password)
+ {
+ UserName = userName;
+ Password = password;
+ }
+ }
+ }
+}
diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/MultithreadingTestHelper.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/MultithreadingTestHelper.cs
new file mode 100644
index 00000000..af6368cb
--- /dev/null
+++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/MultithreadingTestHelper.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Spring.Messaging.Nms.Connections
+{
+ public class MultithreadingTestHelper
+ {
+ [DebuggerStepThrough]
+ public static TestThreadHandler RunOnSeparateThread(Action action)
+ {
+ Exception exception = null;
+ var thread1 = new Thread(() =>
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ }
+ });
+ thread1.Start();
+
+ return new TestThreadHandler(() =>
+ {
+ thread1.Join();
+ if (exception != null)
+ {
+ throw exception;
+ }
+ });
+ }
+
+ public class TestThreadHandler
+ {
+ private readonly Action _waitAction;
+
+ public TestThreadHandler(Action waitAction)
+ {
+ _waitAction = waitAction;
+ }
+
+ [DebuggerStepThrough]
+ public void Wait()
+ {
+ _waitAction();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/UserCredentialsConnectionFactoryAdapterTests.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/UserCredentialsConnectionFactoryAdapterTests.cs
new file mode 100644
index 00000000..5f3f38e3
--- /dev/null
+++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/UserCredentialsConnectionFactoryAdapterTests.cs
@@ -0,0 +1,164 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Apache.NMS;
+using FakeItEasy;
+using NUnit.Framework;
+
+namespace Spring.Messaging.Nms.Connections
+{
+ [TestFixture]
+ public class UserCredentialsConnectionFactoryAdapterTests
+ {
+
+ [Test]
+ public void CreateConnectionWithoutCredentialsWhenTheyAreNotSet()
+ {
+ var underlyingConnectionFactory = A.Fake();
+ var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
+
+ connectionFactory.CreateConnection();
+
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappenedOnceExactly();
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection(A._, A._)).MustHaveHappened(0, Times.Exactly);
+ }
+
+
+ [Test]
+ public void CreateConnectionWithoutCredentialsWhenLoginIsNotSet()
+ {
+ var underlyingConnectionFactory = A.Fake();
+ var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
+ connectionFactory.Password = "Secret";
+ connectionFactory.CreateConnection();
+
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappenedOnceExactly();
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection(A._, A._)).MustHaveHappened(0, Times.Exactly);
+ }
+
+ [Test]
+ public void CreateConnectionWithCredentialsWhenTheyAreSet()
+ {
+ var underlyingConnectionFactory = A.Fake();
+ var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
+ connectionFactory.UserName = "SampleUser";
+ connectionFactory.Password = "Secret";
+ connectionFactory.CreateConnection();
+
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Secret")).MustHaveHappenedOnceExactly();
+ }
+
+
+ [Test]
+ public void SetConnectionCredentialsOnlyForCurrentThread()
+ {
+ var underlyingConnectionFactory = A.Fake();
+ var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
+
+ // Call CreateConnection on thread that also called SetCredentialsForCurrentThread
+ MultithreadingTestHelper.RunOnSeparateThread(() =>
+ {
+ connectionFactory.SetCredentialsForCurrentThread("SampleUser", "Password");
+ connectionFactory.CreateConnection();
+
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Password")).MustHaveHappenedOnceExactly();
+ }).Wait();
+
+ // Call CreateConnection on thread that didn't callSetCredentialsForCurrentThread
+ MultithreadingTestHelper.RunOnSeparateThread(() =>
+ {
+ connectionFactory.CreateConnection();
+
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappenedOnceExactly();
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection(A._, A._)).MustHaveHappenedOnceExactly();
+ }).Wait();
+
+
+ // Call CreateConnection on the main thread
+ connectionFactory.CreateConnection();
+
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(2, Times.Exactly);
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection(A._, A._)).MustHaveHappenedOnceExactly();
+ }
+
+ [Test]
+ public void InheritCredentialsIfTheyAreNotSetForSpecificThread()
+ {
+ var underlyingConnectionFactory = A.Fake();
+ var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
+
+
+ connectionFactory.UserName = "SampleUser";
+ connectionFactory.Password = "Secret";
+
+ // Call CreateConnection on thread that also called SetCredentialsForCurrentThread
+ MultithreadingTestHelper.RunOnSeparateThread(() =>
+ {
+ connectionFactory.CreateConnection();
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Secret")).MustHaveHappenedOnceExactly();
+
+
+ connectionFactory.SetCredentialsForCurrentThread("ThreadSampleUser", "ThreadPassword");
+ connectionFactory.CreateConnection();
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection("ThreadSampleUser", "ThreadPassword")).MustHaveHappenedOnceExactly();
+
+ connectionFactory.RemoveCredentialsFromCurrentThread();
+ connectionFactory.CreateConnection();
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Secret")).MustHaveHappened(2, Times.Exactly);
+ }).Wait();
+
+ connectionFactory.CreateConnection();
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Secret")).MustHaveHappened(3, Times.Exactly);
+ }
+
+ [Test]
+ public void SetDifferentCredentialsOnDifferentThreads()
+ {
+ var underlyingConnectionFactory = A.Fake();
+ var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
+
+ connectionFactory.UserName = "SampleUser";
+ connectionFactory.Password = "Secret";
+
+ var barrier = new Barrier(2);
+
+ // Call CreateConnection on thread that also called SetCredentialsForCurrentThread
+ var thread1 = MultithreadingTestHelper.RunOnSeparateThread(() =>
+ {
+ connectionFactory.SetCredentialsForCurrentThread("Thread1SampleUser", "Password");
+ barrier.SignalAndWait();
+
+ connectionFactory.CreateConnection();
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection("Thread1SampleUser", "Password")).MustHaveHappenedOnceExactly();
+ });
+
+ // Call CreateConnection on thread that didn't callSetCredentialsForCurrentThread
+ var thread2 = MultithreadingTestHelper.RunOnSeparateThread(() =>
+ {
+ connectionFactory.SetCredentialsForCurrentThread("Thread2SampleUser", "Password");
+ barrier.SignalAndWait();
+
+ connectionFactory.CreateConnection();
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection("Thread2SampleUser", "Password")).MustHaveHappenedOnceExactly();
+ });
+
+
+ connectionFactory.CreateConnection();
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
+ A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Secret")).MustHaveHappenedOnceExactly();
+
+ thread1.Wait();
+ thread2.Wait();
+ }
+ }
+}