webentwicklung-frage-antwort-db.com.de

Wie implementiere ich ein Passwort zurücksetzen?

Ich arbeite an einer Anwendung in ASP.NET und habe mich gefragt, wie ich eine Password Reset-Funktion implementieren kann, wenn ich meine eigene Rolle erstellen möchte. 

Im Einzelnen habe ich folgende Fragen:

  • Wie lässt sich eine eindeutige ID erstellen, die schwer zu knacken ist? 
  • Sollte ein Timer daran angeschlossen sein? Wenn ja, wie lange sollte es dauern?
  • Soll ich die IP-Adresse aufzeichnen? Ist es überhaupt wichtig?
  • Welche Informationen sollte ich im Bildschirm "Passwort zurücksetzen" anfordern? Nur E-Mail-Adresse? Oder vielleicht eine E-Mail-Adresse und einige Informationen, die sie "kennen"? (Lieblingsteam, Welpenname usw.)

Gibt es andere Überlegungen, die ich beachten muss?

NB: Andere Fragen haben überarbeitet technische Umsetzung vollständig. Die akzeptierte Antwort beschönigt tatsächlich die blutigen Details. Ich hoffe, dass diese Frage und die folgenden Antworten in die blutigen Details einfließen werden, und ich hoffe, indem ich diese Frage viel enger formuliere, dass die Antworten weniger "Flaum" und mehr "Gore" sind.

Edit: Antworten auf die Frage, wie eine solche Tabelle in SQL Server oder in ASP.NET-MVC-Links zu einer Antwort modelliert und behandelt wird, würden sich freuen.

81
George Stocker

Viele gute Antworten hier, ich werde mich nicht darum kümmern, alles zu wiederholen ... 

Bis auf eine Ausgabe, die von fast jeder Antwort wiederholt wird, auch wenn sie falsch ist:

Leitlinien sind (realistisch) einzigartig und statistisch nicht zu erraten.

Dies trifft nicht zu, GUIDs sind sehr schwache Identifikatoren und solltenNICHTverwendet werden, um den Zugriff auf das Konto eines Benutzers zu ermöglichen.
Wenn Sie die Struktur untersuchen, erhalten Sie maximal 128 Bits ... was heutzutage nicht viel ist.
Davon ist die erste Hälfte typisch (für das erzeugende System) unveränderlich, und die Hälfte davon ist zeitabhängig (oder etwas Ähnliches).
Alles in allem ist es ein sehr schwacher und leicht brachial erzwungener Mechanismus. 

Verwenden Sie das also nicht!

Verwenden Sie stattdessen einfach einen kryptographisch starken Zufallszahlengenerator (System.Security.Cryptography.RNGCryptoServiceProvider), und erhalten Sie mindestens 256 Bit rohe Entropie.

Alles andere als die zahlreichen anderen Antworten.

66
AviD

EDIT 2012/05/22: Als Folgemaßnahme zu dieser beliebten Antwort verwende ich in diesem Verfahren keine GUIDs mehr. Wie die andere beliebte Antwort verwende ich jetzt meinen eigenen Hash-Algorithmus, um den Schlüssel zum Senden der URL zu generieren. Dies hat den Vorteil, dass es auch kürzer ist. Schauen Sie sich System.Security.Cryptography an, um sie zu generieren, was ich normalerweise auch mit einem SALT verwende.

Setzen Sie das Passwort des Benutzers nicht sofort zurück.

Setzen Sie das Kennwort des Benutzers nicht sofort zurück, wenn Sie es anfordern. Dies ist eine Sicherheitslücke, da jemand E-Mail-Adressen erraten kann (d. H. Ihre E-Mail-Adresse im Unternehmen) und die Kennwörter nach Belieben zurücksetzen kann. Best Practices in diesen Tagen enthalten normalerweise einen "Bestätigungslink", der an die E-Mail-Adresse des Benutzers gesendet wird, um zu bestätigen, dass er sie zurücksetzen möchte. Über diesen Link möchten Sie den eindeutigen Schlüssel senden. Ich sende meine mit einem Link wie: domain.com/User/PasswordReset/xjdk2ms92

