Best Practice: Single Logout

Shibboleth-Session versus Anwendungssession

Das Single Logout (SLO) des Shibboleth IdP beendet lediglich die SAML-Session. Dabei kann die Anwendungssession bestehen bleiben. In diesem Tutorial zeigen wir, wie man die Anwendungssession direkt am Apache-Webserver an die Shibboleth-Session koppelt, um beide beim Logout zu beenden.

Viele Web-Anwendungen verwenden eine eigene Anwendungssession, die in Cookies gespeichert wird, und authentifizieren gegen Shibboleth. Dabei sind sie nicht unbedingt in der Lage, die Session auf Veränderungen zu prüfen, z.B. sie mit einem Remote-User zu assoziieren und so gegen Veränderungen zu schützen. (Siehe dazu auch die Diskussion im Shibboleth-Wiki.)

SLO ist möglich, wenn die Anwendung ein Session-Management verwendet, das robust gegen Manipulationen ist und das Shibboleth-Variablen verwendet. Dann ist die erforderliche Verbindung zwischen Shibboleth-Session und Anwendungs-Session gegeben: Nach dem Logout der Shibboleth-Session ist dann auch die Anwendungssession ungültig und es wird ein erneutes Login an Shibboleth angefordert.

Auf dieser Seite geht es darum, wie man verhindert, dass Nutzer*innen mit einer gültigen Anwendungssession, aber ohne gültige Shibboleth-Authentifizierung eine Webanwendung verwenden:

  • Abgelaufene oder auch mutwillig veränderte Anwendungssessions sollen nicht wiederverwendbar sein.
  • Shibboleth-Sessions sollen nicht manipulierbar oder weiterverwendbar sein.

Dazu wird ein Zusammenhang zwischen Shibboleth-Session und Anwendungssession hergestellt.

Sessions können auf drei verschiedene Arten initialisiert werden:

  1. normal: Die Anwendung erfordert immer eine Authentifizierung mit Shibboleth (Standardfall)
  2. lazy: Die Authentifizierung gegen Shibboleth erfolgt anwendungsgesteuert nach Erfordernis. Shibboleth ist die einzige Authentifizierungsmöglichkeit.
  3. mixedLazy: Die Authentifizierung erfolgt anwendungsgesteuert nach Erfordernis. Neben der Authentifizierung gegen Shibboleth kann auch gegen andere Mechanismen authentifiziert werden.

Die Lösungen variieren je nach Session-Initialisierungsmodus.

Einschränkungen: Es ist nicht möglich, auf einem Server mehrere Anwendungen im Modus „mixedLazy“ und „normal“ bzw. „lazy“ gemeinsam zu betreiben, da bei „mixedLazy“ der sessionHook mit dem zugehörigen Weiterleitungsskript nicht verwendet werden darf (s.u.). Der gemeinsame Betrieb von Anwendungen im Modus „normal“ und „lazy“ stellt kein Problem dar.

Da es nahezu unmöglich ist, alle Bedingungen im Apache-Webserver selbst abzubilden, wird hier auf eine externe Lösung zurückgegriffen: Am Apache-Webserver wird eine RewriteMap konfiguriert, die ein externes Programm aufruft.

Zusätzlich kommt die im Shibboleth-SP verfügbare Technologie des sessionHook zum Einsatz. Damit kann beim Login ein zusätzlicher Request erzeugt werden. Er wird benötigt, um zu erkennen, ob bereits eine Anwendungssession vorhanden ist (Skript s.u.).

Die RewriteMap wird mit Parametern vom Shibboleth-Daemon und vom Webserver aufgerufen. Parameter des Shibboleth-Daemons stehen nicht zu jeder Zeit über Environment-Variablen zur Verfügung. Daher muss die Konfigurationsdirektive ShibUseHeaders on benutzt werden.

Mögliche Probleme werden unter https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPSpoofChecking erörtert, jedoch wird vom Shibboleth-Daemon ein HeaderSpoofing mit dem Leeren der Shibboleth-Session quittiert, sodass die Gefahr als eher gering eingestuft werden kann. Es wird jedoch dringend empfohlen, in den Shibboleth nachgelagerten Anwendungen auf Environment-Variablen zuzugreifen.

