[RUBY] Ich habe die Ausführung des Rails-Tutorials geändert: Hinweise zum Rails-Tutorial - Kapitel 9

Derzeit Selbststudium, die erste Runde des Rails-Tutorials Wenn Sie in der zweiten Runde herausfordern möchten Ich möchte Test Driven Development (TDD) ausprobieren. Ich beschloss, die Anforderungen aufzulisten, die im Thema jedes Kapitels implementiert werden sollten

Als Aufgabenablauf, den ich mir in der zweiten Woche vorgestellt habe Anforderungsdefinition (in der ersten Woche hier lassen)> Test schreiben (in der zweiten Woche hier verbinden)> Implementierung (Wenn es weit von der Realität entfernt ist, weisen Sie bitte frühzeitig darauf hin.)

Ziel: Um länger und sicherer eingeloggt zu bleiben

Kenntnis dieses Kapitels

Nicht im Zusammenhang mit dem Inhalt dieses Kapitels Erstellen Sie eine Vorlage für die Ausgabe, um die Qualität des Lernens zu verbessern Überwiegend die Menge erhöht (iuput: output = ca. 2: 8) Die Fortschrittsgeschwindigkeit ist erheblich gesunken, aber ich habe das Gefühl, dass sich die Qualität des Lernens verbessert hat.

Es ist schwierig, Zeiteffizienz und Lerneffekt in Einklang zu bringen

Markdown-Editor für Mac eingeführt, um die Zeiteffizienz so weit wie möglich zu verbessern

Bedarf

Generieren Sie ein Cookie, das auch dann erhalten bleibt, wenn Sie den Browser beim Anmelden schließen

Bleiben Sie als Benutzer in der Datenbank angemeldet, die dem Cookie entspricht, wenn der Browser geschlossen wird (current_user = nil).

Erfolgreiche vollständige Abmeldung

Hinweis: Kapitel 9 Weiterentwicklung des Anmeldemechanismus

9.1 Erinnere dich an mich Funktion

9.1.1 Speichertoken und Verschlüsselung

Risiko der Cookie-Exposition und was zu tun ist

Extrahieren Sie Cookies mit einer speziellen Software namens Packet Sniffer direkt aus Netzwerkpaketen, die ein schlecht verwaltetes Netzwerk durchlaufen > Gegenmaßnahme: SSL (unterstützt)

Extrahieren Sie das Speichertoken aus der Datenbank > Problemumgehung: In der Datenbank gespeicherte Hash-Token

Stehlen Sie den Zugriff, indem Sie den Computer oder das Smartphone, auf dem der Benutzer angemeldet ist, direkt bedienen > Problemumgehung: Ändern Sie das Token, wenn Sie abgemeldet sind (Rails Tutorial 6. Ausgabe)

Attribut Remember_digest zum Benutzermodell hinzugefügt

$ rails generate migration add_remember_digest_to_users remember_digest:string
$ rails db:migrate

Ich möchte in der Lage sein, das Attribut memor_token im Benutzermodell zu verarbeiten Dieses Attribut wird nicht in der Datenbank gespeichert > Benutze attr_accessor

Warum Sie attr_accessor benötigen und warum nicht (Dank an Rails): Rails Tutorial Memorandum-Kapitel 9

Definieren Sie "User.new_token" im Benutzermodell (Rückgabespeichertoken) "SecureRandom.urlsafe_base64" eignet sich zum Generieren von Speicher-Token

$ rails console
>> SecureRandom.urlsafe_base64
=> "brl_446-8bqHv87AQzUj_Q"
  def User.new_token
    SecureRandom.urlsafe_base64
  end

Hash das Speicher-Token und speichere es in der DB Definieren Sie "Merken" im Benutzermodell Sie können "User.digest (string)" zum Hashing verwenden

  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end

self. ist erforderlich

Ich möchte mich auch daran erinnern lassen, ausgeführt worden zu sein, wenn ich mich anmelde Es ist danach

9.1.2 Angemeldet bleiben

Ich möchte die Benutzer-ID im Browser speichern Signiert mit signierten Cookies

cookies.signed[:user_id] = user.id

Entschlüsselt mit cookies.signed [: user_id]

Cookie-Persistenz ist mit "permanent" möglich In der Methodenkette

cookies.permanent.signed[:user_id] = user.id

Definieren Sie "authentifiziert?" Im Benutzermodell Das Verhalten ist das gleiche wie bei der Verwendung eines Passworts, aber Sie können es sich vorstellen, aber das teilweise Verständnis von BCrypt ... ist unzureichend.

#Gibt true zurück, wenn das übergebene Token mit dem Digest übereinstimmt
  def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end

Das Argument memor_token ist eine lokale Variable