Ja, legen Sie ein Timeout für den Link fest und speichern Sie den Schlüssel und das Timeout in Ihrem Backend (und Salt, wenn Sie eines verwenden). Zeitüberschreitungen von 3 Tagen sind die Norm, und Sie sollten den Benutzer auf Web-Ebene über 3 Tage informieren, wenn er eine Rücksetzung anfordert.

Verwenden Sie einen eindeutigen Hash-Schlüssel

In meiner vorherigen Antwort wurde angegeben, eine GUID zu verwenden. Ich bearbeite das jetzt, um jedem zu raten, einen zufällig generierten Hash zu verwenden, z. mit der RNGCryptoServiceProvider. Achten Sie darauf, alle "echten Wörter" aus dem Hash zu entfernen. Ich erinnere mich an ein spezielles Telefonat um 6 Uhr morgens, bei dem eine Frau ein bestimmtes "c" -Wort in ihrem "vermutlich zufälligen" Hash-Schlüssel erhalten hatte, den ein Entwickler getan hat. Doh!

Gesamte Prozedur

  • Der Benutzer klickt auf das Kennwort "Zurücksetzen".
  • Der Benutzer wird nach einer E-Mail gefragt. 
  • Benutzer gibt E-Mail ein und klickt auf Senden. Bestätigen oder verweigern Sie die E-Mail nicht, da dies ebenfalls eine schlechte Praxis ist. Sagen Sie einfach: "Wir haben eine Anfrage zum Zurücksetzen des Passworts gesendet, wenn die E-Mail bestätigt wurde." oder etwas kryptisch.
  • Sie erstellen einen Hash aus der RNGCryptoServiceProvider, speichern ihn als separate Entität in einer ut_UserPasswordRequests-Tabelle und stellen eine Verknüpfung mit dem Benutzer her. So können Sie alte Anfragen verfolgen und den Benutzer darüber informieren, dass ältere Links abgelaufen sind.
  • Senden Sie den Link an die E-Mail.

Der Benutzer erhält den Link wie http://domain.com/User/PasswordReset/xjdk2ms92 und klickt darauf. 

Wenn der Link bestätigt ist, fragen Sie nach einem neuen Passwort. Einfach, und der Benutzer kann sein eigenes Passwort festlegen. Oder legen Sie hier Ihr eigenes kryptisches Passwort fest und teilen Sie ihm hier das neue Passwort mit (und senden Sie es per E-Mail).

67
eduncan911

Zunächst müssen wir wissen, was Sie bereits über den Benutzer wissen. Sie haben offensichtlich einen Benutzernamen und ein altes Passwort. Was weißt du noch? Hast du eine Email adresse? Haben Sie Daten bezüglich der Lieblingsblume des Benutzers?

Angenommen, Sie haben einen Benutzernamen, ein Kennwort und eine funktionierende E-Mail-Adresse, müssen Sie Ihrer Benutzertabelle zwei Felder hinzufügen (vorausgesetzt, es handelt sich um eine Datenbanktabelle): ein Datum mit dem Namen new_passwd_expire und eine Zeichenfolge new_passwd_id.

Angenommen, Sie haben die E-Mail-Adresse des Benutzers. Wenn jemand eine Rücksetzung des Kennworts anfordert, aktualisieren Sie die Benutzertabelle wie folgt:

new_passwd_expire = now() + some number of days
new_passwd_id = some random string of characters (see below)

Als nächstes senden Sie eine E-Mail an den Benutzer an dieser Adresse:

Liebes so und so

Jemand hat ein neues Passwort für das Benutzerkonto <Benutzername> unter <Name Ihrer Website> angefordert. Wenn Sie diese Kennwortrücksetzung angefordert haben, folgen Sie diesem Link:

http://example.com/yourscript.lang?update=<new_password_id >

Wenn dieser Link nicht funktioniert, können Sie nach http://example.com/yourscript.lang gehen und Folgendes in das Formular eingeben: <neue_kennwort_id>

Wenn Sie keine Kennwortrücksetzung angefordert haben, können Sie diese E-Mail ignorieren.

