Laden...

Timeout-Problem mit FormsAuthentication und Ajax

Erstellt von Yheeky vor 12 Jahren Letzter Beitrag vor 12 Jahren 4.205 Views
Y
Yheeky Themenstarter:in
200 Beiträge seit 2008
vor 12 Jahren
Timeout-Problem mit FormsAuthentication und Ajax

Hi,

ich habe eine Webseite mit FormsAuthentication und habe in der web.config testweise einen Timeout von 1 Minute gesetzt.

<authentication mode="Forms">
      <forms loginUrl="~/Home/Index"
             timeout="1"
             defaultUrl="~/Home/Index"
             slidingExpiration="true"
             protection="All"
             enableCrossAppRedirects="false" />
    </authentication>

Ich habe in diversen Artikeln gelesen, dass dieser Wert jedoch nicht immer eine Auswirkung hat. Deswegen habe ich beim Login das FormsAuthenticationTicket selbst gesetzt:

var ticket = new FormsAuthenticationTicket(
                        1,
                        username,
                        DateTime.Now,
                        DateTime.Now.AddMinutes(1),
                        false,
                        String.Format("{0},{1}", username, password),
                        FormsAuthentication.FormsCookiePath
            );

            var encryptedTicket = FormsAuthentication.Encrypt(ticket);
            var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
            {
                HttpOnly = FormsAuthentication.RequireSSL,
                Path = FormsAuthentication.FormsCookiePath,
                Domain = FormsAuthentication.CookieDomain
            };
            Response.AppendCookie(cookie);

Das Problem ist nun, dass der Timeout NIE kommt! Ich habe längere Zeit überlegt und bin dann auf die Idee meiner AJAX-Calls gekommen. Ich lasse nämlich, wenn ein Benutzer eingeloggt ist, alle 10 Sekunden überprüfen, ob er neue Nachrichten im Postfach hat.

Ist es nun so, dass dieser Request immer dafür sorgt, dass das Timeout neu gesetzt wird? Wenn dem so ist: wie kann ich bei einer Anwendung, die AJAX-Calls verwendet, trotzdem einen Timeout hevorrufen lassen?
Ich hätte gerne eine Online/Offline Funktion und die funktioniert momentan natürlich so nicht.

Bin für jeden Tipp dankbar!

Gruß Yheeky

3.170 Beiträge seit 2006
vor 12 Jahren

Hallo,

die FormsAuthentication basiert auf Cookies, und die werden auch bei AJAX-Calls mitgeschickt. Das sit ja auch sinnvoll, weil Du ja schließlch serverseitig wissen willst, um welchen Benutzer es sich handelt. Sonst könntest Du ja nicht dessen Postfach überprüfen.
Ich glaube nicht, dass sich das automatisch umgehen lässt.

Du müsstest Dir also selbst (z.B. in der Session) merken, wann die letzte tatsächliche Benutzerinteraktion stattgefunden hat - es werden ja vermutlich auf dem Server dann andere Handler ausgeführt alsa beim automatisierten AJAX-Call.
Diese gemerkte Zeit könntest Du dann in jedem Request abfragen, und bei Bedarf die Sitzung von Hand beenden.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

16.835 Beiträge seit 2008
vor 12 Jahren

Hi,

wenn Du MVC 😃 verwendest dann erstelle ein Nested-Singleton, in dem alle Benutzer gelistet sind, die derzeit Online sind (bzw. innerhalb des Timeouts).
Anschließend lasse alle Nicht-Ajax-Controller von einem Basis-Controller ableiten, der eine OnActionExecuting beinhaltet, in der die Timeout-Zeit zurück gesetzt wird.
Die Ajax-Aufrufe behindern damit das eigene Timeout nicht und Du kannst den Nutzer nach der verstrichenen Zeit ausloggen.

Das hat auch den Vorteil, dass Du auf gewisse Art und Weise tracken kannst, was der Benutzer so tut, und wann er das letzte mal eine Aktion ausgeführt hat (Achtung Datenschutz!). Dies bildet auch eine gute Basis für weiteres Applikation-Tracking, was natürlich nur positiv genutzt werden sollte (wie es derzeit einige Versicherer machen: Live-Support zB)

Grüße

3.170 Beiträge seit 2006
vor 12 Jahren

Hallo Abt,

vom Prinzip her also ähnlich 😃 , wobei

Anschließend lasse alle Nicht-Ajax-Controller von einem Basis-Controller ableiten bei WebForms meist nicht so sauber getrennt ist (obwohl durchaus möglich, aber aufwändig).

