Shibboleth IdP als OpenID Connect - Provider

Danke

Der Dank für diese Dokumentation geht an Manuel Haim von der Philipps-Universität Marburg.

Die OIDC-Login-Schnittstelle wird für Webanwendungen benötigt, die ausschließlich das OIDC- bzw. OAuth-2.0-Protokoll unterstützen.

root@idp:~# cd /opt/install/
root@idp:~# curl -O https://shibboleth.net/downloads/identity-provider/plugins/oidc-common/1.1.0/oidc-common-dist-1.1.0.tar.gz
root@idp:~# curl -O https://shibboleth.net/downloads/identity-provider/plugins/oidc-common/1.1.0/oidc-common-dist-1.1.0.tar.gz.asc
root@idp:~# curl -O https://shibboleth.net/downloads/identity-provider/plugins/oidc-op/3.0.1/idp-plugin-oidc-op-distribution-3.0.1.tar.gz
root@idp:~# curl -O https://shibboleth.net/downloads/identity-provider/plugins/oidc-op/3.0.1/idp-plugin-oidc-op-distribution-3.0.1.tar.gz.asc

Bei der Installation prüft der Plugin-Installer online die Kompatibilität der Plugin-Version zur Shibboleth-Version. Falls der Shibboleth-IdP nur über einen HTTP-Proxy Zugriff zum Internet erhält, muss für die Plugin-Installation zunächst ein HTTP-Proxy konfiguriert werden.