Danke, Yada Yada

Nun kodiere yourscript.lang: Dieses Skript benötigt ein Formular. Wenn das var-Update an die URL übergeben wurde, werden Sie nur nach dem Benutzernamen und der E-Mail-Adresse des Benutzers gefragt. Wenn das Update nicht bestanden wird, werden Benutzername, E-Mail-Adresse und ID-Code der E-Mail abgefragt. Sie fragen auch nach einem neuen Passwort (zweimal).

Um das neue Kennwort des Benutzers zu überprüfen, überprüfen Sie, ob Benutzername, E-Mail-Adresse und ID-Code übereinstimmen, dass die Anforderung nicht abgelaufen ist und dass die beiden neuen Kennwörter übereinstimmen. Wenn erfolgreich, ändern Sie das Kennwort des Benutzers in das neue Kennwort und löschen Sie die Felder zum Zurücksetzen des Kennworts aus der Benutzertabelle. Melden Sie den Benutzer außerdem ab, um alle Login-Cookies zu löschen bzw. zu löschen, und leiten Sie den Benutzer zur Anmeldeseite weiter.

Im Wesentlichen ist das Feld new_passwd_id ein Kennwort, das nur auf der Seite zum Zurücksetzen des Kennworts funktioniert.

Eine mögliche Verbesserung: Sie könnten <Benutzername> aus der E-Mail entfernen. "Jemand hat ein Kennwort für diese E-Mail-Adresse für ein Konto zurückgesetzt ...". Dadurch wird der Benutzername nur dem Benutzer angezeigt, wenn die E-Mail abgefangen wird. Ich habe nicht so angefangen, denn wenn jemand das Konto angreift, kennen sie bereits den Benutzernamen. Diese zusätzliche Verschleierung verhindert Man-in-the-Middle-Angriffe, wenn jemand die E-Mail abfängt.

Wie für Ihre Fragen:

erzeugen der zufälligen Zeichenfolge: Es muss nicht extrem zufällig sein. Jeder GUID Generator oder sogar md5 (concat (salt, current_timestamp ())) reicht aus, wobei Salt etwas im Benutzerdatensatz ist, als ob ein Zeitstempel-Konto erstellt wurde. Es muss etwas sein, das der Benutzer nicht sehen kann.

timer: Ja, Sie brauchen dies nur, um Ihre Datenbank gesund zu halten. Es ist nicht mehr als eine Woche erforderlich, aber mindestens zwei Tage, da Sie nie wissen, wie lange eine E-Mail-Verzögerung dauert.

IP-Adresse: Da die E-Mail um Tage verzögert werden kann, ist die IP-Adresse nur zur Protokollierung und nicht zur Überprüfung von Nutzen. Wenn Sie es protokollieren möchten, tun Sie es, sonst brauchen Sie es nicht.

Bildschirm zurücksetzen: Siehe oben.

Hoffe das deckt es ab. Viel Glück.

8
jmucchiello

Ein GUID, der an die E-Mail-Adresse des Datensatzes gesendet wird, ist für die meisten normalen Anwendungen wahrscheinlich ausreichend - mit einem noch besseren Timeout.

Wenn die E-Mail-Box des Benutzers gefährdet ist (d. H. Ein Hacker hat das Anmelde-/Kennwort für die E-Mail-Adresse), können Sie nicht viel dagegen tun.

3
E.J. Brennan

Sie können eine E-Mail mit einem Link an den Benutzer senden. Dieser Link würde einige schwer zu erratende Zeichenfolgen enthalten (wie GUID). Auf der Serverseite würden Sie auch die gleiche Zeichenfolge speichern, die Sie an den Benutzer gesendet haben. Wenn der Benutzer jetzt auf den Link drückt, können Sie in Ihrem Datenbankeintrag dieselbe Geheimzeichenfolge finden und sein Kennwort zurücksetzen.

2
Sergej Andrejev