in der die Timeout-Zeit zurück gesetzt wird. Wofür ein eigenes Management des Timeouts ebenfalls unabdingbar ist, denn auf die Properties des FormsAuthenticationTicket besteht nach dessen Erstellung nur lesender Zugriff. Zurücksetzen kann man also höchstens einen Wert, den man selkbst verwalten muss.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

16.835 Beiträge seit 2008
vor 12 Jahren

Hi,

Ich habe angenommen er verwendet bereits MVC, aufgrund der Login- und Home-Url in der XML Definition; und hat so ein leichtes bzgl. den Controllern.
War aber ins blaue geraten =)

Da der Code für das Ticket sealed ist, ist ein einfaches Ändern so leider nicht möglich; weshalb ich an der Stelle auch keine andere Möglichkeit als ein eigenes Management bzgl. des Tickets sehe - aber ich lerne gerne, wenn es hier eine schönere Methode gibt.
Wäre für mich nämlich auch brauchbar 😉

Grüße

Y
Yheeky Themenstarter:in
200 Beiträge seit 2008
vor 12 Jahren

Hi,

@MarsStein: danke erstmal für deinen Post und die Idee 😃 ich habe sowas sogar schonmal in einem anderen Projekt ähnlich gelöst, aber in dem Fall habe ich den Wald vor lauter Bäumen nicht gesehen 😉

@Abt: Ich verwende in der Tat das MVC 😉
Ich habe meine Controller so aufgebaut, dass ich die normalen Aufrufe implementiert habe (Index, Edit, Details, etc...) und eben die WebMethoden mit [HttpPost] Attribut. Du sprichst von einem Aufbau nach dem Nested-Singleton und einem separataten Ajax-Controller. Ich denke ich weiss, was du meinst, aber um sicher zu gehen schreibe ich nochmal auf, wie ich es verstanden habe 😉

Die gewöhnlichen Aufrufe (Index, Edit, Details) der einzelnen Seiten gehen über die herkömmlichen Controller (Home, Benutzer, Nachrichten bzw. Home/Index, Benutzer/Index, Nachrichten/Index etc.). Dann erstelle ich einen Controller, der NUR mit Ajax-Aufrufen umgeht. Dieser beinhaltet keine OnActionExecution Methode, weil der Timeout nicht neu gesetzt werden muss, richtig?

Nun ist es ja aber so, dass das FormsAuthenticationTicket nur zum Lesen freigegeben ist...wie muss ich das denn dann erweitern? Neu instanziieren und zuweisen bzw. überschreiben?

Danke schonmal an euch!

Gruß Yheeky

3.170 Beiträge seit 2006
vor 12 Jahren

Hallo,

Nun ist es ja aber so, dass das FormsAuthenticationTicket nur zum Lesen freigegeben ist...wie muss ich das denn dann erweitern? Neu instanziieren und zuweisen bzw. überschreiben?

Naja, Dein Problem besteht ja darin, dass dsa Ticket den Timeout nicht auslöst, also kannst Du IMO das Ticket vergessen und wenn Du anhand einer von Dir selbst verwalteten Zeitmarke feststellst, dass ein Timeout vorliegt, meldest Du den Benutzer einfach selbst ab.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

16.835 Beiträge seit 2008
vor 12 Jahren

Dann erstelle ich einen Controller, der NUR mit Ajax-Aufrufen umgeht. Dieser beinhaltet keine OnActionExecution Methode, weil der Timeout nicht neu gesetzt werden mussy

Naja, jeder Controller hat diese Methode - sie gehört zur Controller-Klasse 😉
Du machst nun einfach einen eigenen Controller (MyController), der vom Controller ableitet. Dein eigener Controller hat nun die OnActionExecuted-Methode in der Du Deine Authverwaltung durchführen kannst bzw. das Timeout steuern.

Alle Controller, die die Zeit für das Timeout neu setzen sollen, leiten nun von diesem eigenen Controller ab.
Die Ajax-Controller nicht - die erben weiterhin einfach nur von Controller.

Y
Yheeky Themenstarter:in
200 Beiträge seit 2008
vor 12 Jahren

Ok, habe ich jetzt soweit eingebaut 👍 8)
Mein Problem ist jetzt nur noch: wenn ein Benutzer die Seite verlässt, ohne, dass er auf "Ausloggen" geht, bin ich ja immer noch auf den Session/FormsAuthentication Timeout angewiesen, oder? Oder gibt´s ne andere Möglichkeit beim Schließen des Browsers noch eine letzte Aktion auszuführen und den Benutzer in der Datenbank auf Offline zu setzen?
Eine alternative Möglichkeit wäre ein DB-Job, der regelmäßig ausgeführt wird, aber das kostet Performance und das möchte ich vermeiden 😉

