Add Session Index Support

Closes gh-10613
This commit is contained in:
Josh Cummings 2022-01-26 15:17:24 -07:00
parent 99c488c6ae
commit 60eead9ceb
7 changed files with 63 additions and 10 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@ -17,6 +17,7 @@
package org.springframework.security.saml2.provider.service.authentication;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -34,14 +35,22 @@ public class DefaultSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPri
private final Map<String, List<Object>> attributes;
private final List<String> sessionIndexes;
private String registrationId;
public DefaultSaml2AuthenticatedPrincipal(String name, Map<String, List<Object>> attributes) {
this(name, attributes, Collections.emptyList());
}
public DefaultSaml2AuthenticatedPrincipal(String name, Map<String, List<Object>> attributes,
List<String> sessionIndexes) {
Assert.notNull(name, "name cannot be null");
Assert.notNull(attributes, "attributes cannot be null");
Assert.notNull(sessionIndexes, "sessionIndexes cannot be null");
this.name = name;
this.attributes = attributes;
this.registrationId = null;
this.sessionIndexes = sessionIndexes;
}
@Override
@ -54,6 +63,11 @@ public class DefaultSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPri
return this.attributes;
}
@Override
public List<String> getSessionIndexes() {
return this.sessionIndexes;
}
@Override
public String getRelyingPartyRegistrationId() {
return this.registrationId;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@ -76,4 +76,8 @@ public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal {
return null;
}
default List<String> getSessionIndexes() {
return Collections.emptyList();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@ -31,10 +31,12 @@ import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.LogoutRequest;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.SessionIndex;
import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml.saml2.core.impl.LogoutRequestBuilder;
import org.opensaml.saml.saml2.core.impl.LogoutRequestMarshaller;
import org.opensaml.saml.saml2.core.impl.NameIDBuilder;
import org.opensaml.saml.saml2.core.impl.SessionIndexBuilder;
import org.w3c.dom.Element;
import org.springframework.security.core.Authentication;
@ -67,6 +69,8 @@ final class OpenSamlLogoutRequestResolver {
private final NameIDBuilder nameIdBuilder;
private final SessionIndexBuilder sessionIndexBuilder;
private final LogoutRequestBuilder logoutRequestBuilder;
private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver;
@ -87,6 +91,9 @@ final class OpenSamlLogoutRequestResolver {
Assert.notNull(this.issuerBuilder, "issuerBuilder must be configured in OpenSAML");
this.nameIdBuilder = (NameIDBuilder) registry.getBuilderFactory().getBuilder(NameID.DEFAULT_ELEMENT_NAME);
Assert.notNull(this.nameIdBuilder, "nameIdBuilder must be configured in OpenSAML");
this.sessionIndexBuilder = (SessionIndexBuilder) registry.getBuilderFactory()
.getBuilder(SessionIndex.DEFAULT_ELEMENT_NAME);
Assert.notNull(this.sessionIndexBuilder, "sessionIndexBuilder must be configured in OpenSAML");
}
/**
@ -122,6 +129,14 @@ final class OpenSamlLogoutRequestResolver {
NameID nameId = this.nameIdBuilder.buildObject();
nameId.setValue(authentication.getName());
logoutRequest.setNameID(nameId);
if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal) {
Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal();
for (String index : principal.getSessionIndexes()) {
SessionIndex sessionIndex = this.sessionIndexBuilder.buildObject();
sessionIndex.setSessionIndex(index);
logoutRequest.getSessionIndexes().add(sessionIndex);
}
}
logoutRequestConsumer.accept(registration, logoutRequest);
if (logoutRequest.getID() == null) {
logoutRequest.setID("LR" + UUID.randomUUID());

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@ -57,6 +57,7 @@ import org.opensaml.saml.saml2.assertion.impl.DelegationRestrictionConditionVali
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Attribute;
import org.opensaml.saml.saml2.core.AttributeStatement;
import org.opensaml.saml.saml2.core.AuthnStatement;
import org.opensaml.saml.saml2.core.Condition;
import org.opensaml.saml.saml2.core.EncryptedAssertion;
import org.opensaml.saml.saml2.core.OneTimeUse;
@ -425,7 +426,9 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
Assertion assertion = CollectionUtils.firstElement(response.getAssertions());
String username = assertion.getSubject().getNameID().getValue();
Map<String, List<Object>> attributes = getAssertionAttributes(assertion);
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, attributes);
List<String> sessionIndexes = getSessionIndexes(assertion);
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, attributes,
sessionIndexes);
String registrationId = responseToken.token.getRelyingPartyRegistration().getRegistrationId();
principal.setRelyingPartyRegistrationId(registrationId);
return new Saml2Authentication(principal, token.getSaml2Response(),
@ -617,6 +620,14 @@ public final class OpenSaml4AuthenticationProvider implements AuthenticationProv
return attributeMap;
}
private static List<String> getSessionIndexes(Assertion assertion) {
List<String> sessionIndexes = new ArrayList<>();
for (AuthnStatement statement : assertion.getAuthnStatements()) {
sessionIndexes.add(statement.getSessionIndex());
}
return sessionIndexes;
}
private static Object getXmlObjectValue(XMLObject xmlObject) {
if (xmlObject instanceof XSAny) {
return ((XSAny) xmlObject).getTextContent();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@ -247,6 +247,7 @@ public class OpenSaml4AuthenticationProviderTests {
expected.put("registeredDate", Collections.singletonList(registeredDate));
assertThat((String) principal.getFirstAttribute("name")).isEqualTo("John Doe");
assertThat(principal.getAttributes()).isEqualTo(expected);
assertThat(principal.getSessionIndexes()).contains("session-index");
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@ -49,6 +49,7 @@ import org.opensaml.saml.saml2.core.Attribute;
import org.opensaml.saml.saml2.core.AttributeStatement;
import org.opensaml.saml.saml2.core.AttributeValue;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.AuthnStatement;
import org.opensaml.saml.saml2.core.Conditions;
import org.opensaml.saml.saml2.core.EncryptedAssertion;
import org.opensaml.saml.saml2.core.EncryptedAttribute;
@ -153,6 +154,9 @@ public final class TestOpenSamlObjects {
confirmationData.setRecipient(recipientUri);
subjectConfirmation.setSubjectConfirmationData(confirmationData);
assertion.getSubject().getSubjectConfirmations().add(subjectConfirmation);
AuthnStatement statement = build(AuthnStatement.DEFAULT_ELEMENT_NAME);
statement.setSessionIndex("session-index");
assertion.getAuthnStatements().add(statement);
return assertion;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@ -19,6 +19,7 @@ package org.springframework.security.saml2.provider.service.web.authentication.l
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import jakarta.servlet.http.HttpServletRequest;
@ -86,10 +87,13 @@ public class OpenSamlLogoutRequestResolverTests {
Saml2MessageBinding binding = registration.getAssertingPartyDetails().getSingleLogoutServiceBinding();
LogoutRequest logoutRequest = getLogoutRequest(saml2LogoutRequest.getSamlRequest(), binding);
assertThat(logoutRequest.getNameID().getValue()).isEqualTo(authentication.getName());
assertThat(logoutRequest.getSessionIndexes()).hasSize(1);
assertThat(logoutRequest.getSessionIndexes().get(0).getSessionIndex()).isEqualTo("session-index");
}
private Saml2Authentication authentication(RelyingPartyRegistration registration) {
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", new HashMap<>());
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", new HashMap<>(),
Arrays.asList("session-index"));
principal.setRelyingPartyRegistrationId(registration.getRegistrationId());
return new Saml2Authentication(principal, "response", new ArrayList<>());
}