This is an automated email from the ASF dual-hosted git repository.
pvillard31 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new f2b6504ea3d NIFI-16301 Fixed deprecated Spring Security SAML 2
Principal references (#11632)
f2b6504ea3d is described below
commit f2b6504ea3d38281b88db1018db15a230fabc98d
Author: David Handermann <[email protected]>
AuthorDate: Sat Sep 5 07:44:22 2026 -0500
NIFI-16301 Fixed deprecated Spring Security SAML 2 Principal references
(#11632)
---
.../ResponseAuthenticationConverter.java | 16 ++++--
.../Saml2AuthenticationSuccessHandler.java | 15 +++--
.../identity/AttributeNameIdentityConverter.java | 16 +++---
.../Saml2AuthenticationSuccessHandlerTest.java | 62 +++++++++++++++++++++
.../AttributeNameIdentityConverterTest.java | 65 ++++++++++++++++++++++
5 files changed, 154 insertions(+), 20 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/service/authentication/ResponseAuthenticationConverter.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/service/authentication/ResponseAuthenticationConverter.java
index 60a204826d2..2c2b875bf95 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/service/authentication/ResponseAuthenticationConverter.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/service/authentication/ResponseAuthenticationConverter.java
@@ -26,7 +26,7 @@ import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import
org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider;
import
org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider.ResponseToken;
-import
org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
+import
org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication;
import
org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import java.util.Collection;
@@ -53,7 +53,9 @@ public class ResponseAuthenticationConverter implements
Converter<ResponseToken,
}
/**
- * Convert SAML 2 Response Token using default Converter and process
authorities based on Group Attribute Name
+ * Convert SAML 2 Response Token using default Converter and process
authorities based on Group Attribute Name.
+ * The Response Assertion and Relying Party Registration from the default
Converter are retained so that
+ * downstream handlers can read attributes from the Response Assertion.
*
* @param responseToken SAML 2 Response Token
* @return SAML 2 Authentication
@@ -63,8 +65,14 @@ public class ResponseAuthenticationConverter implements
Converter<ResponseToken,
Objects.requireNonNull(responseToken, "Response Token required");
final List<Assertion> assertions =
responseToken.getResponse().getAssertions();
final Saml2Authentication authentication =
Objects.requireNonNull(defaultConverter.convert(responseToken), "Authentication
required");
- final Saml2AuthenticatedPrincipal principal =
(Saml2AuthenticatedPrincipal) authentication.getPrincipal();
- return new Saml2Authentication(principal,
authentication.getSaml2Response(), getAuthorities(assertions));
+ final Collection<? extends GrantedAuthority> authorities =
getAuthorities(assertions);
+
+ if (authentication instanceof final Saml2AssertionAuthentication
assertionAuthentication) {
+ final String registrationId =
assertionAuthentication.getRelyingPartyRegistrationId();
+ return new
Saml2AssertionAuthentication(assertionAuthentication.getPrincipal(),
assertionAuthentication.getCredentials(), authorities, registrationId);
+ }
+
+ return new Saml2Authentication(authentication.getPrincipal(),
authentication.getSaml2Response(), authorities);
}
private Collection<? extends GrantedAuthority> getAuthorities(final
List<Assertion> assertions) {
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandler.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandler.java
index ba2b2c45469..90bd83816b7 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandler.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandler.java
@@ -30,7 +30,8 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import
org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
+import
org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication;
+import
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
import
org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import java.net.URI;
@@ -59,7 +60,7 @@ public class Saml2AuthenticationSuccessHandler extends
SimpleUrlAuthenticationSu
private final Duration expiration;
- private Converter<Saml2AuthenticatedPrincipal, String> identityConverter =
Saml2AuthenticatedPrincipal::getName;
+ private Converter<Saml2ResponseAssertionAccessor, String>
identityConverter = Saml2ResponseAssertionAccessor::getNameId;
/**
* SAML 2 Authentication Success Handler requires Bearer Token Provider
and expiration for generated tokens
@@ -82,11 +83,11 @@ public class Saml2AuthenticationSuccessHandler extends
SimpleUrlAuthenticationSu
}
/**
- * Set Identity Converter for customized mapping of SAML 2 Authenticated
Principal to user identity
+ * Set Identity Converter for customized mapping of SAML 2 Response
Assertion to user identity
*
* @param identityConverter Identity Converter required
*/
- public void setIdentityConverter(final
Converter<Saml2AuthenticatedPrincipal, String> identityConverter) {
+ public void setIdentityConverter(final
Converter<Saml2ResponseAssertionAccessor, String> identityConverter) {
this.identityConverter = Objects.requireNonNull(identityConverter,
"Converter required");
}
@@ -123,11 +124,9 @@ public class Saml2AuthenticationSuccessHandler extends
SimpleUrlAuthenticationSu
}
private String getIdentity(final Authentication authentication) {
- final Object principal = authentication.getPrincipal();
-
final String identity;
- if (principal instanceof final Saml2AuthenticatedPrincipal
authenticatedPrincipal) {
- identity = identityConverter.convert(authenticatedPrincipal);
+ if (authentication instanceof final Saml2AssertionAuthentication
assertionAuthentication) {
+ identity =
identityConverter.convert(assertionAuthentication.getCredentials());
} else {
identity = authentication.getName();
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverter.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverter.java
index 79cf485f13b..d067c62cc0a 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverter.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverter.java
@@ -17,14 +17,14 @@
package org.apache.nifi.web.security.saml2.web.authentication.identity;
import org.springframework.core.convert.converter.Converter;
-import
org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
+import
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
import java.util.Objects;
/**
* Converter for customized User Identity using SAML Attribute Value
*/
-public class AttributeNameIdentityConverter implements
Converter<Saml2AuthenticatedPrincipal, String> {
+public class AttributeNameIdentityConverter implements
Converter<Saml2ResponseAssertionAccessor, String> {
private final String attributeName;
public AttributeNameIdentityConverter(final String attributeName) {
@@ -32,14 +32,14 @@ public class AttributeNameIdentityConverter implements
Converter<Saml2Authentica
}
/**
- * Convert Principal to identity using configured attribute name when found
+ * Convert Response Assertion to identity using configured attribute name
when found
*
- * @param principal SAML 2 Authenticated Principal
- * @return Attribute Value or Principal Name when attribute not found
+ * @param assertion SAML 2 Response Assertion
+ * @return Attribute Value or Name Identifier when attribute not found
*/
@Override
- public String convert(final Saml2AuthenticatedPrincipal principal) {
- final Object attribute = principal.getFirstAttribute(attributeName);
- return attribute == null ? principal.getName() : attribute.toString();
+ public String convert(final Saml2ResponseAssertionAccessor assertion) {
+ final Object attribute = assertion.getFirstAttribute(attributeName);
+ return attribute == null ? assertion.getNameId() :
attribute.toString();
}
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandlerTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandlerTest.java
index 370fca56c22..97bfe125fc3 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandlerTest.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandlerTest.java
@@ -21,10 +21,13 @@ import jakarta.servlet.http.Cookie;
import org.apache.nifi.authorization.util.IdentityMapping;
import org.apache.nifi.web.security.cookie.ApplicationCookieName;
import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
+import
org.apache.nifi.web.security.saml2.web.authentication.identity.AttributeNameIdentityConverter;
+import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.apache.nifi.web.servlet.shared.ProxyHeader;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -32,13 +35,19 @@ import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
+import
org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication;
+import
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion;
+import
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
import java.time.Duration;
import java.util.Collections;
+import java.util.List;
+import java.util.Map;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class Saml2AuthenticationSuccessHandlerTest {
@@ -72,6 +81,20 @@ class Saml2AuthenticationSuccessHandlerTest {
private static final String ALLOWED_CONTEXT_PATHS_PARAMETER =
"allowedContextPaths";
+ private static final String IDENTITY_ATTRIBUTE_NAME =
"urn:oid:0.9.2342.19200300.100.1.3";
+
+ private static final String IDENTITY_ATTRIBUTE_VALUE =
"[email protected]";
+
+ private static final String MAPPED_IDENTITY_ATTRIBUTE_VALUE =
"[email protected]";
+
+ private static final String NAME_IDENTIFIER = "name-identifier";
+
+ private static final String MAPPED_NAME_IDENTIFIER = "NAME-IDENTIFIER";
+
+ private static final String RESPONSE_VALUE = "<saml2p:Response/>";
+
+ private static final String REGISTRATION_ID = "consumer";
+
private static final IdentityMapping UPPER_IDENTITY_MAPPING = new
IdentityMapping(
IdentityMapping.Transform.UPPER.toString(),
MATCH_PATTERN,
@@ -128,6 +151,29 @@ class Saml2AuthenticationSuccessHandlerTest {
assertBearerCookieAdded(FORWARDED_COOKIE_PATH);
}
+ @Test
+ void testDetermineTargetUrlAssertionAuthenticationNameIdentifier() {
+ httpServletRequest.setRequestURI(REQUEST_URI);
+
+ final Authentication authentication =
getAssertionAuthentication(Map.of());
+ final String targetUrl =
handler.determineTargetUrl(httpServletRequest, httpServletResponse,
authentication);
+
+ assertEquals(TARGET_URL, targetUrl);
+ assertBearerTokenIdentityEquals(MAPPED_NAME_IDENTIFIER);
+ }
+
+ @Test
+ void testDetermineTargetUrlAssertionAuthenticationIdentityConverter() {
+ handler.setIdentityConverter(new
AttributeNameIdentityConverter(IDENTITY_ATTRIBUTE_NAME));
+ httpServletRequest.setRequestURI(REQUEST_URI);
+
+ final Authentication authentication =
getAssertionAuthentication(Map.of(IDENTITY_ATTRIBUTE_NAME,
List.of(IDENTITY_ATTRIBUTE_VALUE)));
+ final String targetUrl =
handler.determineTargetUrl(httpServletRequest, httpServletResponse,
authentication);
+
+ assertEquals(TARGET_URL, targetUrl);
+ assertBearerTokenIdentityEquals(MAPPED_IDENTITY_ATTRIBUTE_VALUE);
+ }
+
void assertTargetUrlEquals(final String expectedTargetUrl) {
final Authentication authentication = new
TestingAuthenticationToken(IDENTITY, IDENTITY, AUTHORITY);
@@ -136,6 +182,22 @@ class Saml2AuthenticationSuccessHandlerTest {
assertEquals(expectedTargetUrl, targetUrl);
}
+ void assertBearerTokenIdentityEquals(final String expectedIdentity) {
+ final ArgumentCaptor<LoginAuthenticationToken> tokenCaptor =
ArgumentCaptor.forClass(LoginAuthenticationToken.class);
+ verify(bearerTokenProvider).getBearerToken(tokenCaptor.capture());
+
+ assertEquals(expectedIdentity, tokenCaptor.getValue().getName());
+ }
+
+ private Authentication getAssertionAuthentication(final Map<String,
List<Object>> attributes) {
+ final Saml2ResponseAssertionAccessor assertion =
Saml2ResponseAssertion.withResponseValue(RESPONSE_VALUE)
+ .nameId(NAME_IDENTIFIER)
+ .attributes(attributes)
+ .build();
+
+ return new Saml2AssertionAuthentication(NAME_IDENTIFIER, assertion,
Collections.emptyList(), REGISTRATION_ID);
+ }
+
void assertBearerCookieAdded(final String expectedCookiePath) {
final Cookie responseCookie =
httpServletResponse.getCookie(ApplicationCookieName.AUTHORIZATION_BEARER.getCookieName());
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverterTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverterTest.java
new file mode 100644
index 00000000000..a1e017c3fa5
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverterTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.nifi.web.security.saml2.web.authentication.identity;
+
+import org.junit.jupiter.api.Test;
+import
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion;
+import
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class AttributeNameIdentityConverterTest {
+ private static final String ATTRIBUTE_NAME =
"urn:oid:0.9.2342.19200300.100.1.3";
+
+ private static final String ATTRIBUTE_VALUE = "[email protected]";
+
+ private static final String OTHER_ATTRIBUTE_NAME = "urn:oid:2.5.4.3";
+
+ private static final String NAME_IDENTIFIER = "name-identifier";
+
+ private static final String RESPONSE_VALUE = "<saml2p:Response/>";
+
+ private final AttributeNameIdentityConverter converter = new
AttributeNameIdentityConverter(ATTRIBUTE_NAME);
+
+ @Test
+ void testConvertConfiguredAttributeFound() {
+ final Saml2ResponseAssertionAccessor assertion =
getAssertion(Map.of(ATTRIBUTE_NAME, List.of(ATTRIBUTE_VALUE)));
+
+ assertEquals(ATTRIBUTE_VALUE, converter.convert(assertion));
+ }
+
+ @Test
+ void testConvertConfiguredAttributeNotFoundReturnsNameIdentifier() {
+ final Saml2ResponseAssertionAccessor assertionWithoutAttributes =
getAssertion(Map.of());
+
+ assertEquals(NAME_IDENTIFIER,
converter.convert(assertionWithoutAttributes));
+
+ final Saml2ResponseAssertionAccessor assertionWithOtherAttribute =
getAssertion(Map.of(OTHER_ATTRIBUTE_NAME, List.of(ATTRIBUTE_VALUE)));
+
+ assertEquals(NAME_IDENTIFIER,
converter.convert(assertionWithOtherAttribute));
+ }
+
+ private Saml2ResponseAssertionAccessor getAssertion(final Map<String,
List<Object>> attributes) {
+ return Saml2ResponseAssertion.withResponseValue(RESPONSE_VALUE)
+ .nameId(NAME_IDENTIFIER)
+ .attributes(attributes)
+ .build();
+ }
+}