- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
您建议使用哪种方式在 MVC
和 C#
中创建一个安全 密码重置链接?我的意思是,我会创建一个随机 token ,对吧?在发送给用户之前如何对其进行编码? MD5 是否足够好?您知道其他安全方法吗?
最佳答案
I mean, I'll create a random token, right?
有两种方法:
System.Security.Cryptography.RandomNumberGenerator
这是一个加密安全的 RNG。
System.Random
,它不是加密安全的。GET
的准则。请求应该能够(因为点击链接总是一个 GET
请求,但是 GET
请求不应该导致持久数据的状态改变,只有 POST
, PUT
和 PATCH
请求应该这样做- 这需要让用户手动复制代码并提交 POST
网络表单 - 这不是最好的用户体验。
<form method="POST">
。但它是提交用户的新密码,而不是为他们预先生成一个新密码——因此不违反 HTTP 的指导方针,因为在最终 POST
之前不会更改状态。使用新密码。像这样:
扩展您的数据库' Users
包含密码重置代码列的表格:
ALTER TABLE dbo.Users ADD
PasswordResetCode binary(12) NULL,
PasswordResetStart datetime2(7) NULL;
在您的 Web 应用程序代码中执行如下操作:
[HttpGet]
[HttpHead]
public IActionResult GetPasswordResetForm()
{
// Return a <form> allowing the user to confirm they want to reset their password, which POSTs to the action below.
}
static readonly TimeSpan _passwordResetExpiry = TimeSpan.FromMinutes( 5 );
[HttpPost]
public IActionResult SendPasswordResetCode()
{
// 1. Get a cryptographically secure random number:
// using System.Security.Cryptography;
Byte[] bytes;
String bytesBase64Url; // NOTE: This is Base64Url-encoded, not Base64-encoded, so it is safe to use this in a URL, but be sure to convert it to Base64 first when decoding it.
using( RandomNumberGenerator rng = new RandomNumberGenerator() ) {
bytes = new Byte[12]; // Use a multiple of 3 (e.g. 3, 6, 12) to prevent output with trailing padding '=' characters in Base64).
rng.GetBytes( bytes );
// The `.Replace()` methods convert the Base64 string returned from `ToBase64String` to Base64Url.
bytesBase64Url = Convert.ToBase64String( bytes ).Replace( '+', '-' ).Replace( '/', '_' );
}
// 2. Update the user's database row:
using( SqlConnection c = new SqlConnection( CONNECTION_STRING ) )
using( SqlCommand cmd = c.CreateCommand() )
{
cmd.CommandText = "UPDATE dbo.Users SET PasswordResetCode = @code, PasswordResetStart = SYSUTCDATETIME() WHERE UserId = @userId";
SqlParameter pCode = cmd.Parameters.Add( cmd.CreateParameter() );
pCode.ParameterName = "@code";
pCode.SqlDbType = SqlDbType.Binary;
pCode.Value = bytes;
SqlParameter pUserId = cmd.Parameters.Add( cmd.CreateParameter() );
pCode.ParameterName = "@userId";
pCode.SqlDbType = SqlDbType.Int;
pCode.Value = userId;
cmd.ExecuteNonQuery();
}
// 3. Send the email:
{
const String fmt = @"Greetings {0},
I am Ziltoid... the omniscient.
I have come from far across the omniverse.
You shall fetch me your universe's ultimate cup of coffee... uh... I mean, you can reset your password at {1}
You have {2:N0} Earth minutes,
Make it perfect!";
// e.g. "https://example.com/ResetPassword/123/ABCDEF"
String link = "https://example.com/" + this.Url.Action(
controller: nameof(PasswordResetController),
action: nameof(this.ResetPassword),
params: new { userId = userId, codeBase64 = bytesBase64Url }
);
String body = String.Format( CultureInfo.InvariantCulture, fmt, userName, link, _passwordResetExpiry.TotalMinutes );
this.emailService.SendEmail( user.Email, subject: "Password reset link", body );
}
}
[HttpGet( "/PasswordReset/ResetPassword/{userId}/{codeBase64Url}" )]
public IActionResult ResetPassword( Int32 userId, String codeBase64Url )
{
// Lookup the user and see if they have a password reset pending that also matches the code:
String codeBase64 = codeBase64Url.Replace( '-', '+' ).Replace( '_', '/' );
Byte[] providedCode = Convert.FromBase64String( codeBase64 );
if( providedCode.Length != 12 ) return this.BadRequest( "Invalid code." );
using( SqlConnection c = new SqlConnection( CONNECTION_STRING ) )
using( SqlCommand cmd = c.CreateCommand() )
{
cmd.CommandText = "SELECT UserId, PasswordResetCode, PasswordResetStart FROM dbo.Users SET WHERE UserId = @userId";
SqlParameter pUserId = cmd.Parameters.Add( cmd.CreateParameter() );
pCode.ParameterName = "@userId";
pCode.SqlDbType = SqlDbType.Int;
pCode.Value = userId;
using( SqlDataReader rdr = cmd.ExecuteReader() )
{
if( !rdr.Read() )
{
// UserId doesn't exist in the database.
return this.NotFound( "The UserId is invalid." );
}
if( rdr.IsDBNull( 1 ) || rdr.IsDBNull( 2 ) )
{
return this.Conflict( "There is no pending password reset." );
}
Byte[] expectedCode = rdr.GetBytes( 1 );
DateTime? start = rdr.GetDateTime( 2 );
if( !Enumerable.SequenceEqual( providedCode, expectedCode ) )
{
return this.BadRequest( "Incorrect code." );
}
// Now return a new form (with the same password reset code) which allows the user to POST their new desired password to the `SetNewPassword` action` below.
}
}
[HttpPost( "/PasswordReset/ResetPassword/{userId}/{codeBase64}" )]
public IActionResult SetNewPassword( Int32 userId, String codeBase64, [FromForm] String newPassword, [FromForm] String confirmNewPassword )
{
// 1. Use the same code as above to verify `userId` and `codeBase64`, and that `PasswordResetStart` was less than 5 minutes (or `_passwordResetExpiry`) ago.
// 2. Validate that `newPassword` and `confirmNewPassword` are the same.
// 3. Reset `dbo.Users.Password` by hashing `newPassword`, and clear `PasswordResetCode` and `PasswordResetStart`
// 4. Send the user a confirmatory e-mail informing them that their password was reset, consider including the current request's IP address and user-agent info in that e-mail message as well.
// 5. And then perform a HTTP 303 redirect to the login page - or issue a new session token cookie and redirect them to the home-page.
}
}
这种方法不需要更改您的数据库,也不需要保留新状态,但它确实需要您了解 HMAC 的工作原理。
基本上它是一个简短的结构化消息(而不是随机不可预测的字节),其中包含足够的信息以允许系统识别其密码应该被重置的用户,包括到期时间戳 - 以防止伪造此消息是加密签名的只有您的应用程序代码知道的私钥:这可以防止攻击者生成他们自己的密码重置代码(这显然不好!)。
以下是生成用于密码重置的 HMAC 代码的方法,以及验证代码的方法:
private static readonly Byte[] _privateKey = new Byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; // NOTE: You should use a private-key that's a LOT longer than just 4 bytes.
private static readonly TimeSpan _passwordResetExpiry = TimeSpan.FromMinutes( 5 );
private const Byte _version = 1; // Increment this whenever the structure of the message changes.
public static String CreatePasswordResetHmacCode( Int32 userId )
{
Byte[] message = Enumerable.Empty<Byte>()
.Append( _version )
.Concat( BitConverter.GetBytes( userId ) )
.Concat( BitConverter.GetBytes( DateTime.UtcNow.ToBinary() ) )
.ToArray();
using( HMACSHA256 hmacSha256 = new HMACSHA256( key: _privateKey ) )
{
Byte[] hash = hmacSha256.ComputeHash( buffer: message, offset: 0, count: message.Length );
Byte[] outputMessage = message.Concat( hash ).ToArray();
String outputCodeB64 = Convert.ToBase64( outputMessage );
String outputCode = outputCodeB64.Replace( '+', '-' ).Replace( '/', '_' );
return outputCode;
}
}
public static Boolean VerifyPasswordResetHmacCode( String codeBase64Url, out Int32 userId )
{
String base64 = codeBase64Url.Replace( '-', '+' ).Replace( '_', '/' );
Byte[] message = Convert.FromBase64String( base64 );
Byte version = message[0];
if( version < _version ) return false;
userId = BitConverter.ToInt32( message, startIndex: 1 ); // Reads bytes message[1,2,3,4]
Int64 createdUtcBinary = BitConverter.ToInt64( message, startIndex: 1 + sizeof(Int32) ); // Reads bytes message[5,6,7,8,9,10,11,12]
DateTime createdUtc = DateTime.FromBinary( createdUtcBinary );
if( createdUtc.Add( _passwordResetExpiry ) < DateTime.UtcNow ) return false;
const Int32 _messageLength = 1 + sizeof(Int32) + sizeof(Int64); // 1 + 4 + 8 == 13
using( HMACSHA256 hmacSha256 = new HMACSHA256( key: _privateKey ) )
{
Byte[] hash = hmacSha256.ComputeHash( message, offset: 0, count: _messageLength );
Byte[] messageHash = message.Skip( _messageLength ).ToArray();
return Enumerable.SequenceEquals( hash, messageHash );
}
}
这样使用:
// Note there is no `UserId` URL parameter anymore because it's embedded in `code`:
[HttpGet( "/PasswordReset/ResetPassword/{codeBase64Url}" )]
public IActionResult ConfirmResetPassword( String codeBase64Url )
{
if( !VerifyPasswordResetHmacCode( codeBase64Url, out Int32 userId ) )
{
// Message is invalid, such as the HMAC hash being incorrect, or the code has expired.
return this.BadRequest( "Invalid, tampered, or expired code used." );
}
else
{
// Return a web-page with a <form> to POST the code.
// Render the `codeBase64Url` to an <input type="hidden" /> to avoid the user inadvertently altering it.
// Do not reset the user's password in a GET request because GET requests must be "safe". If you send a password-reset link by SMS text message or even by email, then software bot (like link-preview generators) may follow the link and inadvertently reset the user's password!
}
}
[HttpPost( "/PasswordReset/ResetPassword" )]
public IActionResult ConfirmResetPassword( [FromForm] ConfirmResetPasswordForm model )
{
if( !VerifyPasswordResetHmacCode( model.CodeBase64Url, out Int32 userId ) )
{
return this.BadRequest( "Invalid, tampered, or expired code used." );
}
else
{
// Reset the user's password here.
}
}
关于c# - 如何创建密码重置链接?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12574595/
我有一个网站,并且我使用 javascript sdk 添加了“点赞”按钮。 这是代码 (function(d, s, id) { var js, fjs = d.g
我知道 HTML 是逐行读取的。当您链接多个 css 文件(如规范化文件和样式表文件)时,由于 CSS 重要性特异性和源顺序,样式表文件应链接在规范化文件之后。看起来这不会影响链接的 JavaScri
我正在使用官方 Bootstrap site 提供的 CDN 链接在我的网络应用程序中使用面板进行测试 在彻底检查我的代码后,面板没有显示。 但是我在 SO 上看到了类似的帖子并且 CDN 链接不同
这里是编码初学者。我正在尝试为我的移动设备网站设置断点,以便我的网站适合小屏幕。我只是想检查如果我缩小视口(viewport)的宽度,背景颜色是否会改变,但没有发生任何变化。也许我只是对一个简单的错误
举一个我想要的例子,想象一下这个字符串: $text = 'lorem ipsum About us lorem ipsum'; 如果此字符串包含一个 href 以 / 开头的 anchor 链接,则
如何链接到 LaTeX 文档的另一部分或子部分?这种链接的常规范式是什么,像[链接名称]那样写,或者像网页超链接那样写? 最佳答案 链接到另一个部分需要您的部分进行一些额外的标记。要使用的命令是: \
我有一个订单表,其中包含订单号、客户 ID 和代理 ID。然后有一个带有 id 的客户表和一个带有 id 的代理表。 我需要获取所有具有来自代理 ID 'a03' 和代理 ID 'a05' 的订单的客
假设我有: dic = {"z":"zv", "a":"av"} ## Why doesn't the following return a sorted list of keys? keys = d
我在尝试链接到外部库时得到了一些奇怪的结果。如果我从命令行运行以下命令: gcc fftwTest.c -I../extlib/fftw-3.3.5-dll32 -L../extlib/fftw-3.
我认为我没有正确理解 jQuery 链接。我正在遍历一个数组并尝试将 div 元素添加到我的包装器 CSS 类中,每个 div 元素都有一个“click”类和自定义 css top 和 left 属性
HTML 使用超级链接与网络上的另一个文档相连。几乎可以在所有的网页中找到链接。点击链接可以从一张页面跳转到另一张页面。 HTML 超链接(链接) HTML使用标签 a 来设置超文本链接。 超链
这个问题在这里已经有了答案: How do I link to part of a page? (hash?) (7 个答案) Scroll Automatically to the Bottom
我想创建一个 Docker Swarm 集群,运行一个 Elasticsearch 实例、一个 MongoDB 实例和一个 grails 应用程序,每个都在单独的机器上。我正在使用 Docker Ma
我正在尝试将 CakePHP HTML Linker 用于以下代码 Add Cuisine 由于 span 标签需要在 a 标签内。我无法根据需要获得输出。关于如何完成它的任何建议? 最佳答案 禁用链
大家好, 我最近开发了一个应用程序,很快就会提交到 App Store。我想免费提交这个应用程序,并想知道我是否可以实现一个带有 PayPal 捐赠标志的按钮,上面基本上写着“捐赠用于开发”或与此相关
我想尝试在 dlang 中使用 libuv。我下载了这样的 dlang 绑定(bind): git clone git@github.com:tamediadigital/libuv.git 现在我接
我有一个节点(节点 a),各种其他节点(节点 b/c/d/e)与之引用。 我可以创建一个带有参数的 View 作为我正在查看的节点(节点 a),并获取引用该节点的节点列表。 基本上在节点 a 查看节点
我正在尝试建立一个常见问题页面,上面有目录,下面有答案。我想点击目录中的一个问题,并在同一页面上链接到相应的答案。我如何在 CakePHP 中使用 $this->Html->link() 执行此操作方
在 WooCommerce 3.0+ 中,我使用 js 创建了一些选项卡,每个选项卡中包含来自不同类别的产品。我已经设法修改了简单产品的添加到购物车链接,其中点击了 addtocart 按钮它进入下一
Delphi 2007/2009 奇怪的问题在这里: 根据设计时定义的组件属性,是否可以在链接中包含文件或保留文件? 示例:如果我将 SomeProperty 保留为真,则在编译时,单元 SomeUn
我是一名优秀的程序员,十分优秀!