ActiveSupport::CurrentAttributes und passen nicht zu der Authentifizierung, die Rails 8 generiert.
Es stellt sich heraus, dass es selbst zu bauen rund 30 Zeilen braucht. Das liegt daran, dass der generierte Code klein genug ist, um ihn zu lesen, und so strukturiert ist, dass er sich erweitern lässt.
Was dieses Tutorial abdeckt
Current.userüberschreiben, sodass Impersonation durch den vorhandenen Code fließt.true_userfür Autorisierung und Anzeige verfügbar halten.- Die Reihenfolge in
resume_session, die Sie andernfalls aussperrt.
Voraussetzungen
Wie der generierte Code funktioniert
Zwei Dateien sind entscheidend.app/models/current.rb hält den Zustand pro Request. Sie speichert die Session und delegiert user an sie:
app/controllers/concerns/authentication.rb weist diese Session zu. require_authentication ruft resume_session auf, das den Session-Datensatz anhand eines signierten Cookies findet. Liefert es nichts zurück, wird der Request zur Anmeldeseite geschickt.
Das Cookie ist signiert, nicht verschlüsselt: Ein Nutzer kann seine Session-ID lesen, aber nicht ändern.
Current.user überschreiben
Alles in der Anwendung fragt bereitsCurrent.user, wer angemeldet ist. Statt das überall um Impersonation zu erweitern, fangen Sie es an einer einzigen Stelle ab.
Ersetzen Sie die Delegation durch eine explizite Methode:
Current.user liefert jetzt den impersonierten Nutzer, wenn einer gesetzt ist, und sonst den angemeldeten Nutzer. Gibt es keinen von beiden, liefert es nil, und der Request gilt als nicht authentifiziert, was genau das ist, was der Rest des Concerns erwartet.
true_user ist nicht optional. Sie brauchen ihn für das Banner, das einem Administrator zeigt, als wen er sich gerade ausgibt. Außerdem brauchen Sie ihn für die Autorisierung.
Die Impersonation-Methoden hinzufügen
Inapp/controllers/concerns/authentication.rb:
Current wird nach jedem Request geleert, daher muss der impersonierte Nutzer bei jedem Request erneut ermittelt werden. Die Cookie-Session trägt die ID zwischen den Requests weiter. Die Guard Clause in find_impersonated_user erspart eine Abfrage für einen Nutzer, den es gar nicht geben kann.
Beachten Sie, was bewusst fehlt: Es wird kein Session-Datensatz erstellt. Impersonation ist keine Anmeldung. Würden Sie einen erstellen, sähe der Kunde eine zweite aktive Session auf seinem Konto, was beunruhigend und falsch wäre.
In resume_session einbinden
stop_impersonating zu terminate_session hinzu. Ohne das setzt sich nach dem Abmelden und erneuten Anmelden die Impersonation der zuvor impersonierten Person fort. Die ID steht noch in der Cookie-Session.
Den Controller hinzufügen
Im Layout anzeigen
Impersonierte Aktivität zuordnen
Impersonation reißt eine Lücke in Ihre Telemetrie. Jeder Trace verzeichnet den impersonierten Nutzer, sodass sich Support-Aktivität nicht von der Aktivität des Kunden selbst unterscheiden lässt. Schließen Sie diese Lücke, indem Sie Requests mit dem tatsächlichen Akteur taggen:Transkript
Aus dem Video transkribiert und leicht bearbeitet. Die automatischen Untertitel haben mehrere Produkt- und API-Namen falsch verstanden, diese wurden korrigiert.Transkript lesen
Transkript lesen
0:02 Hey Leute, in dieser Folge fügen wir dem Rails-8-Authentifizierungsgenerator Impersonation hinzu. Das ist eigentlich viel einfacher, als man erwarten würde. Also, legen wir los. Ich führe
rails generate authentication aus, um die User- und Session-Modelle sowie alle Controller und das Authentifizierungsmodul zu erzeugen, das den größten Teil der Arbeit übernimmt. Und wir führen rails db:migrate aus, um diese Datenbanktabellen zu erstellen.0:31 Dann starte ich die Rails-Konsole, und wir legen zwei Nutzer an. Wir melden uns als einer an und impersonieren den anderen. Also User.create!, E-Mail-Adresse chris@gorails.com und Passwort ist “password”. Dann machen wir dasselbe noch einmal, aber mit einem anderen Nutzer mit einer anderen E-Mail-Adresse.0:51 Ich gebe ihm irgendein Passwort, und das sollte reichen. Schauen wir uns also kurz die Authentifizierung selbst an. Wir gehen in application.html.erb und schreiben: wenn authenticated?, dann können wir current_user.email_address ausgeben. Das ist es, was wir für den aktuell angemeldeten Nutzer sehen, und das soll sich ändern, wenn wir einen anderen Nutzer impersonieren. Außerdem gibt es einen Button zum Abmelden, der zum Session-Pfad mit der Methode delete geht, um uns abzumelden.1:34 Okay, das sieht gut aus. Wir sollten unseren Rails-Server starten und das im Browser öffnen können. Da ist mein vorheriger Test, den ich eingerichtet hatte. Und hier ist sessions/new, wohin wir gebracht werden. Wenn wir zur Root-URL gehen, werden wir hierher umgeleitet.1:52 Ich habe bereits eine Haupt-Root-URL für uns eingerichtet. Sie schickt uns also zur Authentifizierung, was standardmäßig passiert. Jede Seite ist authentifiziert, und man muss explizit sagen, dass eine Seite keine Authentifizierung braucht. Hier können wir uns als chris@gorails.com anmelden, und da sind wir. chris@gorails.com, ich kann mich abmelden und wieder anmelden.2:16 Machen wir das noch einmal, um sicherzugehen, dass alles funktioniert. Wie machen wir also Authentifizierung mit Impersonation? Schauen wir uns kurz die Current-Klasse an. Sie ist dafür zuständig, die aktuelle Session und den aktuellen Nutzer für den Request zu speichern. Das wird nach Ende des Requests geleert, und wir weisen einfach die Session zu und holen den Nutzer über diese Session.2:44 Das Authentifizierungsmodul ist dafür zuständig, diese Session zuzuweisen. Bei require_authentication wird also diese Methode aufgerufen, die dann die aktuelle Session fortsetzt oder Sie zur Anmeldung auffordert. Das Fortsetzen ist so einfach wie das Suchen der Session anhand des Cookies, wodurch der Session-Datensatz in der Datenbank anhand der im Cookie gesetzten ID nachgeschlagen wird. Diese sind signiert, damit sie nicht manipuliert werden können, aber sie sind für Nutzer lesbar, weil das keine Rolle spielt, denn sie können sie nicht missbrauchen. Für Impersonation müssen wir also im Grunde etwas hinzufügen, das das abfängt.3:29 Und wenn wir den aktuellen Nutzer holen, bekommen wir eigentlich den Nutzer, den wir impersonieren, nicht den, als den wir angemeldet sind. Und ich möchte für sie auch nicht unbedingt eine Session erstellen, weil wir nicht wirklich als sie angemeldet sind. Wir versuchen nur, zum Beispiel Kundensupport zu leisten. Überlegen wir also, wie das funktionieren soll. In current.rb ist genau diese delegate :user-Zeile das, was wir abfangen müssen.3:54 Wenn wir es so betrachten, könnten wir ein Attribut für den impersonierten Nutzer haben, das wir zuweisen, und dann eine Methode für user, die entweder den impersonierten Nutzer oder den Nutzer der Session zurückgibt. Und gäbe es weder eine Session noch einen impersonierten Nutzer, würde das nil zurückgeben, und wir wären überhaupt nicht authentifiziert. Das würde also gut funktionieren und dafür sorgen, dass es auch mit dem Authentifizierungsmodul funktioniert. Wir brauchen diese eine Zeile zur Delegation von user also nicht mehr, weil wir das im Grunde hier ohnehin überschreiben müssen. Jetzt könnten wir natürlich auch eine Methode wie true_user hinzufügen und sagen, das ist der Nutzer der Session, und den impersonierten Nutzer immer überspringen können, weil wir wollen, dass der impersonierte Nutzer wie unser aktueller Nutzer wirkt, wir aber vielleicht ein Banner anzeigen und den ursprünglichen Nutzer zeigen wollen, wofür man hier auch einen kleinen Helfer hinzufügen könnte.5:01 Das ist optional, nicht unbedingt notwendig, aber gehen wir in unser application.html.erb und fügen etwas hinzu, das sagt: Wenn impersoniert wird, wollen wir einen Button haben, um die Impersonation zu beenden. Wir brauchen dafür auch einen Impersonate-Pfad mit der Methode delete, um die Impersonation zu beenden, und dann können wir einen Button haben, um einen Nutzer zu impersonieren, der zum Impersonate-Pfad geht, wobei wir eine ID für einen Nutzer übergeben. Das müsste in Ihrem Admin-Bereich wahrscheinlich dynamisch sein, und wir machen das zu einem POST-Request, weil wir dort die Impersonation-Session erstellen wollen. Wir müssen also eine Handvoll Methoden und einen Controller implementieren, um das umzusetzen. Gehen wir also zu unseren Routes.5:58 Wir fügen eine Resource für impersonate hinzu. Wir können app/controllers/impersonates_controller.rb bearbeiten und diesen ImpersonatesController erstellen, der von ApplicationController erbt. Eine Sache, die wir tun wollen, ist, ein require_admin hinzuzufügen. Wir haben kein Admin-Flag an unseren Nutzern, aber das ist so etwas wie ein Beispiel, das man umsetzen könnte.6:27 Man könnte also require_admin sagen, und das würde dann prüfen, ob current_user Admin ist, und wenn der aktuelle Nutzer kein Admin ist, zur Root-URL umleiten, oder was auch immer man möchte. Vielleicht möchten Sie dort eine Meldung anzeigen, aber hier müssen wir unsere zwei Aktionen erstellen. Eine erstellt die Impersonation-Session, die andere löscht sie. Das wird so etwas wie stop_impersonating, und wir leiten Sie zur Root-URL um. Für create wird das ziemlich einfach, ähnlich: Wir sagen impersonate_user(User.find(params[:id])).7:13 Sie übergeben also eine ID in der URL. Wir impersonieren dann diesen Nutzer und leiten Sie zur Startseite um, sodass Sie die Startseite jetzt als dieser Nutzer sehen. Jetzt müssen wir in das Authentifizierungsmodul gehen und diese Methoden hinzufügen. Wenn wir uns diese anderen Methoden ansehen, können wir etwas Ähnliches bauen. Wir brauchen also die impersonating?-Methode, die true oder false zurückgibt.7:45 Wir brauchen ein impersonate_user, das das einrichtet. Wir brauchen ein stop_impersonating. Das braucht dafür nichts weiter. Aber wir brauchen auch ein find_impersonated_user. Das ist die Methode, die wir nach der Impersonation verwenden, wenn Sie zur nächsten Seite weitergeleitet werden.8:07 Dieser Request muss den impersonierten Nutzer finden, genau wie er Ihre aktuelle Session anhand des Cookies findet. Fangen wir also einfach an. Wir sagen: Current.impersonated_user ist das vorhanden? Ja, Sie impersonieren.8:28 Nein, tun Sie nicht. Wenn wir dann einen Nutzer impersonieren wollen, weisen wir diese Variable zu, impersonated_user gleich diesem Nutzer. Und wir müssen auch etwas setzen, damit die impersonated_user-ID für den nächsten Request erhalten bleibt. Dafür nutzen wir die Session. Und beim nächsten Request können wir prüfen, ob es eine ID in der Session für die impersonierte Nutzer-ID gibt.8:58 Falls ja, können wir find_by mit der ID sagen und versuchen, diesen Nutzer zu bekommen. So, wie ich es hier einrichte, prüfen wir, ob diese ID vorhanden ist, damit wir Active Record ganz überspringen können, wenn keine ID vorhanden ist. Wir müssen die Datenbank nicht nach einem Nutzer abfragen, dessen ID null ist, weil wir dafür sowieso keinen Nutzer zurückbekommen. Es macht also keinen Sinn, diese Abfrage überhaupt zu stellen. Wir geben also entweder den Nutzer oder nil zurück, und dann ist stop_impersonating ziemlich einfach.9:29 Der impersonierte Nutzer sollte jetzt nil sein, und wir wollen ihn aus der Session löschen. Ziemlich einfach. Das sind also unsere wichtigsten Impersonation-Bausteine. Ich füge hier noch eine Leerzeile hinzu. Mir ist aufgefallen, dass das Authentifizierungsmodul so bereits aufgebaut ist.9:51 Etwas Struktur mit zusätzlichen Leerzeilen. Wir müssen das jetzt also in den eigentlichen Authentifizierungsprozess einbinden. Und der Hauptauthentifizierungsprozess ist diese Methode require_authentication: entweder eine Session fortsetzen oder eine neue starten. Und Sie schicken den Nutzer zur Anmeldeseite. Also ist resume_session die Stelle, die wir tatsächlich ändern müssen.10:10 Wir setzen also Current.impersonated_user gleich find_impersonated_user. Und dann setzen wir die Session. Es ist wichtig, dass diese beiden in genau dieser Reihenfolge stehen, weil der Rückgabewert von resume_session bestimmt, ob wir Sie zur Anmeldung umleiten. Würden Sie das danach setzen und würden nicht impersonieren, würden wir Sie immer wieder zur Authentifizierung schicken, weil impersonated_user nil wäre. Diese Methode würde hier nil zurückgeben, was falsy ist, was dann diese Seite auslöst.10:48 Und es würde so aussehen, als könnten Sie sich nie anmelden. Es ist also wichtig, das richtig zu machen, damit der Rückgabewert von resume_session entsprechend gesetzt ist. Und wenn alles gut läuft, brauchen wir eigentlich nur noch eine Helper-Methode für impersonating?, damit wir sie in unseren Views verwenden können, um diesen Button anzuzeigen. Dann möchten Sie wahrscheinlich auch prüfen, ob der Nutzer Admin ist oder Ähnliches. Und mir ist gerade aufgefallen, dass wir sicherstellen müssen, dass wir überall, wo wir auf die Session zugreifen, den gleichen Schlüssel für die Nutzer-ID verwenden.11:27 Wir wollen sicherstellen, dass das konsistent ist. Sonst passieren merkwürdige Dinge. Außerdem wollen wir, wenn die Session beendet wird, sicherstellen, dass wir auch die Impersonation beenden, denn wenn Sie sich abmelden und wieder anmelden, wollen Sie nicht als Ihr eigenes Konto angemeldet sein, aber automatisch die Impersonation der zuletzt impersonierten Person fortsetzen. Das wäre merkwürdig. Wir wollen also sicherstellen, dass wir beim Abmelden auch die Impersonation beenden.11:56 Probieren wir das also aus. Gehen wir in den Browser. Wir sind auf sessions/new. Wir können uns anmelden, chris@gorails.com. Das meldet uns immer noch an, was gut ist.12:05 Wir haben nichts kaputt gemacht. Wir impersonieren nicht, also erscheint der Impersonate-Button. Wir können darauf klicken. Das stellt einen POST-Request an den Impersonate-Controller, der create trifft. Er wird die ID 2 haben.12:20 Wir haben sie dafür in der URL. Sie könnten das auch unter users/:id/impersonate einrichten, wenn Sie möchten. Ich habe sie einfach in einem Controller belassen und übergebe die ID. Beides funktioniert, aber wir können impersonieren. Und jetzt ist Current.user.email_address bob@bob.bob.12:37 Aber wir sind immer noch als chris@gorails.com angemeldet. Also wissen wir jetzt, dass wir impersonieren, weil wir stop_impersonating sehen. Wenn wir die Rails-Konsole öffnen wollen, vergrößere ich mal die Schrift. Wir können Session.count sagen. Und wir sehen, dass es nur eine Session für mich gibt, chris@gorails.com.13:02 Wenn wir Session.first sagen und den Nutzer holen, ist es Nutzer Nummer eins, den ich hier nicht zeige. Er ist gefiltert, aber die E-Mail-Adresse ist chris@gorails.com. Es ist also nur ich angemeldet, und meine Impersonation wird nicht als Session gespeichert, was merkwürdig wäre, wenn ein Kunde sähe, dass jemand anderes in seinem Konto angemeldet ist. Wir wollen also keine Session dafür erstellen. Aber Sie könnten es, wenn Sie wollten.13:31 Und das auch am Datensatz vermerken. Sie könnten dem Session-Modell ein zusätzliches Feld hinzufügen, um diese Impersonation-Sessions auch für die Historie festzuhalten. Es gibt vieles, das Sie hier ausbauen können. Wenn wir also die Impersonation beenden, wird Current.impersonated_user auf nil gesetzt, aber auch die impersonated_user_id aus der Session entfernt. Und wir sind wieder bei chris@gorails.com.13:58 Und ich kann jederzeit zu bob@bob.bob wechseln. Und dafür mussten wir eigentlich nur resume_session und terminate_session am Ende ändern, um unsere Impersonation-Änderungen einzubeziehen. Und in unserer current.rb verwenden wir statt der Delegation den impersonierten Nutzer. Und natürlich haben wir immer auch Zugriff auf true_user. Wenn wir also in unser application.html.erb gehen, könnten wir sagen: bei Impersonation zeigen Sie wahrscheinlich die E-Mail-Adresse des aktuellen Nutzers an.14:39 Aber vielleicht sagen Sie bei Impersonation Current.true_user.email_address, wenn Sie den echten Nutzer zeigen wollen, den Sie haben. Jetzt sehen wir chris@gorails.com an dieser Stelle. Sie haben also Zugriff auf beide Arten von Nutzern, genau wie bei der Pretender-Gem für Devise oder jeder anderen Impersonation-Bibliothek, die es für bestehende Authentifizierung gibt. Aber die funktionieren nicht mit CurrentAttributes. Und deshalb ist es schön, dass wir das mit nur ein wenig Code selbst bauen können.15:18 Es steckt nicht besonders viel dahinter. Und es ist einfach genug, um nachzuvollziehen, wenn man sich das ansieht und herausfindet, wie es funktioniert. Ich bin also wirklich zufrieden damit, wie das geworden ist. Die Rails-8-Authentifizierung ist sehr, sehr flexibel. Sie können damit alle möglichen Dinge tun und sie ganz nach Ihren Bedürfnissen anpassen, egal ob für Impersonation, OAuth, was auch immer.15:39 Da gibt es jede Menge Flexibilität. Und mit dem Authentifizierungsmodul lässt sich das sehr, sehr leicht lesen. Das war es also für diese Folge. Ich hoffe, sie hat Ihnen gefallen. Und wenn Sie mehr von so etwas sehen möchten, lassen Sie es uns unten in den Kommentaren wissen.15:56 Und daraus schöpfen wir dann die Inspiration für unsere nächsten Lektionen. Das war’s. Bis dann. Und wir sprechen uns bald wieder. Tschüss.