Der Aufruf der RewriteMap mit den erforderlichen Parametern (s.u.) erzeugt vier Rückgabewerte

  1. doLogout, wenn versucht wurde,
    1. mit einer Anwendungssession ohne Shibboleth-Session zuzugreifen
    2. die Anwendungs-Session während der Sitzung zu verändern
    3. zusätzlich eine weitere Anwendungssession und / oder ein Shibboleth-Cookie einzuschleusen
    4. die Shibboleth-Session während der Sitzung zu verändern
  2. doLogin – nur bei Anwendungsszenario „lazy“
    Am Webserver muss mit einer RewriteRule auf das Login umgeleitet werden, sonst ist das Einschleusen einer vorhanden Anwendungs-Session möglich! Beim Login muss die Anwendungs-Session erzeugt werden! Siehe weiter unten bei Prüfskript Sonderzustände Punkt 1
  3. doAppSession
    Am Webserver muss mit einer RewriteRule die Initialisierung der Anwendungs-Session sicher gestellt werden. Da beim Login noch keine Anwendungs-Session vorhanden sein darf ist es möglich, dass unmittelbar nach dem erfolgreichen Shibboleth-Login noch keine Anwendungs-Session für die RewriteRule existiert, aber das Cookie schon existiert und somit verändert werden könnte, Siehe weiter unten bei Prüfskript Sonderzustände Punkt 2
  4. good
    wenn keine Aktion notwendig ist.

Mit Hilfe der RewriteMap wird das Prüfskript bei jeder Anfrage an den Webserver ausgeführt. Beim Login beträgt die dadurch zusätzlich verursachte Ausführungszeit bis zu 20 Millisekunden, danach benötigen die Prüfungen i.d.R. unter 0,5 Millisekunden.

Im Session-Initialisierungsmodus „mixedLazy“ sind neben Shibboleth weitere Authentifizierungsmethoden möglich. Bei einer Authentifizierung, die nicht gegen Shibboleth erfolgt, muss man sich auf die Schutzmechanismen der Anwendung verlassen.

Bei Authentifizierungen über Shibboleth können Anmeldungen mit einer bestehenden Session nicht von vornherein unterbunden werden, eine Duplikatsprüfung ist jedoch möglich. Das Prüfskript schaltet danach in den Modus „lazy“. In der Duplikatsprüfung wird untersucht, ob der Identifier der Anwendungssession schon einmal Verwendung fand. Wenn die Bereinigung richtig konfiguriert ist, bietet das quasi den gleichen Schutz wie das Verbot, sich mit einer bestehenden Anwendungssession anzumelden.

Ob eine Anwendungssession besteht, kann und darf hier nicht vorab mittels sessionHook geprüft werden!

Wenn das Prüfskript (s.u.) im Apache-Webserver für die RewriteMap konfiguriert wurde, wird es in der RewriteCond im zu schützenden Verzeichnis aufgerufen:

RewriteCond ${Context,Shib-Session-ID,Name des Cookies der Anwendungssession,Cookies,[,mixedLazy]} ^return$

Der Aufruf erfolgt mit 4 Parametern und einem optionalen Parameter:

  1. Context: sessionHook für Aufruf der RewriteMap für das Skript im SessionHook, normal oder lazy
  2. Shib-Session-ID: die Session-ID %{HTTP:Shib-Session-ID}
  3. Name des Cookies der Anwendungssession: z.B. APPSESSIONNAME
  4. Cookies: alle Cookies %{HTTP:COOKIE}
  5. optional: mixedLazy: Angabe nur, wenn die Anwendung neben Shibboleth noch andere Authentifizierungsmethoden unterstützt

Das Prüfskript führt im ersten Schritt grundsätzliche Validierungen durch:

Zuerst wird geprüft, ob die Shib-Session-ID aus Parameter 2 mit der ermittelten Shib-Session-ID aus dem übertragenen Cookie (Parameter 4) übereinstimmt. Falls nicht, ist die Anfrage ungültig. Dies dient zur Erkennung von Manipulationen am Session-Cookie bzw. an den Headern. Weiterhin wird in diesem Schritt auch geprüft, ob versucht wurde, zusätzliche Anwendungssessions und / oder Cookies mit Shibboleth-Sessions einzuschleusen. Ist dies der Fall, ist die Anfrage ungültig.

