비밀번호 재설정을 구현하는 방법?
저는 ASP.NET에서 응용 프로그램을 작업 중이며, Password Reset
제가 직접 롤링하려는 경우 함수를 구현할 수있는 방법을 구체적으로 궁금해 했습니다.
특히 다음과 같은 질문이 있습니다.
- 크랙하기 어려운 고유 ID를 생성하는 좋은 방법은 무엇입니까?
- 타이머가 부착되어 있어야합니까? 그렇다면 얼마나 걸리나요?
- IP 주소를 기록해야합니까? 그것이 중요합니까?
- "비밀번호 재설정"화면에서 어떤 정보를 요청해야합니까? 이메일 주소 만? 아니면 이메일 주소와 그들이 '알고있는'정보의 일부일까요? (좋아하는 팀, 강아지 이름 등)
알아야 할 다른 고려 사항이 있습니까?
NB : 기술적 구현에 대해 다른 질문 이 완전히 사라졌습니다 . 실제로 받아 들여지는 대답은 피투성이의 세부 사항을 광택이 없습니다. 이 질문과 후속 답변이 피투성이의 세부 사항으로 들어가기를 바랍니다.이 질문을 훨씬 더 좁게 표현하여 대답이 '보풀'이 적고 '고어'가 되길 바랍니다.
편집 : SQL Server 또는 ASP.NET MVC 링크에서 이러한 테이블을 모델링하고 처리하는 방법에 대한 답변도 감사하겠습니다.
여기에 좋은 답변이 많이 있습니다. 모든 것을 반복하지 않을 것입니다.
한 가지 문제를 제외하고는 여기에 거의 모든 답변이 반복됩니다.
가이드는 (실제로) 독특하고 통계적으로 추측하기 불가능합니다.
이 GUID가 매우 약한 식별자, 사실이 아니다, 그리고해야 하지 사용자 계정에 대한 액세스를 허용 할 수.
구조를 살펴보면 기껏해야 총 128 비트를 얻을 수 있습니다. 요즘에는 많이 고려되지 않습니다.
그중 전반부는 전형적인 불변이며 (생성 시스템의 경우) 나머지 절반은 시간 의존적입니다 (또는 다른 유사한 것).
대체로 매우 약하고 쉽게 무차별 대입하는 메커니즘입니다.
그러니 사용하지 마세요!
대신 암호 학적으로 강력한 난수 생성기 ( System.Security.Cryptography.RNGCryptoServiceProvider
)를 사용하고 최소한 256 비트의 원시 엔트로피를 얻으십시오.
나머지는 다른 수많은 답변이 제공했습니다.
2012/05/22 편집 :이 인기있는 답변에 대한 후속 조치로이 절차에서 더 이상 GUID를 직접 사용하지 않습니다. 다른 인기있는 답변과 마찬가지로 이제 URL로 보낼 키를 생성하기 위해 자체 해싱 알고리즘을 사용합니다. 이것은 또한 더 짧다는 장점이 있습니다. System.Security.Cryptography를 살펴보고 생성하는 데 일반적으로 SALT도 사용합니다.
첫째, 사용자의 비밀번호를 즉시 재설정하지 마십시오.
첫째, 사용자의 암호를 요청할 때 즉시 재설정하지 마십시오. 누군가가 이메일 주소 (즉, 회사의 이메일 주소)를 추측하고 비밀번호를 재설정 할 수 있으므로 이는 보안 위반입니다. 요즘의 모범 사례에는 일반적으로 사용자의 이메일 주소로 전송되는 "확인"링크가 포함되어있어 재설정 의사를 확인합니다. 이 링크는 고유 키 링크를 보내려는 곳입니다. 나는 다음과 같은 링크로 내 보낸다.domain.com/User/PasswordReset/xjdk2ms92
예, 링크에 시간 제한을 설정하고 키와 시간 제한을 백엔드에 저장하십시오 (사용하는 경우 솔트도 포함). 3 일의 제한 시간이 일반적이며 사용자가 재설정을 요청할 때 웹 수준에서 3 일을 사용자에게 알려야합니다.
고유 한 해시 키 사용
내 이전 답변은 GUID를 사용한다고 말했습니다. 나는 모든 사람에게 무작위로 생성 된 해시를 사용하도록 조언하기 위해 이것을 편집하고 RNGCryptoServiceProvider
있습니다. 그리고 해시에서 "실제 단어"를 제거해야합니다. 나는 여성이 개발자가 한 "무작위로 가정"해시 키에서 특정 "c"단어를받은 특별한 오전 6시 전화 통화를 기억합니다. 도!
전체 절차
- 사용자가 "재설정"암호를 클릭합니다.
- 사용자에게 이메일을 요청합니다.
- 사용자가 이메일을 입력하고 보내기를 클릭합니다. 이것은 나쁜 습관이므로 이메일을 확인하거나 거부하지 마십시오. "이메일이 확인되면 비밀번호 재설정 요청을 보냈습니다."라고 말하면됩니다. 또는 비슷하게 비밀스러운 것.
- 에서 해시를 만들고 테이블에
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)
다음으로 해당 주소로 사용자에게 이메일을 보냅니다.
친애하는 사람들
누군가 <웹 사이트 이름>에서 사용자 계정 <사용자 이름>에 대한 새 암호를 요청했습니다. 이 비밀번호 재설정을 요청한 경우 다음 링크를 따르십시오.
http://example.com/yourscript.lang?update=<new_password_id >
해당 링크가 작동하지 않으면 http://example.com/yourscript.lang으로 이동 하여 양식에 다음을 입력하십시오. <new_password_id>
비밀번호 재설정을 요청하지 않으 셨다면이 이메일을 무시 하셔도됩니다.
고마워 야다 야다
이제 yourscript.lang 코딩 :이 스크립트에는 양식이 필요합니다. var 업데이트가 URL에 전달 된 경우 양식은 사용자의 사용자 이름과 이메일 주소 만 묻습니다. 업데이트가 통과되지 않으면 사용자 이름, 이메일 주소 및 이메일로 전송 된 ID 코드를 요청합니다. 또한 새 비밀번호를 요청합니다 (물론 두 번).
사용자의 새 암호를 확인하려면 사용자 이름, 이메일 주소 및 ID 코드가 모두 일치하는지, 요청이 만료되지 않았는지, 두 개의 새 암호가 일치하는지 확인합니다. 성공하면 사용자의 암호를 새 암호로 변경하고 사용자 테이블에서 암호 재설정 필드를 지 웁니다. 또한 사용자를 로그 아웃하거나 로그인 관련 쿠키를 지우고 사용자를 로그인 페이지로 리디렉션하십시오.
기본적으로 new_passwd_id 필드는 비밀번호 재설정 페이지에서만 작동하는 비밀번호입니다.
한 가지 잠재적 개선 사항 : 이메일에서 <username>을 제거 할 수 있습니다. "누군가이 이메일 주소로 계정에 대한 비밀번호 재설정을 요청했습니다 ...."따라서 사용자 이름을 이메일이 도청 된 경우 사용자 만 알 수있는 것으로 만듭니다. 누군가가 계정을 공격하는 경우 이미 사용자 이름을 알고 있기 때문에 그렇게 시작하지 않았습니다. 이렇게 추가 된 모호성은 악의적 인 누군가가 이메일을 가로채는 경우 중간자 (man-in-the-middle) 기회 공격을 차단합니다.
귀하의 질문 :
임의의 문자열 생성 : 매우 임의적 일 필요는 없습니다. 모든 GUID 생성기 또는 md5 (concat (salt, current_timestamp ()))만으로도 충분합니다. 여기서 salt는 타임 스탬프 계정이 생성 된 것과 같은 사용자 레코드에 있습니다. 사용자가 볼 수없는 것이어야합니다.
타이머 : 예, 데이터베이스를 정상 상태로 유지하기 위해 필요합니다. 일주일 이상은 필요하지 않지만 이메일 지연이 얼마나 오래 지속 될지 모르기 때문에 최소 2 일은 필요합니다.
IP 주소 : 이메일이 며칠 지연 될 수 있으므로 IP 주소는 유효성 검사가 아닌 로깅에만 유용합니다. 기록하고 싶다면 그렇게하세요. 그렇지 않으면 필요하지 않습니다.
화면 재설정 : 위를 참조하십시오.
그것이 그것을 덮기를 바랍니다. 행운을 빕니다.
기록의 이메일 주소로 전송 된 GUID는 대부분의 평범한 응용 프로그램에 충분할 가능성이 높으며 제한 시간이 훨씬 더 좋습니다.
결국 사용자의 이메일 박스가 손상 되었다면 (즉, 해커가 이메일 주소에 대한 로그온 / 비밀번호를 가지고있는 경우) 이에 대해 할 수있는 일이별로 없습니다.
링크가있는 사용자에게 이메일을 보낼 수 있습니다. 이 링크에는 추측하기 어려운 문자열 (예 : GUID)이 포함됩니다. 서버 측에서도 사용자에게 보낸 것과 동일한 문자열을 저장합니다. 이제 사용자가 링크를 누르면 동일한 비밀 문자열이있는 db 항목을 찾아 비밀번호를 재설정 할 수 있습니다.
1) 고유 ID를 생성하려면 Secure Hash Algorithm을 사용할 수 있습니다. 2) 타이머 부착? 재설정 암호 링크에 대한 만료를 의미 했습니까? 예, 만료 설정을 할 수 있습니다. 3) 이메일 ID 이외의 다른 정보를 확인하여 요청할 수 있습니다. 생년월일 또는 보안 질문과 같은 4) 임의의 문자를 생성하고 요청과 함께 입력하도록 요청할 수도 있습니다. .. 암호 요청이 일부 스파이웨어 또는 이와 유사한 것에 의해 자동화되지 않도록합니다.
ASP.NET Identity에 대한 Microsoft 가이드가 좋은 시작이라고 생각합니다.
ASP.NET ID에 사용하는 코드 :
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; }
}
참고 URL : https://stackoverflow.com/questions/664673/how-to-implement-password-resets
'programing' 카테고리의 다른 글
프레임 내 마스킹 (자르기) 이미지 (0) | 2020.10.05 |
---|---|
이미지 처리, 파이썬? (0) | 2020.10.05 |
Android 프로젝트에서 대상 빌드를 변경하는 방법은 무엇입니까? (0) | 2020.10.05 |
항목을 확인한 후 어떤 CheckedListBox 이벤트가 트리거됩니까? (0) | 2020.10.05 |
Java에서 Long에서 Double로 변환 (0) | 2020.10.05 |