This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/4.1.x-fixes by this push:
new 05c1104c613 Make STSTokenValidator thread safe (#3499)
05c1104c613 is described below
commit 05c1104c61308f53ce416d0f63c2cea1f2698304
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Fri Sep 25 05:08:46 2026 +0100
Make STSTokenValidator thread safe (#3499)
(cherry picked from commit 751e906585ad25b183e726cfd5f25f954e6f8eba)
---
.../cxf/ws/security/trust/STSTokenValidator.java | 4 +-
.../ws/security/trust/STSTokenValidatorTest.java | 167 +++++++++++++++++++++
2 files changed, 170 insertions(+), 1 deletion(-)
diff --git
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/trust/STSTokenValidator.java
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/trust/STSTokenValidator.java
index 9b9bf5f91a9..6c62d267150 100644
---
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/trust/STSTokenValidator.java
+++
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/trust/STSTokenValidator.java
@@ -52,7 +52,6 @@ import org.apache.wss4j.dom.validate.Validator;
* "useIssueBinding" to "true" only works for validating UsernameTokens.
*/
public class STSTokenValidator implements Validator {
- private STSSamlAssertionValidator samlValidator = new
STSSamlAssertionValidator();
private boolean alwaysValidateToSts;
private boolean useIssueBinding;
private boolean useOnBehalfOf = true;
@@ -179,6 +178,9 @@ public class STSTokenValidator implements Validator {
if (!alwaysValidateToSts && credential.getSamlAssertion() != null) {
try {
+ // STSSamlAssertionValidator records the trust verification
result in an instance field, so
+ // a new instance must be used for each request to avoid
sharing state between requests
+ STSSamlAssertionValidator samlValidator = new
STSSamlAssertionValidator();
samlValidator.validate(credential, data);
return samlValidator.isTrustVerificationSucceeded();
} catch (RuntimeException e) {
diff --git
a/rt/ws/security/src/test/java/org/apache/cxf/ws/security/trust/STSTokenValidatorTest.java
b/rt/ws/security/src/test/java/org/apache/cxf/ws/security/trust/STSTokenValidatorTest.java
new file mode 100644
index 00000000000..fdab35bde4d
--- /dev/null
+++
b/rt/ws/security/src/test/java/org/apache/cxf/ws/security/trust/STSTokenValidatorTest.java
@@ -0,0 +1,167 @@
+/**
+ * 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.cxf.ws.security.trust;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.cxf.helpers.DOMUtils;
+import org.apache.cxf.ws.security.wss4j.saml.SAML2CallbackHandler;
+import org.apache.wss4j.common.crypto.Crypto;
+import org.apache.wss4j.common.crypto.CryptoFactory;
+import org.apache.wss4j.common.saml.SAMLCallback;
+import org.apache.wss4j.common.saml.SAMLUtil;
+import org.apache.wss4j.common.saml.SamlAssertionWrapper;
+import org.apache.wss4j.dom.engine.WSSConfig;
+import org.apache.wss4j.dom.handler.RequestData;
+import org.apache.wss4j.dom.saml.WSSSAMLKeyInfoProcessor;
+import org.apache.wss4j.dom.validate.Credential;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for local validation of SAML Assertions in the STSTokenValidator.
+ */
+public class STSTokenValidatorTest {
+
+ private static Crypto signingCrypto;
+ private static Crypto trustedCrypto;
+ private static Crypto untrustedCrypto;
+
+ @BeforeClass
+ public static void init() throws Exception {
+ WSSConfig.init();
+ signingCrypto = CryptoFactory.getInstance("outsecurity.properties");
+ // Contains the signing certificate
+ trustedCrypto = CryptoFactory.getInstance("outsecurity.properties");
+ // Only trusts an unrelated CA
+ untrustedCrypto = CryptoFactory.getInstance("cxfca.properties");
+ }
+
+ @Test
+ public void testTrustedSignedAssertion() throws Exception {
+ STSTokenValidator validator = new STSTokenValidator();
+ RequestData data = createRequestData(trustedCrypto);
+ assertTrue(validator.isValidatedLocally(createCredential(true, data),
data));
+ }
+
+ @Test
+ public void testUntrustedSignedAssertion() throws Exception {
+ STSTokenValidator validator = new STSTokenValidator();
+ RequestData data = createRequestData(untrustedCrypto);
+ assertFalse(validator.isValidatedLocally(createCredential(true, data),
data));
+ }
+
+ /**
+ * An unsigned Assertion must not be treated as validated locally because
a previous
+ * signed Assertion was trusted.
+ */
+ @Test
+ public void testUnsignedAssertionAfterTrustedAssertion() throws Exception {
+ STSTokenValidator validator = new STSTokenValidator();
+
+ RequestData trustedData = createRequestData(trustedCrypto);
+ assertTrue(validator.isValidatedLocally(createCredential(true,
trustedData), trustedData));
+
+ RequestData data = createRequestData(trustedCrypto);
+ assertFalse(validator.isValidatedLocally(createCredential(false,
data), data));
+ }
+
+ /**
+ * An untrusted Assertion must never be treated as validated locally, even
when a trusted
+ * Assertion is validated concurrently by the same STSTokenValidator.
+ */
+ @Test
+ public void testConcurrentTrustedAndUntrustedAssertions() throws Exception
{
+ final STSTokenValidator validator = new STSTokenValidator();
+ final int iterations = 200;
+
+ final RequestData trustedData = createRequestData(trustedCrypto);
+ final Credential trustedCredential = createCredential(true,
trustedData);
+ final RequestData untrustedData = createRequestData(untrustedCrypto);
+ final Credential untrustedCredential = createCredential(true,
untrustedData);
+
+ ExecutorService executor = Executors.newFixedThreadPool(2);
+ try {
+ Callable<Boolean> trusted = () -> {
+ boolean result = true;
+ for (int i = 0; i < iterations; i++) {
+ result &= validator.isValidatedLocally(trustedCredential,
trustedData);
+ }
+ return result;
+ };
+ Callable<Boolean> untrusted = () -> {
+ boolean result = false;
+ for (int i = 0; i < iterations; i++) {
+ result |=
validator.isValidatedLocally(untrustedCredential, untrustedData);
+ }
+ return result;
+ };
+
+ Future<Boolean> trustedResult = executor.submit(trusted);
+ Future<Boolean> untrustedResult = executor.submit(untrusted);
+
+ assertTrue(trustedResult.get(60, TimeUnit.SECONDS));
+ assertFalse(untrustedResult.get(60, TimeUnit.SECONDS));
+ } finally {
+ executor.shutdownNow();
+ }
+ }
+
+ private static RequestData createRequestData(Crypto sigVerCrypto) {
+ RequestData data = new RequestData();
+ data.setSigVerCrypto(sigVerCrypto);
+ data.setWssConfig(WSSConfig.getNewInstance());
+ return data;
+ }
+
+ private static Credential createCredential(boolean signed, RequestData
data) throws Exception {
+ SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
+ SAMLCallback samlCallback = new SAMLCallback();
+ SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
+ SamlAssertionWrapper assertion = new
SamlAssertionWrapper(samlCallback);
+ if (signed) {
+ assertion.signAssertion("myalias", "myAliasPassword",
signingCrypto, false);
+ }
+
+ // Round-trip the Assertion via DOM, as would happen on receipt of a
message
+ Document doc = DOMUtils.createDocument();
+ Element element = assertion.toDOM(doc);
+ // The Assertion must be attached to the Document so that the
signature Reference can be resolved
+ doc.appendChild(element);
+ SamlAssertionWrapper receivedAssertion = new
SamlAssertionWrapper(element);
+ if (signed) {
+ receivedAssertion.verifySignature(new
WSSSAMLKeyInfoProcessor(data), data.getSigVerCrypto());
+ }
+
+ Credential credential = new Credential();
+ credential.setSamlAssertion(receivedAssertion);
+ return credential;
+ }
+}