Im Context „sessionHook“ wird geprüft, ob keine Anwendungssession vorhanden ist. Bei „normal“ und „lazy“ werden die Anwendungssession und die Shibboleth-Session gegen ein Muster geprüft. Beide Prüfungen müssen erfolgreich sein. Ist diese Prüfung erfolgreich, wird die erweiterte Prüfung durchgeführt.

In der erweiterten Prüfung wird untersucht, ob die Shib-Session-ID im Datenspeicher existiert und ob die dazu passende Anwendungssession-ID geliefert wird. Sind die Anwendungssession-IDs unterschiedlich, ist die Anfrage ungültig. Ist sie gültig, handelt es sich um einen neuen Eintrag, der dann im Datenspeicher vermerkt wird.

Zusätzlich erfolgt bei „mixedLazy“ bei einem Neueintrag die Prüfung, ob die Anwendungssession-ID bereits vergeben wurde. Wenn ja, ist die Anfrage ungültig.

Es existieren noch zwei Sonderzustände:

  1. Im Modus „lazy“ gibt es beim ersten Aufruf einen Zustand, bei dem weder die Shib-Session-ID noch die Anwendungs-Session-ID vorhanden ist. Dies muss eine gültige Anfrage sein. Hier wird als Rückgabeparameter doLogin geliefert. Es liegt bei der Administration, auf diesen Zustand mit einer RewriteRule zum Login zu reagieren, falls die Anwendung nicht selbsttätig zum Shibboleth-Login weiterleitet. Achtung: Erfolgt keine Weiterleitung zum Login, so entsteht hier eine Sicherheitslücke, da dann eine ungewollte Anwendungssession eingeschleust werden kann!
  2. Im Moment der durchgeführten Authentifizierung mit Shibboleth darf noch keine Anwendungssession vorhanden sein, aber es muss eine Shib-Session-ID existieren. Dieser Zustand muss als gültige Anfrage erklärt werden, aber es muss noch sichergestellt werden, dass zur Shib-Session-ID die korrekte Anwendungssession zugeordnet wird. Hier wird als Rückgabeparameter doAppSession geliefert. Auf diesen Zustand muss die Administration reagieren, sonst besteht die Gefahr, dass ein Cookie einer vorherigen Session eingeschleust wird! Es muss ein zusätzlicher Request erfolgen, in dem die Anwendungssession in den Variablen für das Prüfskript zur Verfügung gestellt wird. Hierzu bestehen zwei Möglichkeiten:
    1. Empfohlen: Es ist möglich, die Session mittels einer Umleitung auf ein zusätzliches Skript zu erzeugen (funktioniert bei PHP).
    2. Alternativ: Am Apache-Webserver kann dem Request ein Refresh-Header hinzugefügt werden, der einmalig für den Reload der Seite sorgt.

Voraussetzungen

  • php → getestet mit php5, php7(.2)
  • memcached auf localhost (oder ggf. in Skripten anpassen)
  • php-memcache (nicht php-memcached!!!)
  • shibd Version >= 2.5
  • Apache-Webserver mit mod_rewrite und mod_headers

Das Prüfskript ist in PHP geschrieben und verwendet memcached. Es steht unter der GPLv3.

shibchecker.php
<?php
/*
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
    Copyright 2015 Frank Schreiterer, University of Bamberg, Computing Centre     
*/ 
 
#unbegrenzt ausführen
set_time_limit(0);
#set logging to true or false
$logging=true;
#Pfad zur Logfile
$logfile="/var/log/apache2/map.log";
#apt-get install php5-memchache NICHT php5-memchached!
$mcsrv="127.0.0.1";
$mcport="11211";
#Lebenzeit der Einträge in Sekunden -> Wert muss an die SessionLifeTime der Shibboleth-Session und der Anwendungs-Session angepasst sein
#hier 12 Stunden
$mcExpire=43200;
#Das Prüfmuster für die Session-ID der Anwendung
$regexappsessionid="/^[a-z0-9]{26}$/";
### Ende User-Konfig ###

