Я работаю над приложением в ASP.NET, и мне было интересно, как я могу реализовать Password Reset
функцию, если бы я хотел свернуть свою собственную.
В частности, у меня есть следующие вопросы:
- Каков хороший способ создания уникального идентификатора, который трудно взломать?
- Должен ли быть к нему привязан таймер? Если да, то как долго это должно быть?
- Следует ли мне записывать IP-адрес? Это вообще имеет значение?
- Какую информацию я должен запрашивать на экране «Сброс пароля»? Просто адрес электронной почты? Или, может быть, адрес электронной почты плюс некоторая информация, которую они «знают»? (Любимая команда, имя щенка и т. Д.)
Есть ли еще какие-то соображения, о которых мне нужно знать?
NB : Другие вопросы полностью замалчивают техническую реализацию. Действительно, принятый ответ замалчивает кровавые детали. Я надеюсь, что в этом вопросе и последующих ответах будут подробно описаны кровавые детали, и я надеюсь, что, если сформулировать этот вопрос гораздо более узко, ответы будут менее «пустыми» и более «кровавыми».
Изменить : ответы, которые также касаются того, как такая таблица будет моделироваться и обрабатываться в SQL Server, или любые ссылки ASP.NET MVC на ответ будут оценены.
источник
Ответы:
Здесь много хороших ответов, я не буду повторять все это ...
За исключением одной проблемы, которая повторяется здесь почти в каждом ответе, даже если он неверен:
Это неверно, GUID - очень слабые идентификаторы, и их НЕ следует использовать для разрешения доступа к учетной записи пользователя.
Если вы исследуете структуру, вы получите максимум 128 бит ... что в настоящее время не считается большим количеством.
Из которых первая половина является типичным инвариантом (для генерирующей системы), а половина того, что осталось, зависит от времени (или что-то подобное).
В общем, это очень слабый и легко взломанный механизм.
Так что не используйте это!
Вместо этого просто используйте криптографически стойкий генератор случайных чисел (
System.Security.Cryptography.RNGCryptoServiceProvider
) и получите не менее 256 бит необработанной энтропии.Все остальное, как и многие другие ответы.
источник
EDIT 2012/05/22: в продолжение этого популярного ответа я больше не использую GUID в этой процедуре. Как и другой популярный ответ, теперь я использую свой собственный алгоритм хеширования для генерации ключа для отправки в URL-адресе. Это также имеет то преимущество, что оно короче. Загляните в System.Security.Cryptography, чтобы сгенерировать их, я также обычно использую SALT.
Во-первых, не сбрасывайте пароль пользователя сразу.
Во-первых, не сбрасывайте пароль пользователя сразу после его запроса. Это нарушение безопасности, поскольку кто-то может угадать адреса электронной почты (то есть ваш адрес электронной почты в компании) и сбросить пароли по своей прихоти. В наши дни передовой опыт обычно включает ссылку «подтверждение», отправляемую на адрес электронной почты пользователя, подтверждающую, что он хочет сбросить ее. По этой ссылке вы хотите отправить уникальную ключевую ссылку. Я отправляю свой со ссылкой вроде:
domain.com/User/PasswordReset/xjdk2ms92
Да, установите тайм-аут для ссылки и сохраните ключ и тайм-аут на вашем сервере (и соль, если вы его используете). Таймауты в 3 дня являются нормой, и обязательно уведомляйте пользователя о 3 днях на веб-уровне, когда они запрашивают сброс.
Используйте уникальный хеш-ключ
В моем предыдущем ответе говорилось, что нужно использовать GUID. Сейчас я редактирую это, чтобы посоветовать всем использовать случайно сгенерированный хеш, например, используя
RNGCryptoServiceProvider
. И не забудьте исключить из хеша «настоящие слова». Я вспоминаю специальный телефонный звонок в 6 утра, в котором женщина получила определенное слово «c» в своем хешированном ключе «предположим, что это случайный», который сделал разработчик. Дох!Вся процедура
RNGCryptoServiceProvider
, храните его как отдельный объект вut_UserPasswordRequests
таблице и связываете его с пользователем. Таким образом, вы можете отслеживать старые запросы и сообщать пользователю, что срок действия старых ссылок истек.Пользователь получает ссылку, например
http://domain.com/User/PasswordReset/xjdk2ms92
, и нажимает на нее.Если ссылка подтверждена, вы запрашиваете новый пароль. Просто, и пользователь может установить свой собственный пароль. Или установите здесь свой собственный загадочный пароль и сообщите им об их новом пароле здесь (и отправьте им по электронной почте).
источник
Во-первых, нам нужно знать, что вы уже знаете о пользователе. Очевидно, у вас есть имя пользователя и старый пароль. Что еще ты знаешь? У вас есть адрес электронной почты? У вас есть данные о любимом цветке пользователя?
Предполагая, что у вас есть имя пользователя, пароль и рабочий адрес электронной почты, вам нужно добавить два поля в свою таблицу пользователей (если это таблица базы данных): дату с именем new_passwd_expire и строку new_passwd_id.
Предполагая, что у вас есть адрес электронной почты пользователя, когда кто-то запрашивает сброс пароля, вы обновляете таблицу пользователей следующим образом:
new_passwd_expire = now() + some number of days new_passwd_id = some random string of characters (see below)
Затем вы отправляете электронное письмо пользователю по этому адресу:
Теперь напишите код yourscript.lang: для этого скрипта нужна форма. Если обновление var передается по URL-адресу, форма просто запрашивает имя пользователя и адрес электронной почты. Если обновление не прошло, оно запрашивает имя пользователя, адрес электронной почты и идентификационный код, отправленные в электронном письме. Вы также просите новый пароль (дважды, конечно).
Чтобы проверить новый пароль пользователя, вы подтверждаете, что имя пользователя, адрес электронной почты и идентификационный код совпадают, срок действия запроса не истек и два новых пароля совпадают. В случае успеха вы меняете пароль пользователя на новый пароль и очищаете поля сброса пароля из таблицы пользователей. Также не забудьте вывести пользователя из системы / очистить все файлы cookie, связанные с входом в систему, и перенаправить пользователя на страницу входа.
По сути, поле new_passwd_id - это пароль, который работает только на странице сброса пароля.
Одно потенциальное улучшение: вы можете удалить <username> из письма. «Кто-то запросил сброс пароля для учетной записи на этом адресе электронной почты ...» Таким образом, имя пользователя будет известно только пользователю в случае перехвата электронной почты. Я не начал с этого, потому что если кто-то атакует учетную запись, они уже знают имя пользователя. Эта дополнительная скрытность предотвращает атаки типа «злоумышленник в середине» в случае, если кто-то злоумышленник перехватит электронную почту.
Что касается ваших вопросов:
генерация случайной строки: она не должна быть очень случайной. Достаточно любого генератора GUID или даже md5 (concat (salt, current_timestamp ())), где соль - это что-то в записи пользователя, например, была создана учетная запись timestamp. Это должно быть то, что пользователь не может видеть.
timer: Да, вам это нужно только для того, чтобы ваша база данных работала. На самом деле необходимо не более недели, но не менее 2 дней, поскольку никогда не знаешь, как долго может длиться задержка по электронной почте.
IP-адрес: поскольку электронное письмо может быть задержано на несколько дней, IP-адрес полезен только для регистрации, а не для проверки. Если вы хотите его зарегистрировать, сделайте это, иначе он вам не понадобится.
Экран сброса: см. Выше.
Надеюсь, что это покрывает его. Удачи.
источник
GUID, отправленный на указанный адрес электронной почты, вероятно, будет достаточным для большинства обычных приложений - с таймаутом даже лучше.
В конце концов, если почтовый ящик пользователя был скомпрометирован (т. Е. У хакера есть логин / пароль для этого адреса электронной почты), вы мало что можете с этим поделать.
источник
Вы можете отправить электронное письмо пользователю со ссылкой. Эта ссылка будет содержать некоторую трудно угадываемую строку (например, GUID). На стороне сервера вы также должны сохранить ту же строку, что и отправили пользователю. Теперь, когда пользователь нажимает ссылку, вы можете найти в своей базе данных запись с той же секретной строкой и сбросить ее пароль.
источник
1) Для создания уникального идентификатора вы можете использовать алгоритм безопасного хеширования. 2) таймер прикреплен? Вы имели в виду истечение срока действия ссылки сброса pwd? Да, у вас может быть установлен срок действия 3) Вы можете запросить дополнительную информацию, кроме адреса электронной почты, для проверки .. Например, дату рождения или некоторые вопросы безопасности 4) Вы также можете сгенерировать случайные символы и попросить ввести их также вместе с запросом .. чтобы убедиться, что запрос пароля не автоматизирован шпионскими программами и т.п.
источник
Я думаю, что руководство Microsoft по ASP.NET Identity - хорошее начало.
https://docs.microsoft.com/en-us/aspnet/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity
Код, который я использую для идентификации ASP.NET:
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; } }
источник