Im IdP <= 4.1.2 gibt es einen Bug (vgl. https://issues.shibboleth.net/jira/browse/IDP-1838), hier lässt sich die Kompatibilitäts-Prüfung mittels Parameter --nocheck umgehen:

root@idp:~# /opt/shibboleth-idp/bin/plugin.sh --nocheck -i oidc-common-dist-1.1.0.tar.gz
root@idp:~# /opt/shibboleth-idp/bin/plugin.sh --nocheck -i idp-plugin-oidc-op-distribution-3.0.1.tar.gz

Zur Konfiguration des HTTP-Proxy (IdP >= 4.1.3) müssen wir eine Datei /opt/install/beanfile.xml mit eigenen HttpClient-Parametern anlegen:

/opt/install/beanfile.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">
 
    <!--
    HttpClient bean for plugin installation.
    Use with /opt/shibboleth-idp/bin/plugin.sh -hc <BeanName> -i <plugin.tar.gz> <this-file.xml>
    -->
    <bean id="myHttpClient" parent="shibboleth.HttpClientFactory"
        p:connectionProxyHost="http-proxy.example.org"
        p:connectionProxyPort="3128" />
 
</beans>

Und dann mit folgenden Parametern installieren:

root@idp:~# /opt/shibboleth-idp/bin/plugin.sh -hc myHttpClient -i oidc-common-dist-1.1.0.tar.gz beanfile.xml
root@idp:~# /opt/shibboleth-idp/bin/plugin.sh -hc myHttpClient -i idp-plugin-oidc-op-distribution-3.0.1.tar.gz beanfile.xml
root@idp:~# /opt/shibboleth-idp/bin/plugin.sh -i oidc-common-dist-1.1.0.tar.gz
2021-07-21 11:07:04,309 - INFO [net.shibboleth.idp.installer.plugin.impl.PluginInstaller:233] - Installing Plugin net.shibboleth.oidc.common version 1.1.0
Installing Plugin net.shibboleth.oidc.common version 1.1.0
2021-07-21 11:07:04,400 - INFO [net.shibboleth.idp.installer.BuildWar:225] - Rebuilding /opt/shibboleth-idp/war/idp.war, Version 4.1.2
Rebuilding /opt/shibboleth-idp/war/idp.war, Version 4.1.2
2021-07-21 11:07:04,445 - INFO [net.shibboleth.idp.installer.BuildWar:225] - Initial populate from /opt/shibboleth-idp/dist/webapp to /opt/shibboleth-idp/webpapp.tmp
Initial populate from /opt/shibboleth-idp/dist/webapp to /opt/shibboleth-idp/webpapp.tmp
2021-07-21 11:07:05,614 - INFO [net.shibboleth.idp.installer.BuildWar:225] - Overlay from /opt/shibboleth-idp/dist/plugin-webapp to /opt/shibboleth-idp/webpapp.tmp
Overlay from /opt/shibboleth-idp/dist/plugin-webapp to /opt/shibboleth-idp/webpapp.tmp
2021-07-21 11:07:05,670 - INFO [net.shibboleth.idp.installer.BuildWar:225] - Overlay from /opt/shibboleth-idp/edit-webapp to /opt/shibboleth-idp/webpapp.tmp
Overlay from /opt/shibboleth-idp/edit-webapp to /opt/shibboleth-idp/webpapp.tmp
2021-07-21 11:07:05,736 - INFO [net.shibboleth.idp.installer.BuildWar:217] - Creating war file /opt/shibboleth-idp/war/idp.war
Creating war file /opt/shibboleth-idp/war/idp.war
root@idp:~# /opt/shibboleth-idp/bin/plugin.sh -i idp-plugin-oidc-op-distribution-3.0.1.tar.gz
Plugin net.shibboleth.idp.plugin.oidc.op: Trust store folder does not exist, creating
Plugin net.shibboleth.idp.plugin.oidc.op: Trust store does not exist, creating
TrustStore does not contain signature 0X26691839355EBCA
Accept this key:
Signature:	0X26691839355EBCA
FingerPrint:	6D18FD63708FCCA079B68CCE026691839355EBCA
Username:	Henri Mikkonen <henri.mikkonen@iki.fi>
 [yN] y
Installing Plugin net.shibboleth.idp.plugin.oidc.op version 3.0.1
Rebuilding /opt/shibboleth-idp/war/idp.war, Version 4.1.2
Initial populate from /opt/shibboleth-idp/dist/webapp to /opt/shibboleth-idp/webpapp.tmp
Overlay from /opt/shibboleth-idp/dist/plugin-webapp to /opt/shibboleth-idp/webpapp.tmp
Overlay from /opt/shibboleth-idp/edit-webapp to /opt/shibboleth-idp/webpapp.tmp
Creating war file /opt/shibboleth-idp/war/idp.war
Module file changes as a result of this install
	conf/oidc-clientinfo-resolvers.xml created
	static/openid-configuration.json created
	bin/lib/json-web-key-generator-0.8.2-jar-with-dependencies.jar created
	conf/oidc-credentials.xml created
	conf/attributes/oidc-claim-rules.xml created
	bin/jwtgen.sh created
	conf/examples/oidc-attribute-filter.xml created
	bin/jwtgen.bat created
	conf/oidc.properties created
	conf/examples/oidc-attribute-resolver.xml created

Datei /opt/shibboleth-idp/conf/credentials.xml anpassen:

/opt/shibboleth-idp/conf/credentials.xml
...
    <!-- OIDC extension default credential definitions -->
    <import resource="oidc-credentials.xml" />
...

Datei /opt/shibboleth-idp/conf/attributes/default-rules.xml anpassen:

/opt/shibboleth-idp/conf/attributes/default-rules.xml
...
    <import resource="oidc-claim-rules.xml" />
...

JSON Web Keys erzeugen:

root@idp:~# /opt/shibboleth-idp/bin/jwtgen.sh -t RSA -s 2048 -u sig -i defaultRSASign | tail -n +2 >/opt/shibboleth-idp/credentials/idp-signing-rs.jwk
root@idp:~# /opt/shibboleth-idp/bin/jwtgen.sh -t EC -c P-256 -u sig -i defaultECSign | tail -n +2 >/opt/shibboleth-idp/credentials/idp-signing-es.jwk
root@idp:~# /opt/shibboleth-idp/bin/jwtgen.sh -t RSA -s 2048 -u enc -i defaultRSAEnc | tail -n +2 >/opt/shibboleth-idp/credentials/idp-encryption-rsa.jwk

Datei /opt/shibboleth-idp/conf/oidc.properties anpassen:

/opt/shibboleth-idp/conf/oidc.properties
...
idp.oidc.issuer = https://idp.example.org
...

Wir wollen unter https://idp.example.org/.well-known/openid-configuration die Konfigurationsparameter dynamisch bereitstellen, vgl. https://shibboleth.atlassian.net/wiki/spaces/IDPPLUGINS/pages/1376879256/OPDiscovery

In der Datei /opt/shibboleth-idp/static/openid-configuration.json muss jeweils {{ service_name }} durch Ihren Wert für idp.example.org ausgetauscht werden.

Datei /opt/shibboleth-idp/conf/relying-party.xml bearbeiten:

/opt/shibboleth-idp/conf/relying-party.xml
...
    <bean id="shibboleth.UnverifiedRelyingParty" parent="RelyingParty">
        <property name="profileConfigurations">
            <list>
                <bean parent="OIDC.Configuration" />
                <!-- <bean parent="SAML2.SSO" p:encryptAssertions="false" /> -->
            </list>
        </property>
    </bean>
...

In unserem Fall wird alles unter dem URL-Pfad /idp/ vom Tomcat bereitgestellt, der Basispfad / hingegen von Apache (Einrichtung weiter unten). Daher müssen wir das Redirect für /.well-known/openid-configuration im Apache einrichten, wie nachfolgend beschrieben:

Datei /etc/apache2/sites-enabled/25-apache-443.conf (oder ähnlich) bearbeiten:

...
  Redirect seeother /.well-known/openid-configuration https://idp.example.org/idp/profile/oidc/configuration
...

Alternativ können die Konfigurationsparameter wie folgt statisch bereitgestellt werden (erfordert a2enmod headers):

  Alias /.well-known/openid-configuration /opt/shibboleth-idp/static/openid-configuration.json
  <Location "/.well-known/openid-configuration">
    ForceType application/json
    Header set Access-Control-Allow-Origin *
  </Location>

Schließlich müssen noch die entsprechenden OIDC-Profile in Shibboleth aktiviert werden. Wir wollen OIDC-Clients manuell hinzufügen und lassen das Profil „OIDC-Registration“ daher weg. Außerdem sollen auch beim OIDC-Login dieselben Flows wie beim Shibboleth-Login berücksichtigt werden.

Datei /opt/shibboleth-idp/conf/relying-party.xml bearbeiten:

/opt/shibboleth-idp/conf/relying-party.xml
...
    <bean id="shibboleth.UnverifiedRelyingParty" parent="RelyingParty">
        <property name="profileConfigurations">
            <list>
                <bean parent="OIDC.Configuration" />
                <bean parent="OIDC.Keyset" />
                <!-- <bean parent="SAML2.SSO" p:encryptAssertions="false" /> -->
            </list>
        </property>
    </bean>
...
    <!-- Default configuration, with default settings applied for all profiles. -->
    <bean id="shibboleth.DefaultRelyingParty" parent="RelyingParty">
        <property name="profileConfigurations">
            <list>
...
                <bean parent="OIDC.SSO"
                     p:postAuthenticationFlows="#{ {'terms-of-use', 'context-check', 'attribute-release'} }" />
                <ref bean="OIDC.UserInfo" />
                <ref bean="OAUTH2.Revocation" />
                <ref bean="OAUTH2.Introspection" />
            </list>
        </property>
    </bean>
...

Tomcat und Apache neustarten:

root@idp:~# systemctl restart tomcat9
root@idp:~# systemctl restart apache2

Wenn soweit alles ohne Fehler läuft (vgl. Shibboleth-Logs), können wir die Attribute konfigurieren.

Für OIDC sollten eine globale subject-id und/oder eine anwendungsbezogene pairwise-id definiert werden. Nachfolgend ein Beispiel basierend auf uid und persistentId:

In der Datei /opt/shibboleth-idp/conf/attribute-resolver.xml weitere Attribute analog zu /opt/shibboleth-idp/conf/examples/oidc-attribute-resolver.xml ergänzen:

/opt/shibboleth-idp/conf/attribute-resolver.xml
<?xml version="1.0" encoding="UTF-8"?>
<AttributeResolver
        xmlns="urn:mace:shibboleth:2.0:resolver" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:oidc="urn:mace:shibboleth:2.0:resolver:oidc"
        xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd
                            urn:mace:shibboleth:2.0:resolver:oidc http://shibboleth.net/schema/oidc/shibboleth-attribute-encoder-oidc.xsd">
...
    <!-- OIDC subjects -->
    <AttributeDefinition id="subject-public" xsi:type="Scoped" scope="%{idp.scope}"
            activationConditionRef="shibboleth.oidc.Conditions.PublicRequired">
        <InputDataConnector ref="myLDAP_account" attributeNames="uid" />
        <AttributeEncoder xsi:type="oidc:OIDCScopedString" name="sub" />
    </AttributeDefinition>
 
    <AttributeDefinition id="subject-pairwise" xsi:type="Scoped" scope="%{idp.scope}"
            activationConditionRef="shibboleth.oidc.Conditions.PairwiseRequired">
        <InputDataConnector ref="myStoredId" attributeNames="persistentId"/>
        <AttributeEncoder xsi:type="oidc:OIDCScopedString" name="sub" />
    </AttributeDefinition>
 
    <!-- gender -->
    <AttributeDefinition id="gender" xsi:type="Mapped">
        <InputDataConnector ref="myLDAP_people" attributeNames="UniMrAnrede" />
        <DefaultValue></DefaultValue>
        <ValueMap>
            <ReturnValue>male</ReturnValue>
            <SourceValue>Herr</SourceValue>
        </ValueMap>
        <ValueMap>
            <ReturnValue>female</ReturnValue>
            <SourceValue>Frau</SourceValue>
        </ValueMap>
    </AttributeDefinition>
...

In Datei /opt/shibboleth-idp/conf/attribute-filter.xml weitere Attribute analog zu /opt/shibboleth-idp/conf/examples/oidc-attribute-filter.xml ergänzen:

/opt/shibboleth-idp/conf/attribute-filter.xml
...
    <!-- OIDC scopes -->
    <AttributeFilterPolicy id="OPENID_SCOPE">
        <PolicyRequirementRule xsi:type="oidc:OIDCScope" value="openid" />
        <AttributeRule attributeID="subject-public">
            <PermitValueRule xsi:type="ANY" />
        </AttributeRule>
        <AttributeRule attributeID="subject-pairwise">
            <PermitValueRule xsi:type="ANY" />
        </AttributeRule>
    </AttributeFilterPolicy>
 
    <AttributeFilterPolicy id="OPENID_SCOPE_EMAIL">
        <PolicyRequirementRule xsi:type="oidc:OIDCScope" value="email" />
        <AttributeRule attributeID="mail">
            <PermitValueRule xsi:type="ANY" />
        </AttributeRule>
        <AttributeRule attributeID="email_verified">
            <PermitValueRule xsi:type="ANY" />
        </AttributeRule>
    </AttributeFilterPolicy>
 
    <AttributeFilterPolicy id="OPENID_SCOPE_PROFILE">
        <PolicyRequirementRule xsi:type="oidc:OIDCScope" value="profile" />
        <AttributeRule attributeID="displayName">
            <PermitValueRule xsi:type="ANY" />
        </AttributeRule>
        <AttributeRule attributeID="sn">
            <PermitValueRule xsi:type="ANY" />
        </AttributeRule>
        <AttributeRule attributeID="givenName">
            <PermitValueRule xsi:type="ANY" />
        </AttributeRule>
        <AttributeRule attributeID="uid">
            <PermitValueRule xsi:type="ANY" />
        </AttributeRule>
        <AttributeRule attributeID="gender">
            <PermitValueRule xsi:type="ANY" />
        </AttributeRule>
        <AttributeRule attributeID="eduPersonPrincipalName">
            <PermitValueRule xsi:type="ANY" />
        </AttributeRule>
    </AttributeFilterPolicy>
...

OIDC-Clients (Relying Party, RP) können am besten über XML-Metadaten hinzugefügt werden, vgl. https://shibboleth.atlassian.net/wiki/spaces/SC/pages/1912406916/OAuthRPMetadataProfile.

<md:EntityDescriptor
        xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
        xmlns:oidcmd="urn:mace:shibboleth:metadata:oidc:1.0"
        entityID="rp.example.org">
    <md:SPSSODescriptor protocolSupportEnumeration="http://openid.net/specs/openid-connect-core-1_0.html">
        <md:Extensions>
            <oidcmd:OAuthRPExtensions
                grant_types="authorization_code"
                response_types="code"
                token_endpoint_auth_method="client_secret_basic"
                scopes="openid profile email" />
            <mdui:UIInfo xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
                <mdui:DisplayName xml:lang="de">Beispiel-OIDC-RP</mdui:DisplayName>
                <mdui:DisplayName xml:lang="en">Example OIDC RP</mdui:DisplayName>
                <mdui:Description xml:lang="de">Beispiel-OIDC-RP der Hochschule XY.</mdui:Description>
                <mdui:Description xml:lang="en">Example OIDC RP of University XY.</mdui:Description>
                <mdui:InformationURL xml:lang="de">https://rp.example.org</mdui:InformationURL>
                <mdui:InformationURL xml:lang="en">https://rp.example.org</mdui:InformationURL>
                <mdui:Logo height="93" width="260">https://rp.example.org/logo.png</mdui:Logo>
            </mdui:UIInfo>
        </md:Extensions>
        <md:KeyDescriptor>
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <oidcmd:ClientSecret>HIER-STEHT-EIN-GEHEIMES-SECRET</oidcmd:ClientSecret>
            </ds:KeyInfo>
        </md:KeyDescriptor>
        <md:NameIDFormat>urn:mace:shibboleth:metadata:oidc:1.0:nameid-format:public</md:NameIDFormat>
        <md:AssertionConsumerService
                Binding="https://tools.ietf.org/html/rfc6749#section-3.1.2"
                Location="https://rp.example.org/OIDC-ODER-OAUTH-ENDPOINT"
                index="1"/>
    </md:SPSSODescriptor>
    <md:Organization>
        <md:OrganizationDisplayName xml:lang="de">Hochschule XY</md:OrganizationDisplayName>
        <md:OrganizationDisplayName xml:lang="en">University XY</md:OrganizationDisplayName>
        <md:OrganizationURL xml:lang="de">https://www.example.org</md:OrganizationURL>
        <md:OrganizationURL xml:lang="en">https://www.example.org</md:OrganizationURL>
    </md:Organization>
</md:EntityDescriptor>

Für jeden OIDC-Client muss eine client_id festgelegt werden (z.B. 32 Hex-Zeichen langer Zufallswert oder ein FQDN, aber keine URL) sowie ein client_secret (z.B. per makepasswd --chars=32) und die redirect_uri.

Die für die Client-Konfiguration nötigen Parameter können per https://idp.example.org/.well-known/openid-configuration abgefragt werden. Nicht unterstützte Scopes sollten in /opt/shibboleth-idp/static/openid-configuration.json entfernt werden.

Die freigegebenen Shibboleth-Attribute werden gemäß /opt/shibboleth-idp/conf/attributes/oidc-claim-rules.xml in OIDC-Attribute übersetzt.

Das OIDC-Plugin unterstützt bislang noch kein Logout, und die OIDC-RPs werden auch nicht auf der Logout-Seite angezeigt: https://issues.shibboleth.net/jira/browse/JOIDC-13

Die OIDC-Attribute werden im Attribute Release bislang nur mit englischen Beschreibungstexten angezeigt. Zur Korrektur müssen die entsprechenden deutschen Beschreibungstexte unter /opt/shibboleth-idp/conf/attributes/oidc-claim-rules.xml ergänzt werden.

  • Zuletzt geändert: vor 7 Wochen