Ändern Sie das Verhalten beim Anmelden Remember (user) hinzugefügt Dies unterscheidet sich von der Remember-Methode des Benutzermodells (unter Berücksichtigung eines Arguments).

app/controllers/sessions_controller.rb


  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      remember user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

Remember (Benutzer) ist als Helfer definiert Rufen Sie an, wenn Sie sich anmelden Generiert ein Speicher-Token, speichert es in einem Cookie und speichert ein Digest-Token in einer Datenbank. (Die Bedeutung der Verwendung verschiedener Helfer und die Priorität der Methode mit demselben Namen sind nicht vollständig verstanden.)

app/helpers/sessions_helper.rb


  #Machen Sie die Sitzung eines Benutzers dauerhaft
  def remember(user)
    user.remember#Benutzermodellmethode (Generierung des Speicher-Tokens, DB-Speicherung des Digest-Tokens)
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

current_user ist nicht nur eine Sitzung Von Cookies gepflegt werden

app/helpers/sessions_helper.rb


  #Gibt den Benutzer zurück, der dem Speicher-Token-Cookie entspricht
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end

Wiederholung kann mit (user_id = session [: user_id]) weggelassen werden = ist keine logische Operation, Zuweisung

Zu diesem Zeitpunkt ist "Schienentest" (ROT)

