Skip to content

Commit 65faca8

Browse files
committed
Consistently using credentials for creating the JMSContext
This commit updates UserCredentialsConnectionFactoryAdapter to apply the same handling of credentials for creating the JMSContext. Closes gh-33270
1 parent 6e55e78 commit 65faca8

File tree

2 files changed

+189
-11
lines changed

2 files changed

+189
-11
lines changed

spring-jms/src/main/java/org/springframework/jms/connection/UserCredentialsConnectionFactoryAdapter.java

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,14 +33,16 @@
3333

3434
/**
3535
* An adapter for a target JMS {@link jakarta.jms.ConnectionFactory}, applying the
36-
* given user credentials to every standard {@code createConnection()} call,
37-
* that is, implicitly invoking {@code createConnection(username, password)}
38-
* on the target. All other methods simply delegate to the corresponding methods
39-
* of the target ConnectionFactory.
36+
* given user credentials to every standard methods that can also be used with
37+
* authentication, this {@code createConnection()} and {@code createContext()}. In
38+
* other words, it is implicitly invoking {@code createConnection(username, password)} or
39+
* {@code createContext(username, password)}} on the target. All other methods simply
40+
* delegate to the corresponding methods of the target ConnectionFactory.
4041
*
4142
* <p>Can be used to proxy a target JNDI ConnectionFactory that does not have user
4243
* credentials configured. Client code can work with the ConnectionFactory without
43-
* passing in username and password on every {@code createConnection()} call.
44+
* passing in username and password on every {@code createConnection()} and
45+
* {@code createContext()} call.
4446
*
4547
* <p>In the following example, client code can simply transparently work
4648
* with the preconfigured "myConnectionFactory", implicitly accessing
@@ -58,9 +60,9 @@
5860
* &lt;/bean&gt;</pre>
5961
*
6062
* <p>If the "username" is empty, this proxy will simply delegate to the standard
61-
* {@code createConnection()} method of the target ConnectionFactory.
62-
* This can be used to keep a UserCredentialsConnectionFactoryAdapter bean
63-
* definition just for the <i>option</i> of implicitly passing in user credentials
63+
* {@code createConnection()} or {@code createContext()} method of the target
64+
* ConnectionFactory. This can be used to keep a UserCredentialsConnectionFactoryAdapter
65+
* bean definition just for the <i>option</i> of implicitly passing in user credentials
6466
* if the particular target ConnectionFactory requires it.
6567
*
6668
* <p>As of Spring Framework 5, this class delegates JMS 2.0 {@code JMSContext}
@@ -69,8 +71,10 @@
6971
* as long as no actual JMS 2.0 calls are triggered by the application's setup.
7072
*
7173
* @author Juergen Hoeller
74+
* @author Stephane Nicoll
7275
* @since 1.2
7376
* @see #createConnection
77+
* @see #createContext
7478
* @see #createQueueConnection
7579
* @see #createTopicConnection
7680
*/
@@ -296,7 +300,22 @@ protected TopicConnection doCreateTopicConnection(
296300

297301
@Override
298302
public JMSContext createContext() {
299-
return obtainTargetConnectionFactory().createContext();
303+
JmsUserCredentials threadCredentials = this.threadBoundCredentials.get();
304+
if (threadCredentials != null) {
305+
return doCreateContext(threadCredentials.username, threadCredentials.password);
306+
}
307+
else {
308+
return doCreateContext(this.username, this.password);
309+
}
310+
}
311+
312+
protected JMSContext doCreateContext(@Nullable String username, @Nullable String password) {
313+
if (StringUtils.hasLength(username)) {
314+
return obtainTargetConnectionFactory().createContext(username, password);
315+
}
316+
else {
317+
return obtainTargetConnectionFactory().createContext();
318+
}
300319
}
301320

302321
@Override
@@ -311,7 +330,22 @@ public JMSContext createContext(String userName, String password, int sessionMod
311330

312331
@Override
313332
public JMSContext createContext(int sessionMode) {
314-
return obtainTargetConnectionFactory().createContext(sessionMode);
333+
JmsUserCredentials threadCredentials = this.threadBoundCredentials.get();
334+
if (threadCredentials != null) {
335+
return doCreateContext(threadCredentials.username, threadCredentials.password, sessionMode);
336+
}
337+
else {
338+
return doCreateContext(this.username, this.password, sessionMode);
339+
}
340+
}
341+
342+
protected JMSContext doCreateContext(@Nullable String username, @Nullable String password, int sessionMode) {
343+
if (StringUtils.hasLength(username)) {
344+
return obtainTargetConnectionFactory().createContext(username, password, sessionMode);
345+
}
346+
else {
347+
return obtainTargetConnectionFactory().createContext(sessionMode);
348+
}
315349
}
316350

317351
private ConnectionFactory obtainTargetConnectionFactory() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.jms.connection;
18+
19+
import jakarta.jms.ConnectionFactory;
20+
import jakarta.jms.JMSContext;
21+
import org.junit.jupiter.api.Test;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
import static org.mockito.BDDMockito.given;
25+
import static org.mockito.Mockito.mock;
26+
import static org.mockito.Mockito.verify;
27+
import static org.mockito.Mockito.verifyNoMoreInteractions;
28+
29+
/**
30+
* Tests for {@link UserCredentialsConnectionFactoryAdapter}.
31+
*
32+
* @author Stephane Nicoll
33+
*/
34+
class UserCredentialsConnectionFactoryAdapterTests {
35+
36+
private static final JMSContext MOCK_CONTEXT = mock(JMSContext.class);
37+
38+
private final ConnectionFactory target;
39+
40+
private final UserCredentialsConnectionFactoryAdapter adapter;
41+
42+
UserCredentialsConnectionFactoryAdapterTests() {
43+
this.target = mock(ConnectionFactory.class);
44+
this.adapter = new UserCredentialsConnectionFactoryAdapter();
45+
this.adapter.setTargetConnectionFactory(this.target);
46+
}
47+
48+
@Test
49+
void createContextWhenNoAuthentication() {
50+
given(this.target.createContext()).willReturn(MOCK_CONTEXT);
51+
assertThat(this.adapter.createContext()).isSameAs(MOCK_CONTEXT);
52+
verify(this.target).createContext();
53+
verifyNoMoreInteractions(this.target);
54+
}
55+
56+
@Test
57+
void createContextWhenAuthentication() {
58+
this.adapter.setUsername("user");
59+
this.adapter.setPassword("password");
60+
given(this.target.createContext("user", "password")).willReturn(MOCK_CONTEXT);
61+
assertThat(this.adapter.createContext()).isSameAs(MOCK_CONTEXT);
62+
verify(this.target).createContext("user", "password");
63+
verifyNoMoreInteractions(this.target);
64+
}
65+
66+
@Test
67+
void createContextWhenThreadLevelAuthentication() {
68+
this.adapter.setCredentialsForCurrentThread("user", "password");
69+
given(this.target.createContext("user", "password")).willReturn(MOCK_CONTEXT);
70+
assertThat(this.adapter.createContext()).isSameAs(MOCK_CONTEXT);
71+
verify(this.target).createContext("user", "password");
72+
verifyNoMoreInteractions(this.target);
73+
}
74+
75+
@Test
76+
void createContextWhenAuthenticationAndThreadLevelAuthentication() {
77+
this.adapter.setCredentialsForCurrentThread("specific", "secret");
78+
this.adapter.setUsername("user");
79+
this.adapter.setPassword("password");
80+
given(this.target.createContext("specific", "secret")).willReturn(MOCK_CONTEXT);
81+
assertThat(this.adapter.createContext()).isSameAs(MOCK_CONTEXT);
82+
verify(this.target).createContext("specific", "secret");
83+
verifyNoMoreInteractions(this.target);
84+
}
85+
86+
@Test
87+
void createContextWithSessionModeWhenNoAuthentication() {
88+
given(this.target.createContext(1)).willReturn(MOCK_CONTEXT);
89+
assertThat(this.adapter.createContext(1)).isSameAs(MOCK_CONTEXT);
90+
verify(this.target).createContext(1);
91+
verifyNoMoreInteractions(this.target);
92+
}
93+
94+
@Test
95+
void createContextWithSessionModeWhenAuthentication() {
96+
this.adapter.setUsername("user");
97+
this.adapter.setPassword("password");
98+
given(this.target.createContext("user", "password", 1)).willReturn(MOCK_CONTEXT);
99+
assertThat(this.adapter.createContext(1)).isSameAs(MOCK_CONTEXT);
100+
verify(this.target).createContext("user", "password", 1);
101+
verifyNoMoreInteractions(this.target);
102+
}
103+
104+
@Test
105+
void createContextWithSessionModeWhenThreadLevelAuthentication() {
106+
this.adapter.setCredentialsForCurrentThread("user", "password");
107+
given(this.target.createContext("user", "password", 1)).willReturn(MOCK_CONTEXT);
108+
assertThat(this.adapter.createContext(1)).isSameAs(MOCK_CONTEXT);
109+
verify(this.target).createContext("user", "password", 1);
110+
verifyNoMoreInteractions(this.target);
111+
}
112+
113+
@Test
114+
void createContextWithSessionModeWhenAuthenticationAndThreadLevelAuthentication() {
115+
this.adapter.setCredentialsForCurrentThread("specific", "secret");
116+
this.adapter.setUsername("user");
117+
this.adapter.setPassword("password");
118+
given(this.target.createContext("specific", "secret", 1)).willReturn(MOCK_CONTEXT);
119+
assertThat(this.adapter.createContext(1)).isSameAs(MOCK_CONTEXT);
120+
verify(this.target).createContext("specific", "secret", 1);
121+
verifyNoMoreInteractions(this.target);
122+
}
123+
124+
@Test
125+
void createContextWithUsernamePasswordIgnoresAuthentication() {
126+
this.adapter.setUsername("user");
127+
this.adapter.setPassword("password");
128+
given(this.target.createContext("specific", "secret")).willReturn(MOCK_CONTEXT);
129+
assertThat(this.adapter.createContext("specific", "secret")).isSameAs(MOCK_CONTEXT);
130+
verify(this.target).createContext("specific", "secret");
131+
verifyNoMoreInteractions(this.target);
132+
}
133+
134+
@Test
135+
void createContextWithSessionModeAndUsernamePasswordIgnoresAuthentication() {
136+
this.adapter.setUsername("user");
137+
this.adapter.setPassword("password");
138+
given(this.target.createContext("specific", "secret", 1)).willReturn(MOCK_CONTEXT);
139+
assertThat(this.adapter.createContext("specific", "secret", 1)).isSameAs(MOCK_CONTEXT);
140+
verify(this.target).createContext("specific", "secret", 1);
141+
verifyNoMoreInteractions(this.target);
142+
}
143+
144+
}

0 commit comments

Comments
 (0)