1) Um die eindeutige ID zu generieren, können Sie den Secure Hash Algorithm verwenden. Meinten Sie ein Verfallsdatum für den Link zum Zurücksetzen von pwd? Ja, Sie können ein Ablaufdatum festlegen 3) Sie können neben der E-Mail-ID weitere Informationen anfordern, die überprüft werden sollen .. Wie Geburtsdatum oder einige Sicherheitsfragen 4) Sie können auch zufällige Zeichen generieren und diese auch zusammen mit .__ eingeben. request .., um sicherzustellen, dass die Passwortabfrage nicht durch einige Spyware oder ähnliches automatisiert wird.

2
Java Guy

Ich denke, Microsoft-Leitfaden für ASP.NET Identity ist ein guter Anfang.

https://docs.Microsoft.com/en-us/aspnet/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity

Code, den ich für ASP.NET-Identität verwende:

Web.Config:

<add key="AllowedHosts" value="example.com,example2.com" />

AccountController.cs:

[Route("RequestResetPasswordToken/{email}/")]
[HttpGet]
[AllowAnonymous]
public async Task<IHttpActionResult> GetResetPasswordToken([FromUri]string email)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    var user = await UserManager.FindByEmailAsync(email);
    if (user == null)
    {
        Logger.Warn("Password reset token requested for non existing email");
        // Don't reveal that the user does not exist
        return NoContent();
    }

    //Prevent Host Header Attack -> Password Reset Poisoning. 
    //If the IIS has a binding to accept connections on 80/443 the Host parameter can be changed.
    //See https://security.stackexchange.com/a/170759/67046
    if (!ConfigurationManager.AppSettings["AllowedHosts"].Split(',').Contains(Request.RequestUri.Host)) {
            Logger.Warn($"Non allowed Host detected for password reset {Request.RequestUri.Scheme}://{Request.Headers.Host}");
            return BadRequest();
    }

    Logger.Info("Creating password reset token for user id {0}", user.Id);

    var Host = $"{Request.RequestUri.Scheme}://{Request.Headers.Host}";
    var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
    var callbackUrl = $"{Host}/resetPassword/{HttpContext.Current.Server.UrlEncode(user.Email)}/{HttpContext.Current.Server.UrlEncode(token)}";

    var subject = "Client - Password reset.";
    var body = "<html><body>" +
               "<h2>Password reset</h2>" +
               $"<p>Hi {user.FullName}, <a href=\"{callbackUrl}\"> please click this link to reset your password </a></p>" +
               "</body></html>";

    var message = new IdentityMessage
    {
        Body = body,
        Destination = user.Email,
        Subject = subject
    };

    await UserManager.EmailService.SendAsync(message);

    return NoContent();
}

[HttpPost]
[Route("ResetPassword/")]
[AllowAnonymous]
public async Task<IHttpActionResult> ResetPasswordAsync(ResetPasswordRequestModel model)
{
    if (!ModelState.IsValid)
        return NoContent();

    var user = await UserManager.FindByEmailAsync(model.Email);
    if (user == null)
    {
        Logger.Warn("Reset password request for non existing email");
        return NoContent();
    }            

    if (!await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", model.Token, UserManager, user))
    {
        Logger.Warn("Reset password requested with wrong token");
        return NoContent();
    }

    var result = await UserManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);

    if (result.Succeeded)
    {
        Logger.Info("Creating password reset token for user id {0}", user.Id);

        const string subject = "Client - Password reset success.";
        var body = "<html><body>" +
                   "<h1>Your password for Client was reset</h1>" +
                   $"<p>Hi {user.FullName}!</p>" +
                   "<p>Your password for Client was reset. Please inform us if you did not request this change.</p>" +
                   "</body></html>";

        var message = new IdentityMessage
        {
            Body = body,
            Destination = user.Email,
            Subject = subject
        };

        await UserManager.EmailService.SendAsync(message);
    }

    return NoContent();
}

public class ResetPasswordRequestModel
{
    [Required]
    [Display(Name = "Token")]
    public string Token { get; set; }

    [Required]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 10)]
    [DataType(DataType.Password)]
    [Display(Name = "New password")]
    public string NewPassword { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm new password")]
    [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}
0
Ogglas