FAIL["test_login_with_valid_information_followed_by_logout", #<Minitest::Reporters::Suite:0x0000556848d6b040 @name="UsersLoginTest">, 1.7997455329999923]
 test_login_with_valid_information_followed_by_logout#UsersLoginTest (1.80s)
        Expected at least 1 element matching "a[href="/login"]", found 0..
        Expected 0 to be >= 1.
        test/integration/users_login_test.rb:36:in `block in <class:UsersLoginTest>'

Ich sehe den Link zum Login nicht Mit anderen Worten, Sie haben sich anscheinend nicht abgemeldet (Es wird erwartet, dass current_user per Cookie beibehalten wird.)

9.1.3 Vergiss den Benutzer

DB memor_digest beim Abmelden Browser-Cookies löschen (Update mit Null)

Definieren Sie zunächst "Vergessen", um die DB zu betreiben

app/models/user.rb


 #Benutzeranmeldeinformationen verwerfen
  def forget
    update_attribute(:remember_digest, nil)
  end

Definieren Sie als Nächstes die Hilfsmethode "Vergiss (Benutzer)" Nil : memor_digest in der DB mit dem vorherigen user.forget Keine Browser-Cookies mit cookies.delete

/sample_app/app/helpers/sessions_helper.rb


 def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
  end

Nennen Sie diese Hilfsmethode "Vergiss (Benutzer)" mit derselben Hilfsmethode "log_out"

/sample_app/app/helpers/sessions_helper.rb


 def log_out
    forget(current_user)
    session.delete(:user_id)
    @current_user = nil
  end

Jetzt kann log_out sowohl die Sitzung als auch das Cookie leeren Vollständige Abmeldung möglich

Rails Test ist (GRÜN)

9.1.4 Zwei unauffällige Fehler

Beheben Sie Fehler, die bei Verwendung mehrerer Registerkarten und Browser auftreten

Wenn Sie mehrere Registerkarten öffnen, während Sie gleichzeitig angemeldet sind, Probleme, die dadurch verursacht wurden, dass Sie mehrmals auf Abmelden (logout_path löschen) zugreifen konnten

Da current_user = nil im ersten delete logout_path gesetzt ist Wenn Sie erneut "logout_path löschen" anfordern, ruft der Controller die Methode "log_out" erneut auf. Wenn ich versuche, "Vergiss (aktueller_Benutzer)" auszuführen, erhalte ich eine Fehlermeldung, weil "aktueller_Benutzer = nil"

NoMethodError: undefined method `forget' for nil:NilClass
app/helpers/sessions_helper.rb:36:in `forget'
app/helpers/sessions_helper.rb:24:in `log_out'
app/controllers/sessions_controller.rb:20:in `destroy'

Um dies in einem Test erkennen zu können Sehr einfach, wenn man bedenkt, dass Sie erneut "delete logout_path" anfordern sollten

test/integration/users_login_test.rb


require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest
  .
  .
  .
  test "login with valid information followed by logout" do
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert is_logged_in?
    assert_redirected_to @user
    follow_redirect!
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
    delete logout_path
    assert_not is_logged_in?
    assert_redirected_to root_url
    #↓ Hier wieder abmelden löschen_Pfad anfordern
    delete logout_path
    follow_redirect!
    assert_select "a[href=?]", login_path
    assert_select "a[href=?]", logout_path,      count: 0
    assert_select "a[href=?]", user_path(@user), count: 0
  end
end

Wenn Sie den Test ausführen (ROT) Stellen Sie sicher, dass der Fehler erfolgreich reproduziert werden kann

Wenn nicht angemeldet Für die Anforderung "logout_path löschen" Die Methode "log_out" sollte nicht aufgerufen werden

Sie können die Methode "logged_in" verwenden, um Ihre Anmeldung zu bestätigen

  def logged_in?
    !current_user.nil?
  end

app/controllers/sessions_controller.rb


  def destroy
    log_out if logged_in?
    redirect_to root_url
  end

Wird log_out if logged_in? Ist eine modische Schreibweise Kann durch "if ... end" ersetzt werden

[Ruby] Missbrauch ist strengstens verboten! ?? Fälle, in denen das Schreiben mit einem Postfix das Lesen erschwert Diese Person wurde unter dem Gesichtspunkt der Lesbarkeit auf diese Weise hingewiesen und möchte darauf verweisen.

In diesem Zustand ist "Schienentest" (GRÜN)

Nächster, A, B, zwei Browser sind geöffnet, melden Sie sich mit einem Browser B ab (DB memor_digest ist nil), Schließen Sie dann Browser A (die Sitzung [: user_id] von Browser A ist null) Probleme, die dadurch verursacht werden, dass nur Cookies im Browser A gespeichert bleiben

Wenn Sie Browser A in diesem Zustand erneut öffnen, bleibt das Cookie erhalten.

python


def current_user
  if (user_id = session[:user_id])
    @current_user ||= User.find_by(id: user_id)
  elsif (user_id = cookies.signed[:user_id]) #Werden Sie hier wahr
    user = User.find_by(id: user_id)
    if user && user.authenticated?(cookies[:remember_token])#Error
      log_in user
      @current_user = user
    end
  end
end

if user && user.authenticated? (Cookies [: Remember_token]) wird ausgeführt Weil memor_digest aufgrund des Verhaltens eines anderen Browsers auf nil gesetzt ist

python


  def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)#remember_digest = nil
  end

Gibt eine Ausnahme aufgrund von Inkonsistenzen mit dem Inhalt des Cookies zurück

BCrypt::Errors::InvalidHash: invalid hash

Um dies im Test erkennen zu können Die Situation von Browser A sollte mit dem Objekt des Benutzermodells reproduziert werden. Mit anderen Worten, bereiten Sie ein Modell mit memor_digest = nil vor

test/models/user_test.rb


require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "[email protected]",
                     password: "foobar", password_confirmation: "foobar")
  end
  .
  .
  .
  test "authenticated? should return false for a user with nil digest" do
    assert_not @user.authenticated?('')
  end
end

Der von der Setup-Methode erstellte @ user entspricht genau dem, sodass Sie diesen verwenden können Derzeit memor_digest = nil @ user.authenticated? ('') Gibt eine Ausnahme zurück und der Test ist (ROT)

Wenn memor_digest = nil Ich möchte, dass "@ user.authenticated? (") "" False "zurückgibt

  def authenticated?(remember_token)
    return false if remember_digest.nil?
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end

Sollte sein Bei Progate habe ich es oft am Ende der If-Anweisung verwendet, sodass ich es nicht bemerke. return beendet dort die Methode und gibt den Wert zurück Es wird also nicht weniger ausgeführt

Jetzt ist der Test (GRÜN)

9.2 Kontrollkästchen An mich erinnern

Kontrollkästchen können mit der Hilfsmethode eingefügt werden

html:/sample_app/app/views/sessions/new.html.erb


      <%= f.label :remember_me, class: "checkbox inline" do %>
        <%= f.check_box :remember_me %>
        <span>Remember me on this computer</span>
      <% end %>

CSS hinzufügen

app/assets/stylesheets/custom.scss


.
.
.
/* forms */
.
.
.
.checkbox {
  margin-top: -10px;
  margin-bottom: 10px;
  span {
    margin-left: 20px;
    font-weight: normal;
  }
}

#session_remember_me {
  width: auto;
  margin-left: 0;
}

params [: session] [: Remember_me] Wenn diese Option aktiviert ist, erhalten Sie den Wert "1". Wenn sie deaktiviert ist, erhalten Sie den Wert "0" "1" oder "0" anstelle von "1" oder "0"

Fügen Sie der Aktion "Erstellen" des Sitzungscontrollers Folgendes hinzu

if params[:session][:remember_me] == '1'
  remember(user)
else
  forget(user)
end

Das Obige kann wie folgt umgeschrieben werden (ternärer Operator)

params[:session][:remember_me] == '1' ? remember(user) : forget(user)

Abschluss des permanenten Anmeldemechanismus bisher

9.3 [Erinnere dich an mich] Test

9.3.1 Testen Sie das Feld Angemeldet bleiben

Definieren Sie Hilfsmethoden, mit denen sich Benutzer innerhalb des Tests anmelden können

Kann für Unit-Test bzw. Integrationstest verwendet werden Definieren Sie die Methode "log_in_as" in jeder der Klassen ActiveSupport :: TestCase und "ActionDispatch :: IntegrationTest"

Weil der Integrationstest "Sitzung" nicht direkt verarbeiten kann Verwenden Sie "post login_path" in der "log_in_as" -Methode für Integrationstests

test/test_helper.rb


ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
  fixtures :all

  #Gibt true zurück, wenn der Testbenutzer angemeldet ist
  def is_logged_in?
    !session[:user_id].nil?
  end

  #Melden Sie sich als Testbenutzer an
  def log_in_as(user)
    session[:user_id] = user.id
  end
end

class ActionDispatch::IntegrationTest

  #Melden Sie sich als Testbenutzer an
  def log_in_as(user, password: 'password', remember_me: '1')
    post login_path, params: { session: { email: user.email,
                                          password: password,
                                          remember_me: remember_me } }
  end
end

rails test(GREEN)

9.3.1 Übung:

Lassen Sie uns zwei Probleme haben

Als Voraussetzung ist der "@ user", der durch die "setup" -Methode im Integrationstest definiert wird Enthält nicht das Attribut Remember_token

Warum Sie attr_accessor benötigen und warum nicht (Dank an Rails): Rails Tutorial Memorandum-Kapitel 9

/sample_app/app/controllers/sessions_controller.rb


  def create
    @user = User.find_by(email: params[:session][:email].downcase)
    if @user&.authenticate(params[:session][:password])
      log_in @user
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
      redirect_to @user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

/sample_app/test/integration/users_login_test.rb


  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_equal cookies[:remember_token], assigns(:user).remember_token
  end

9.3.2 Test [Erinnere dich an mich]

Testen des Verhaltens von current_user

Notiert als assert_equal <expected>, <actual>

/sample_app/test/helpers/sessions_helper_test.rb


require 'test_helper'

class SessionsHelperTest < ActionView::TestCase

  def setup
    @user = users(:michael)
    remember(@user)
  end

  test "current_user returns right user when session is nil" do
    assert_equal @user, current_user
    assert is_logged_in?
  end

  test "current_user returns nil when remember digest is wrong" do
    @user.update_attribute(:remember_digest, User.digest(User.new_token))
    assert_nil current_user
  end
end

9.4 Schließlich

Wenn Sie Folgendes tun, ist vorübergehend kein Zugriff darauf möglich Die Wartungsseite kann angezeigt werden

$ heroku maintenance:on
$ git push heroku
$ heroku run rails db:migrate
$ heroku maintenance:off

Recommended Posts

Ich habe die Ausführung des Rails-Tutorials geändert: Hinweise zum Rails-Tutorial - Kapitel 9
11.1 AccountActivations-Ressource: Rails Tutorial Memorandum-Kapitel 11
Grep? Zusammengeschraubt?: Rails Tutorial Notes - Kapitel 8
Ich habe eine Antwortfunktion für die Rails Tutorial-Erweiterung (Teil 1) erstellt.
[Rails] Ich habe versucht, die Anwendung zu löschen
Ich habe eine Antwortfunktion für die Rails Tutorial-Erweiterung (Teil 5) erstellt:
Testen auf Fehlermeldungen: Rails Tutorial Notes-Kapitel 7
11.2 E-Mail zur Kontoaktivierung senden: Rails Tutorial Notes - Kapitel 11
Schienen Tutry
Schienen Tutorial
Schienen Tutry
Deshalb habe ich dieses Mal die Methode "Verknüpfen des Inhalts des Verzeichnisses" übernommen. Ich denke, es wird je nach Zeit und Fall richtig verwendet. Tutorial zu Linux, ln, Linux-Befehlsschienen
Schienen Tutry
Schienen Tutorial
Schienen Tutorial
Wo ich im heutigen "Rails Tutorial" (2020/10/08) stecken geblieben bin
[Rails] Wenn ich form_with benutze, friert der Bildschirm ein! ??
[Rails] Ich habe versucht, die Version von Rails von 5.0 auf 5.2 zu erhöhen
Ich habe versucht, die Sitzung in Rails zu organisieren
Wo ich im heutigen "Rails Tutorial" (2020/10/05) stecken geblieben bin
Code zum Verbinden von Rails 3 mit PostgreSQL 10
Wo ich im heutigen "Rails Tutorial" (2020/10/06) stecken geblieben bin
Wo ich im heutigen "Rails Tutorial" (2020/10/04) stecken geblieben bin
Ich habe versucht, Tomcat so einzustellen, dass das Servlet ausgeführt wird.
Wo ich im heutigen "Rails Tutorial" (2020/10/07) stecken geblieben bin