#es wird versucht, den Overhead für die Verbingung durch die ständige Verbindung zu minimieren,
#schlägt bei memcached die Verbindung fehl, so wird die Verbindung selbständig wieder aufgebaut
$mc=new Memcache;
$mc->connect($mcsrv,$mcport);
 
$keyboard = fopen("php://stdin","r");
$out = fopen("php://stdout","w");
while (1) {		
	#Variablen initialisieren
	$shibvalid=false;
	$appvalid=false	;
	$valid=true;
	$return="";
	$returnmsg="";
	$logstr = "";
	$mixedlazy = false;	
	$checkappsessionid = false;
	$appsessionid="";
	$shibcookiesessionid="";
	$appsessionidmatch=0;
	$shibcookiesessionidmatch=0;
	#Ende Variablen initialisieren	
	$input = trim(fgets($keyboard));
	$startts = microtime(true);
	#input zerlegen
	$tmp = explode(",",$input);
	#Context -> Parameter 1 bei Aufruf RewriteMap sessionHook wenn über den LoginHook des Shibd aufgerufen wird, lazy für ausschließlich Lazy Session und normal für normale Shib-Session
	#Der Parameter 5 ist optional und wird verwendet, um dem shibcheker mitzuteilen, dass es sich um die Prüfung im Modus mixedLazy handelt, d. h. die Anwendung unterstützt neben
	#Shibboleth Lazy Session und ander Authentifizungen.
	$context = strtolower(trim($tmp[0]));
	#Shib-Session-ID -> Parameter 2 bei Aufruf RewriteMap
	$shibsessionid = trim($tmp[1]);
	#Name der Anwendungssession -> Parameter 3 bei Aufruf RewriteMap
	$sesscookie = trim($tmp[2]);
	#die gesetzen Cookies, aus denen der Wert der Anwendungssession extrahiert werden muss -> Parameter 4 bei Aufruf ReweriteMap
	$cookies = $tmp[3];
	#Parameter 5 mixedLazy für Modus mixedLazy, falls die shibsessionid leer ist	
	if (isset($tmp[4]) && strtolower(trim($tmp[4])) == "mixedlazy") {
		#Prüfung der übermittelten appsessionid auf Duplikate, wenn im Parameter 5 mixedLazy übergeben wurde
		$checkappsessionid = true;
		if ($shibsessionid == "") {
			$mixedlazy = true;		
		}
	}		
 
	#aus den Cookies die ID der Anwendungssession extrahieren	
	$regexapp="/^$sesscookie=.*$/";
	$regexshib="/^_shibsession_[a-z0-9]+=_[a-z0-9]{32}/";
	$tmp = explode(";",$cookies);	
	foreach ($tmp as $cookie) {
		$cookie = trim($cookie);
		if (preg_match($regexapp,$cookie)) {
			$appsessionid = trim(str_replace($sesscookie."=","",$cookie));
			$appsessionidmatch++;
		}
		if (preg_match($regexshib,$cookie)) {
			$shibcookiesessionid = trim(preg_replace("/_shibsession_[a-z0-9]+=/","",$cookie));			
			$shibcookiesessionidmatch++;
		}
	}	
	#Es wurde am Shib-Session-Cookie nichts verändert, Veränderung am Cookienamen werden von Shibd mit einer leeren Session-ID im Header quitiert, jedoch 
	#bleibt das Cookie ansich mit der Session-ID bestehen
	#wird mehr als einmal versucht, ein Cookie mit der Anwendungssession bzw. Shibbolethsession zu übergeben, ist das auch verboten
	if ($shibsessionid == $shibcookiesessionid && $appsessionidmatch < 2 && $shibcookiesessionidmatch < 2) {		
		#wenn mixedLazy, dann können wir uns alle weiteren Prüfungen sparen, da die Shibboleth-Checks nur ausgeführt werden können, 
		#wenn auch die Anmeldung über Shibboleth erfolgt. 
		#Anderenfalls liegt Die Prüfung der übergebenen Anwendungssession in der Verantwortung der Anwendung und kann hier nicht geprüft werden.		
		if ($mixedlazy == false) {	
			#grundlegende Prüfungen			
			#shibsessionid
			if (preg_match("/^_[a-z0-9]{32}$/",$shibsessionid)) {
				$shibvalid=true;
			} elseif ($shibsessionid != "") {
				#eine ungültige ID der Shibboleth-Session wurde übermittelt
				$logstr = "$logstr\nShibboleth-Session-Id $shibsessionid ungültig";
				$valid = false;
			}
			#appsessionid - hier ggf. Anpassungen vornehmen (ist für PHP-Session)
			if (preg_match($regexappsessionid,$appsessionid)) {
				$appvalid=true;
			} elseif ($appsessionid != "") {
				#eine ungültige ID der Anwendungs-Session wurde übermittelt
				$valid = false;	
				$logstr = "$logstr\nAnwendungs-Session-Id $appsessionid ungültig";	
			}
			#ist die Anwendungs-Session gesetzt und noch keine Shib-Session gesetzt ist das böse
			if ($shibvalid == false && $appvalid == true) {
				$valid = false;		
				$logstr = "$logstr\nAnwendungs-Session-Id $appsessionid vor Shibboleth-Session gesetzt";	
			}				
			#wurde über den sessionHook aufgerufen, dann darf im Anwendungscookie nichts drin stehen
			if ($context == "sessionhook" && $appsessionid != "") {
				$valid = false;
				$logstr = "$logstr\nAnwendungs-Session-Id $appsessionid im Modus sessionHook gesetzt - verboten";	
			} 						
			#erweiterte Prüfung nur notwendig, wenn nicht abgemeldet werden soll und nicht der sessonHook geprüft wird
			if ($valid == true && $context != "sessionhook") {
				#nur weiter prüfen, wenn die Anwendungs-Session und die Shib-Session gültig sind
				if ($shibvalid == true && $appvalid == true) {
					#schauen, ob es die Kombination der Sessions schon gibt
					$item = $mc->get($shibsessionid);
					#bei memcached-Cluster hier noch aus den anderen lesen und ggf. Werte lokal schreiben
					if ($item == false) {
						$logstr = "$logstr\nneue Shibboleth-Session $shibsessionid";
						if ($checkappsessionid == true) {	
							#noch prüfen, ob die Anwendungssession nicht schon einmal vergeben wurde, so kann bei mixedLazy die Gefahr minimiert werden, dass versucht wird, mit einer alten Anwendungssession zuzugreifen
							$get = $mc->get($appsessionid);
							if ($get != false) {
								$valid = false;
								$logstr = "$logstr\nAnwendungs-Session-Id $appsessionid bereits vergeben - ungültig";	
							} else {
								$logstr = "$logstr\nPrüfung auf doppelte Anwendungs-Session-Id $appsessionid erfolgreich - Anwendungs-Session-Id $appsessionid nicht vorhanden";
							}
						}
						#nur weiter, wenn noch gültig
						if ($valid == true) {	
							#Eintrag in die Sicherungstabelle zur Session schreiben, wenn auch eine Anwendungssession da ist 
							if ($appsessionid != "") {			
								$mc->set($shibsessionid,$appsessionid,false,$mcExpire);	
								$logstr = "$logstr\nSchreibe Key Shibboleth-Session $shibsessionid mit Wert der Anwendungs-Session $appsessionid";
								#bei memcached-Cluster hier noch in die weiteren schreiben
								if ($checkappsessionid == true ) {
									#und noch die appsessionid als key
									$mc->set($appsessionid,"$shibsessionid",false,$mcExpire);
									$logstr = "$logstr\nSchreibe Sicherungseintrag zur Anwendungs-Session $appsessionid";
								}
							}
						}
					} elseif ($valid == true) {
						#prüfen, ob versucht wurde, zur Shibboleth-Session eine andere Kombination mit der Anwendungs-Session unterzuschieben
						if ($item != $appsessionid) {
							$valid = false;
							$logstr = "$logstr\nAnwendungs-Session $appsessionid zur Shibboleth-Session $shibsessionid übermittelt, aber Wert der Anwendungs-Session $item erwartet - ungültig";
						} 
					}
				} #Anwendungs-Session und die Shib-Session gültig
				else {
					$valid = false;			
					#SONDERFÄLLE			
					#erster Aufruf bei Lazy-Session
					if ($shibsessionid == "" && $appsessionid == "" && $context == "lazy") {
						$returnmsg = "doLogin";
						$valid = true;
					}				
					#dieser Zustand darf bei normaler und bei lazy Session erreicht werden, bei sessionHook kommen wir gar nicht hier hin
					#es muss zwingend die Anwendungssession initialisiert werden, sonst ist es möglich, eine falsche Session zu verwenden
					if ($appsessionid == "" && $shibvalid == true) {
						$valid = true;
						$returnmsg = "doAppSession";
						$logstr = "$logstr\nShibboleth-Session $shibsessionid ohne Anwendungs-Session gefunden - gültig, aber Initialisierung der Anwendungs-Session erforderlich";
					}	
				}		
			} #erweiterte Prüfung	
		} # nicht mixedLazy
		else {
			$valid = true;
			$logstr = "$logstr\nModus mixedLazy ohne Shibboleth-Authentifizierung - Anfrage zugelassen";
		}	
	} #$shibsessionid == $shibcookiesessionid
	else {
		$shibvalid = false;
		$valid = false;		
		if ($shibsessionid != $shibcookiesessionid) {
			$logstr = "$logstr\nShibboleth-Session-Id vom Shibd ungleich Shibboleth-Session-Id aus Cookie";			
		} else {
			$logstr = "$logstr\nShibboleth- oder Anwendungs-Cookie mehrfach gesetzt";
		}		 
	}		
	#die Rückgabewerte
	if ($valid == false) {
		$return = "doLogout"; 
	} else {
		$return = "good";
		if ($returnmsg != "") {
			$return = $returnmsg;
		}
	}
	if ($logging == true) {
		$log = fopen($logfile,"a+");
		$now = date("Y-m-d H:i:s");
		$nowts = microtime(true);
		$extime = ($nowts - $startts)*1000;
		$logstr="\n$now Ausführungsdauer: $extime Millisekunden \nKontext: $context mixedLazy: $mixedlazy shibvalid: $shibvalid appvalid: $appvalid \nEingabe: $input \nshibsessionid: $shibsessionid \nappsessionid: $appsessionid $logstr \nreturn: $return\n";
		fwrite($log,$logstr);
		fclose($log);
	}
	fwrite($out,"$return\n");
}
?>

