Server-Side Storage und persistent Id

Standardmäßig werden Informationen zu Sessions, User Consent (bzgl. Attributfreigabe) und Persistent IDs clientseitig in Cookies abgelegt. Wir betrachten die clientseitige Speicherung nur als initiale „Notlösung“, damit der IdP auch ohne Datenbank funktioniert. In einem fertigen Produktivszenario ist das Abspeichern dieser Informationen auf Serverseite unbedingt zu empfehlen, da nur damit erweiterte SAML-Funktionalitäten möglich sind (z.B. persistentIds oder Single-Logout). Nur so kann sichergestellt werden, dass bei Attribute Queries, die Entscheidungen des/der Nutzers/Nutzerin hinsichtlich Attributfreigabe berücksichtigt werden.

persistentId?

PersistentIds werden vom IdP pro Useraccount und pro Service Provider automatisch generiert. Sie sind pseudonym und können am SP zur Wiedererkennung und Personalisierung verwendet werden. Nur am IdP ist ist ersichtlich, welchen Nutzer*innen die persistentIds zuzuordnen sind. persistentIds sind keine normalen Attribute, sondern sogenannte SAML2 NameIDs. Ihre Freigabe wird in der Konfigurationsdatei ./conf/relying-party.xml reguliert.

Attribute Query?

Bei einer Attribute Query fragt ein Service Provider direkt beim IdP Nutzerdaten ab, also ohne, dass Nutzer*innen einen Loginvorgang angestoßen haben. Dies tun manche SPs, um Informationen darüber zu bekommen, ob Useraccounts am IdP noch aktiv sind. Für Attribute Queries wird gerne die persistentId verwendet.

Wir zeigen die Vorgehensweise hier anhand von MariaDB. Es ist aber möglich, andere Datenbank-Software zu verwenden, etwa MySQL, Oracle oder Postgres, siehe hierzu die Dokumentation im Shibboleth-Wiki. Ein Beispiel für PostgreSQL findet sich bei SWITCHAAI.

1. Im einfachsten Fall installieren Sie auf dem IdP einen lokalen Datenbank-Server. Sie können natürlich auch entfernte Datenbanken über das Netzwerk einbinden.

root@idp:~# apt install mariadb-server mariadb-client libmariadb-java
root@idp:~# ln -s /usr/share/java/mariadb-java-client.jar /var/lib/tomcat10/lib/mariadb-java-client.jar

2. Starten Sie Tomcat neu um die neuen Einstellungen zu aktivieren:

root@idp:~# systemctl restart tomcat10

3. Installieren Sie schließlich im IdP das JDBC-Plugin, falls noch nicht geschehen:

root@idp:~# /opt/shibboleth-idp/bin/plugin.sh -I net.shibboleth.plugin.storage.jdbc

Die Datenbank und der Datenbank-Benutzeraccount müssen manuell erstellt werden. Dann werden noch zwei Tabellen angelegt:

  • StorageRecords für Sessions und User Consent-Informationen (die COLLATION muss case-sensitive sein, hier utf8_bin)
  • shibpid für die persistentIds
mysql> SET NAMES 'utf8';
SET CHARACTER SET utf8;
CHARSET utf8;
CREATE DATABASE IF NOT EXISTS shibboleth CHARACTER SET=utf8;
USE shibboleth;
 
mysql> CREATE TABLE IF NOT EXISTS StorageRecords (
  context varchar(255) NOT NULL,
  id varchar(255) NOT NULL,
  expires bigint(20) DEFAULT NULL,
  value longtext NOT NULL,
  version bigint(20) NOT NULL,
  PRIMARY KEY (context, id)
) COLLATE utf8_bin;
 
mysql> CREATE TABLE IF NOT EXISTS shibpid (
    localEntity VARCHAR(255) NOT NULL,
    peerEntity VARCHAR(255) NOT NULL,
    persistentId VARCHAR(50) NOT NULL,
    principalName VARCHAR(50) NOT NULL,
    localId VARCHAR(50) NOT NULL,
    peerProvidedId VARCHAR(50) NULL,
    creationDate TIMESTAMP NOT NULL,
    deactivationDate TIMESTAMP NULL,
    PRIMARY KEY (localEntity, peerEntity, persistentId)
);
 
mysql> CREATE USER 'shibboleth'@'localhost' IDENTIFIED BY 'GeHEIM007';
 
mysql> GRANT ALL PRIVILEGES ON shibboleth.* TO 'shibboleth'@'localhost';
 
mysql> FLUSH PRIVILEGES;

Der DB-Zugriff wird über den JDBCStorageService hergestellt. Dieser wird in ./conf/global.xml definiert. Diese Datei ist im Auslieferungszustand leer (bis auf Kommentare). Füllen Sie sie wie folgt:

./conf/global.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
 
       default-init-method="initialize"
       default-destroy-method="destroy">
 
    <!-- Use this file to define any custom beans needed globally. -->
 
    <!-- Die folgenden Werte sind Default-Werte:
           p:maxTotal="100"
           p:maxIdle="100"
         Es ist unter Umständen nötig, dass Sie diese Werte je nach Auslastung Ihres IdP anpassen,
         ebenso wie die Konfiguration Ihres MySQL-Servers. -->
        <bean id="shibboleth.MySQLDataSource"
          class="%{mysql.class}"
          p:driverClassName="org.mariadb.jdbc.Driver"
          p:url="%{mysql.url}"
          p:username="%{mysql.username}"
          p:password="%{mysql.password}"
          p:maxTotal="100"
          p:maxIdle="100"
          p:maxWaitMillis="15000"
          p:testOnBorrow="true"
          p:validationQuery="select 1"
          p:validationQueryTimeout="5" />
 
        <bean id="JDBCStorageService"
              parent="shibboleth.JDBCStorageService"
              p:cleanupInterval="%{idp.storage.cleanupInterval:PT10M}"
              p:dataSource-ref="shibboleth.MySQLDataSource" />  
 
</beans>

1. Die Shibboleth-Entwickler empfehlen nicht mehr das tomcat-pooling zu verwenden, sondern DBCP 2. Die Properties für den Datenbank-Zugriff werden jetzt noch entsprechend in ./conf/idp.properties ergänzt:

/opt/shibboleth-idp/conf/idp.properties
# ...
mysql.class    = org.apache.commons.dbcp2.BasicDataSource
mysql.url      = jdbc:mysql://localhost:3306/shibboleth
mysql.username = shibboleth
# ...

2. Das Passwort gehört wieder in die Datei ./credentials/secrets.properties:

mysql.password = GeHEIM007

3. Starten Sie Tomcat neu, um sicherzustellen, dass die ./conf/global.xml ohne Probleme eingelesen werden kann:

root@idp:~# systemctl restart tomcat10

Die Konfiguration der persistentId ist über mehrere Konfigurationsdateien verteilt. Die persistentId wird generiert aus einem im IdM vorhandenen Attribut und einem Hash.

Wählen Sie ein Quellattribut aus Ihrem IdM, das über die Zeit eindeutig bleibt! Bei OpenLDAP ist das oft die uid, bei Active Directory der sAMAccountName oder cn. Wenn Sie diese Attribute für neue Accounts wiederverwenden, dann müssen Sie ein anderes IdM-Attribut zur Generierung der persistentId verwenden.

Ein möglicher Workaround: Sie können sich in der ./conf/attribute-resolver.xml ein neues Attribut definieren. Dieses Attribut könnte aus einem Hash aus uid und dem Anlegedatum des Accounts bestehen. Beispiele zur Generierung finden Sie unter Persistent ID - Sonderfälle.

Das gewählte Quellattribut legen Sie in ./conf/saml-nameid.properties fest: Schauen Sie in ./conf/attribute-resolver.xml nach, welche „id“ das Quellattribut hat und tragen Sie sie hier ein. Es wird nicht der originale Attribut-Name aus dem IdM verwendet! Hier stellen Sie auch ein, dass die persistentIds in der MySQL-Datenbank gespeichert werden sollen.

/opt/shibboleth-idp/conf/saml-nameid.properties
idp.persistentId.sourceAttribute = uid
# BASE64 will match V2 values, we recommend BASE32 encoding for new installs.
idp.persistentId.encoding = BASE32
 
# To use a database, use shibboleth.StoredPersistentIdGenerator
idp.persistentId.generator = shibboleth.StoredPersistentIdGenerator
# For basic use, set this to a JDBC DataSource bean name:
idp.persistentId.dataSource = shibboleth.MySQLDataSource

Der Salt-Hash, mit dem die persistentIds generiert werden, wird aus Sicherheitsgründen in der zugriffsbeschränkten Passwortdatei ./credentials/secrets.properties hinterlegt. Er sollte möglichst beliebig, also zufällig generiert, und möglichst lang sein und mit niemandem geteilt werden.

/opt/shibboleth-idp/credentials/secrets.properties
# Bitte durch einen zufällig generierten Salt ersetzen!
idp.persistentId.salt = my-very-very-long-hash

Die Generierung der persistentIds muss separat angeschaltet werden. Entfernen Sie dazu den Kommentar bei shibboleth.SAML2PersistentGenerator in ./conf/saml-nameid.xml:

/opt/shibboleth-idp/conf/saml-nameid.xml
<beans ...>
    <!-- ... -->
    <!-- SAML 2 NameID Generation -->
    <util:list id="shibboleth.SAML2NameIDGenerators">
 
        <ref bean="shibboleth.SAML2TransientGenerator" />
 
        <!-- Uncommenting this bean requires configuration in saml-nameid.properties. -->
 
        <ref bean="shibboleth.SAML2PersistentGenerator" />
 
    </util:list>
    <!-- ... -->
</beans>

Die Verarbeitung der persistentId muss auch extra aktiviert werden:

/opt/shibboleth-idp/conf/c14n/subject-c14n.xml
<beans ...>
    <!-- ... -->
    <util:list id="shibboleth.SAMLSubjectCanonicalizationFlows">
        <!-- ... -->
 
        <!-- Handle a SAML 2 persistent ID, provided a stored strategy is in use. -->
        <ref bean="c14n/SAML2Persistent" />
 
        <!-- ... -->
    </util:list>
    <!-- ... -->
</beans>

Meist sind Usernamen in IdM-Systemen unabhängig von Groß- und Kleinschreibung: Nutzer*innen können ihre Anmeldenamen sowohl groß, als auch klein schreiben und sich damit erfolgreich anmelden. Die IdP-Datenbank unterscheidet jedoch zwischen Groß- und Kleinschreibung. Wir empfehlen daher, alle Usernamen im IdP in Kleinbuchstaben zu verarbeiten:

bis IdP 4.0.1

./conf/c14n/simple-subject-c14n-config.xml
   ...
   <util:constant id="shibboleth.c14n.simple.Lowercase" static-field="java.lang.Boolean.TRUE"/>
   ...

ab IdP 4.1.0

./conf/c14n/subject-c14n.properties
idp.c14n.simple.lowercase = true

Stellen Sie sicher, dass Ihre ./conf/attribute-resolver.xml unten bei den Data Connectors einen Abschnitt für die Datenbank enthält. Er kann dann als InputDataConnector in Attribut-Definitionen verwendet werden, in denen die persistentId verwendet werden soll (z.B. für die samlPairwiseID (Wert der persistentId + Scope)).

./conf/attribute-resolver.xml
    <DataConnector id="StoredId"
        xsi:type="StoredId"
        generatedAttributeID="persistentId"
        salt="%{idp.persistentId.salt}">
        <InputAttributeDefinition ref="%{idp.persistentId.sourceAttribute}" />
        <BeanManagedConnection>shibboleth.MySQLDataSource</BeanManagedConnection>
    </DataConnector>

Nachdem Datenbankverbindung und persistentId aktiviert sind, können diese nun für die Speicherung von Session- und User Consent-Informationen genutzt werden. Dadurch wird als netter Nebeneffekt auch SingleLogout-Unterstützung im IdP ermöglicht.

/opt/shibboleth-idp/conf/idp.properties
...
# Set to "shibboleth.StorageService" for server-side storage of user sessions
idp.session.StorageService = JDBCStorageService
 
# Set to "shibboleth.StorageService" or custom bean for alternate storage of consent
idp.consent.StorageService = JDBCStorageService
 
# Set to "shibboleth.consent.AttributeConsentStorageKey" to use an attribute
# to key user consent storage records (and set the attribute name)
idp.consent.attribute-release.userStorageKey = shibboleth.consent.PrincipalConsentStorageKey
idp.consent.attribute-release.userStorageKeyAttribute = %{idp.persistentId.sourceAttribute}
idp.consent.terms-of-use.userStorageKey = shibboleth.consent.PrincipalConsentStorageKey
idp.consent.terms-of-use.userStorageKeyAttribute = %{idp.persistentId.sourceAttribute}
 
# Flags controlling how built-in attribute consent feature operates
#idp.consent.allowDoNotRemember = true
# damit der User für jeden SP mindestens einmal einwilligen muß sollte
# die Möglichkeit für eine globale Einwilligung abgeschaltet werden:
idp.consent.allowGlobal = false
#idp.consent.allowPerAttribute = false
 
# Damit auch bei neuem Wert eines Attributes und bei neuem Terms-Of-Use-Text der
# User erneut abnicken muss. Da jetzt statt Browser-Cookies eine lokale DB zur
# Verfügung steht sollte dieses sinnvolle Feature aktiviert werden!
idp.consent.compareValues = true

Damit bei Attribute Queries Nutzer-Entscheidungen zur Attributfreigabe berücksichtigt werden, muss in ./conf/intercept/consent-intercept-config.xml die entsprechende Condition gesetzt werden. Ab dem IdP 4.1.0 müssen Sie zunächst das Intercept Consent-Modul aktivieren, damit Sie die Datei überhaupt haben:

bin/module.sh -t idp.intercept.Consent || bin/module.sh -e idp.intercept.Consent