Gruß Yheeky

16.835 Beiträge seit 2008
vor 12 Jahren

Hi,

nein, es gibt keine zuverlässige Rückmeldung, ob ein Anwender die Seite verlassen hat, sie noch offen hat oder den Browser geschlossen hat.

Ganz primitiv kannst Du im Singleton einfach ein Dictionary<string,DateTime> verwenden, in das, Du bei jeder Anfrage den User mit der aktuellen Zeit hinzufügst / aktualisierst.
Ist die letzte Anfrage länger als 30 Minuten ( Dein Timeout ) her, dann lässt Du das OnActionExecuted wissen, und leitest ihn auf eine Loginseite weiter.

(Ungetesteter Codeschnipsen)


protected override void OnActionExecuted(FilterExecutedContext filterContext)
    {
        if( ! MyUserOnlineNestedSingleton.AddUser(this.User.Identity.Name))
        {
              FormsAuthentication.SignOut();
              Session.Abandon();

              this.ViewData["ErrorMessage"] = "Du wurdest aufgrund längerer Inaktivität automatisch ausgeloggt";

              RedirectToAction("Login", "Account");
         }                     

      base.OnActionExecuted(filterContext);
    }

// MyUserOnlineNestedSingleton.AddUser Content ( string username )
{
    if(!this.UserOnlineDictionary.ContainsKey(username))
    {
         this.UserOnlineDictionary.Add(username, DateTime.Now);
    }
    else if(DateTime.Now.Substract(this.UserOnlineDictionary[username]).TotalMinutes >30)
     {
          this.UserOnlineDictionary.Remove(username);
          return false; // Logout!
     }
     else
     {
           this.UserOnlineDictionary[username] = DateTime.Now;
      }
         
       return true;
   }


Y
Yheeky Themenstarter:in
200 Beiträge seit 2008
vor 12 Jahren

Hi,

ich verstehe aber nicht, wie man das Problem mit dem Schließen des Browsers lösen könnte 🙁
Oder meintest du, dass bei OnActionExecuting alle eingeloggten Benutzer überprüft werden, ob sie beispielsweise in den letzten 30 Minuten eine Aktion hatten? Dies würde ja nur funktionieren, wenn sich immer ein Benutzer eingeloggt hat, ansonsten funktioniert das ja auch nicht.

3.170 Beiträge seit 2006
vor 12 Jahren

Hallo,

wenn der Benutzer den Browser schliesst, kannst Du das wie Abt schon schrieb nicht zuverlässig abfangen.
Bei den meisten Webseiten, die einen Onlinestatus anzeigen, erscheinen die Benutzer in so einem Fall dann noch eine Weile als "online" - z.B. auch hier auf mycsharp.de.

Wenn Du dafür ein Flag in der DB hast, wird es natürlich problematisch, da es sich semantisch eigentlich um Laufzeitdaten handelt. Die einzige Möglichkeit, die ich sehe, ist, an der Stelle, an der der Status angezeigt wird, eine Überprüfung zu machen, ob der Benutzer tatsächlich noch da ist - dann wird er zwar auch bis Ablauf des Timeouts als "online" angezeigt, aber Du sparst Dir den DB-Job. Denn solange keiner auf die Anzeige zugreift, braucht sie nich aktualisiert zu werden, und wenn jemand zugreift, machst Du dann die Überprüfung...

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

Y
Yheeky Themenstarter:in
200 Beiträge seit 2008
vor 12 Jahren

Ich möchte nicht nur eine Art Onlineliste realisieren sondern ich möchte auch implementieren, dass ein Benutzer per E-Mail über neue Nachrichten informiert wird, wenn er nich mehr eingeloggt ist. Dabei könnte ich beim Absenden einer Nachricht prüfen lassen, wie lange die letzte Aktion des Empfängers schon her ist und demnach entscheiden, ob er Benutzer z.B. per E-Mail benachrichtigt wird, da er nicht als "Online" angenommen wird oder er direkt über die Webseite informiert wird.
Alles nicht so einfach! 😉

16.835 Beiträge seit 2008
vor 12 Jahren

Alles zentral über den Singleton möglich.
Ich halte nicht viel davon den aktuellen Status eines Benutzers in der Datenbank zu halten. Von mir aus das Datum des letzten Logins; aber nicht, ob in User Online ist oder nicht.
Du kannst ja beim Singleton ein Get hinzufügen, der alle Benutzer in der aktuellen Benutzerliste ausloggt, die länger als die letzten 30 Minuten nicht aktiv waren.
So bekommst Du auch gleichzeitige eine Liste der Anwender, die derzeit Online sind.
=> Mehrere Fliegen mit einer Klatsche.