Um die Einträge beim Logout zu entfernen, wird in /etc/shibboleth/shibboleth2.xml das <Notify>-Element des Shibboleth-Daemons verwendet. Es wird unterhalb des <Session>-Elements definiert und kann für Front- und Back-Channel konfiguriert werden.

Über den Front-Channel werden die Cookies der Anwendung gelöscht. Über den Back-Channel werden die Einträge im Datenspeicher und die Anwendungssession in der Anwendung selbst zerstört.

Konfiguration:

shibboleth2.xml
...
<Errors supportContact="foobar@beispiel-uni.de"
            logoLocation="/shibboleth-sp/logo.jpg"
            styleSheet="/shibboleth-sp/main.css"/>
 
 <!-- Here we do the Session removal stuff for both channels-->
 <Notify Channel="back" Location="https://sp.beispiel-uni.de/PATH/TO/logoutnotify.php" />
 <Notify Channel="front" Location="https://sp.beispiel-uni.de/PATH/TO/logoutnotify.php" />
...

Das Skript logoutnotify.php wird hier zur Bereinigung aufgerufen:

logoutnotify.php
<?php
// Sample PHP 5 Shibboleth logout code by lukas.haemmerle@switch.change_user
// History:
// - December 8 2008:    Uploaded initial version that also was used for Moodle
 