Dann modifizieren Sie die Datei wie folgt:

./conf/intercept/consent-intercept-config.xml
    <!--
    Condition to evaluate to apply attribute-release consent to attribute queries.
    -->
    <bean id="shibboleth.consent.AttributeQuery.Condition" parent="shibboleth.Conditions.TRUE" />

Starten Sie Tomcat neu, um die neuen Einstellungen zu aktivieren:

root@idp:~# systemctl restart tomcat10

Um in den Fällen, in denen ein anfragender SP keine Präferenzen bzgl. Name ID Format signalisiert (Metadaten und/oder AuthnRequest), eine Gewichtung festzulegen (siehe hierzu die Doku im Shibboleth-Wiki), kann die SAML2.SSO-Bean-Definition bei Bedarf entsprechend erweitert werden:

/conf/relying-party.xml
<beans ...>
  <!-- ... -->
  <bean id="shibboleth.DefaultRelyingParty" parent="RelyingParty">
    <property name="profileConfigurations">
       <list>
         <!-- ... -->
         <bean parent="SAML2.SSO"
               p:postAuthenticationFlows="#{{'terms-of-use', 'attribute-release'}}"
               p:nameIDFormatPrecedence="#{{'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'}}"/>
         <!-- ... -->
        </list>
    </property>
  </bean>
  <!-- ... -->
</beans>

Starten Sie Tomcat neu, um die neuen Einstellungen zu aktivieren (dabei Logdateien mitverfolgen!):

root@idp:/opt/shibboleth-idp# systemctl restart tomcat10

Testen Sie nochmals einen Login mithilfe der DFN-Test-SP(s) und überzeugen Sie sich, dass die persistentId übertragen wird.

HINWEIS: Da die persistendId kein SAML-Attribut ist, wird Ihnen diese nach dem Login am IdP nicht in der Liste der zu übertragenden Attribute angezeigt. Erst wenn Sie wieder am Test-SP sind wird Ihnen dort die persistentId, sofern diese übertragen wurde, zusammen mit den übertragenen Attributen angezeigt.

Falls die persistentId nur an ausgewählte SPs übertragen werden soll, so finden sich hier einige Beispiele.

Die persistentID und das funktionsanaloge, als deprecated geltende Attribut eduPersonTargetedID sollen in Zukunft von der SAML pairwise-id (Konfigurationsbeispiel) abgelöst werden. Die erlaubten Werte der pairwise-id unterscheiden sich allerdings von denen der persistentID (siehe die Spezifikation):

  • Die pairwise-id hat einen Scope.
  • Die Werte werden mit BASE32 statt mit BASE64 kodiert. Sie dürfen also weniger Zeichen enthalten als alte persistentIDs und sind case-insensitive zu behandeln.

Für die Umstellung der persistentID auf die pairwise-id gibt es keinen perfekten Weg. Wir empfehlen folgendes Vorgehen, mit dem Sie vermeiden, alle Service Provider die persistentIDs bestehender Accounts umschreiben zu lassen:

  • Ändern Sie im IdP das Encoding der persistentIDs auf BASE32. Damit erreichen Sie, dass neu generierte persistentIDs so kodiert werden, dass sie als Basis für standardkonforme pairwise-ids verwendet werden können.
    /opt/shibboleth-idp/conf/saml-nameid.properties
    # BASE64 will match V2 values, we recommend BASE32 encoding for new installs.
    idp.persistentId.encoding = BASE32
  • Setzen Sie auch beim entsprechenden Data Connector in conf/attribute-resolver.xml das Encoding auf BASE32 wie in diesem Beispiel (siehe auch StoredIdConnector im Shibboleth-Wiki):
    /opt/shibboleth-idp/conf/attribute-resolver.xml
        <DataConnector id="StoredId"
            xsi:type="StoredId"
            generatedAttributeID="persistentID"
            salt="%{idp.persistentId.salt}"
            encoding="BASE32"
            queryTimeout="0">
            <InputAttributeDefinition ref="%{idp.persistentId.sourceAttribute}" />
            <BeanManagedConnection>shibboleth.MySQLDataSource</BeanManagedConnection>
        </DataConnector>
  • Bereits bestehende persistentIDs lassen Sie in der Datenbank bestehen, wie sie sind. Aus diesen persistentIDs werden dann zwar nicht standardkonforme SAML pairwise-ids gebildet. Wir gehen allerdings nicht davon aus, dass Service Provider, die die pairwise-id entgegennehmen, prüfen, ob der Wert vor dem Scope standardkonform ist.
  • Übermitteln Sie für die pairwise-id den Wert, der persistentID mit Scope.
  • Zuletzt geändert: vor 6 Tagen