// 2015: Changes for Shibchecker by frank.schreiterer@uni-bamberg.de
 
// Just for debugging the WSDL part
ini_set("soap.wsdl_cache_enabled", "0"); // disabling WSDL cache
 
/************************************************/
/* Sample code to log out user from application */
/************************************************/
 
// Requirements:
// PHP 5 with SOAP support (should be available in default deployment)
// PHP 5 memcache not memcached!!!
 
 
 
/* Note:
 * 
 * It is possible to call the Notify-hook twice in shibboleth2.xml 
 * <Notify Channel="back" Location="URI_TO/logoutnotify.php" />
 * <Notify Channel="front" Location="URI_TO/logoutnotify.php" />
 * 
 * So we do this and implement on front channel only the destruction for the application cookies an
 * on back channel the destruction for the application session an the removal of database / memcached - entries 
 * from the shibshecker RewriteMap in our apache configuration.
 * 
 * The connection parameters to the shibcheckerdb / memcached are set in function LogoutNotification.
*/
 
 
//////////////////////////
// Front channel logout //
//////////////////////////
 
// Note: Generally the back-channel logout should be used once the Shibboleth
//       Identity Provider supports Single Log Out!
//       Front-channel logout is not of much use.
 
if (
        isset($_GET['return'])
        && isset($_GET['action'])
        && $_GET['action'] == 'logout'
   ){
 
	//Only destroy application cookie via front channel and destroy the application session via back channel
 
    // Destroy PHP-session-cookie cookie
    if (isset($_COOKIE[session_name()])) {
        setcookie(session_name(), '', time()-42000, '/');
    }
 
    // Finally, send user to the return URL
    header('Location: '.$_GET['return']);    
    exit;
}
 
/////////////////////////
// Back channel logout //
/////////////////////////
 
// Note: This is the preferred logout channel because it also allows
//       administrative logout. However, it requires your application to be
//       adapated in the sense that the user's Shibboleth session ID must be
//       stored in the application's session data.
//       See function LogoutNotification below
 
elseif (!empty(file_get_contents("php://input"))) {
    // Set SOAP header
    $server = new SoapServer('https://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].'/LogoutNotification.wsdl');
    $server->addFunction("LogoutNotification");
    $server->handle();
}
 
/////////////////
// Return WSDL //
/////////////////
 
// Note: This is needed for the PHP SoapServer class.
//       Since I'm not a web service guru it might be that the code below is not
//       absolutely correct but at least it seems to to its job properly when it
//       comes to Shibboleth logout
 
else {
 
    header('Content-Type: text/xml');
 
    echo <<<WSDL
<?xml version ="1.0" encoding ="UTF-8" ?>
<definitions name="LogoutNotification"
  targetNamespace="urn:mace:shibboleth:2.0:sp:notify"
  xmlns:notify="urn:mace:shibboleth:2.0:sp:notify"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns="http://schemas.xmlsoap.org/wsdl/">
 
    <types>
       <schema targetNamespace="urn:mace:shibboleth:2.0:sp:notify"
           xmlns="http://www.w3.org/2000/10/XMLSchema"
           xmlns:notify="urn:mace:shibboleth:2.0:sp:notify">
 
            <simpleType name="string">
                <restriction base="string">
                    <minLength value="1"/>
                </restriction>
            </simpleType>
 
            <element name="OK" type="notify:OKType"/>
            <complexType name="OKType">
                <sequence/>
            </complexType>
 
        </schema>
    </types>
 
    <message name="getLogoutNotificationRequest">
        <part name="SessionID" type="notify:string" />
    </message>
 
    <message name="getLogoutNotificationResponse" >
        <part name="OK"/>
    </message>
 
    <portType name="LogoutNotificationPortType">
        <operation name="LogoutNotification">
            <input message="getLogoutNotificationRequest"/>
            <output message="getLogoutNotificationResponse"/>
        </operation>
    </portType>
 
    <binding name="LogoutNotificationBinding" type="notify:LogoutNotificationPortType">
        <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="LogoutNotification">
            <soap:operation soapAction="urn:xmethods-logout-notification#LogoutNotification"/>
        </operation>
    </binding>
 
    <service name="LogoutNotificationService">
          <port name="LogoutNotificationPort" binding="notify:LogoutNotificationBinding">
            <soap:address location="https://{$_SERVER['HTTP_HOST']}{$_SERVER['PHP_SELF']}"/>
          </port>
    </service>
</definitions>
WSDL;
    exit;
 
}
 
/******************************************************************************/
/// This function does the actual logout
function LogoutNotification($SessionID){
 
    // Delete session of user using $SessionID to locate the user's session file
    // on the file system or in the database
    // Then delete this entry or record to clear the session
    // However, for that to work it is essential that the user's Shibboleth
    // SessionID is stored in the user session data!
 
	//connection parameters to memcached
	$mcsrv="127.0.0.1";
	$mcport="11211";
 
	$mc=new Memcache;
	$mc->connect($mcsrv,$mcport);
	//get the application session id
	$appsessionid = $mc->get($SessionID);
	//remove 
	$ret = $mc-> delete($SessionID);
	$ret = $mc-> delete($appsessionid);
	if ($appsessionid == false) {
		$appsessionid = "";
	}
 
	//Connect to the application session (PHP Session)
	session_id($appsessionid);
	session_start();
	//and destroy
	$_SESSION = array();
	session_destroy();	
}
 
?>

FIXME Je nach Anwendungsszenario sind unterschiedliche Konfigurationen notwendig. Für alle Anwendungsszenarien muss die allgemeine Konfiguration durchgeführt werden.

Alle Konfigurationen sind für Apache 2.4. Man beachte, dass sich die Konfigurationselemente von Apache 2.2 nach Apache 2.4 geändert haben, siehe z.B. https://www.switch.ch/aai/support/presentations/techupdate-2014/12_Apache_24_vs_22.pdf, https://www.switch.ch/aai/guides/sp/access-rules/ und https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPApacheConfig.

FIXME Alle hier aufgeführten Skripte sind beispielhaft und müssen ggf. angepasst werden.

checker.php

Sorgt für den notwendigen zusätzlichen Request beim SessionHook.

checker.php
<?php
//redirect to application
header('Location: '.$_GET['return']);
?>

initsess.php ⇒ Erzeugt eine appsession

initsess.php
<?php
//initialize application session
session_start();
//applicationpath
$path = "Path/to/NORMALAPPLICATION";
//and redirect to application
$redirect = "https://".$_SERVER['SERVER_NAME']."/$path";
header('Location: '.$redirect);
?>

remsess.php ⇒ entfernt bei Zugriff mit einer ungültigen Kombination shibsession appsession beides sessions

remsess.php
<?php
 
/*
	This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
    Copyright 2015 Frank Schreiterer, University of Bamberg, Computing Centre     
*/ 
 
function removeMemcached($destid) {
	$mcsrv="127.0.0.1";
	$mcport="11211";
	$mc=new Memcache;
	$mc->connect($mcsrv,$mcport);
	$stats = $mc->getExtendedStats();
    $list = array();
    #memcached auslesen und bei passendem Wert den Key entfernen
    $allSlabs = $mc->getExtendedStats('slabs');
    $items = $mc->getExtendedStats('items');
    foreach($allSlabs as $server => $slabs) {
        foreach($slabs AS $slabId => $slabMeta) {
			if (is_numeric($slabId)) {
				$cdump = $mc->getExtendedStats('cachedump',(int)$slabId);
				foreach($cdump AS $keys => $arrVal) {
					if (!is_array($arrVal)) continue;
					foreach($arrVal AS $k => $v) { 
						$get = $mc->get($k);     
						if ($get == $destid) {            
							$ret = $mc->delete($k);
						}
					}
				}	
			}		
        }
    } 		
    #und bei mixedLazy den Sicherungseintrag zur destid
    $ret = $mc->delete($destid);
	$mc->close();		
}
 
#eine böse Anwendungs-Session-ID zerstören
if (isset($_REQUEST['appsid'])) {
	$destid = $_REQUEST['appsid'];
	if ($destid != "") {
		session_id($destid);
		removeMemcached($destid);
		session_start();		
		session_destroy();		
	}
}
 
session_start();
$serverurl="https://".$_SERVER['SERVER_NAME'];
if (isset($_REQUEST['shibloggedoff'])) {	
	$shiblogoff = $_REQUEST['shibloggedoff'];
	if ($shiblogoff == "true") {
		echo "Sie wurden abgemeldet.<br><br>Erneut <a href=\"$serverurl/PATH/TO/Login\">anmelden</a><br>";		
	}
} else {
 
	$destid = session_id();
	removeMemcached($destid);	
 
	$url="$serverurl/Shibboleth.sso/Logout?return=$serverurl/PATH/TO/remsess.php?shibloggedoff=true";
	header('Location: '.$url);
}
 
session_destroy();
#SessionCookie löschen
setcookie(session_name(),"",time() -3600,"/");
?>
  • Zuletzt geändert: vor 5 Jahren