如何在 .Net Core Identity 中实施 2FA?
How do I enforce 2FA in .Net Core Identity?
问题:如何强制现有用户在 .Net Core 3.1 Identity 中设置 2FA?
我已经在这里看到了几个答案,但我对它们有如下疑问:
重定向用户以在登录时设置 2FA 页面(如果他们尚未设置)。问题在于用户可以简单地跳转到另一个 url 来避免这种情况,因此实际上并没有强制执行。
执行过滤器检查用户是否启用了 2FA,如果未启用则将他们重定向到 MFA 设置页面。我遇到的问题是,在每次导航时,服务器都必须转到数据库以检查用户是否启用了此字段,从而对每个请求产生重大性能影响。我知道去数据库一次可能听起来不多,但我曾处理过一些应用程序,在这些应用程序中,这是常态,其他事情也使用这种方法,导致操作前的数据库查询堆积如山。除非绝对必要,否则我想避免这种行为。
我目前的想法是登录时:
检查用户凭据但不登录
userManager.CheckPasswordAsync(....)
如果凭据通过,请检查用户是否启用了 2FA。如果他们这样做,请继续登录流程,否则:
生成用户令牌:
userManager.GenerateUserTokenAsync(.......)
并将其与用户名一起存储在服务器端缓存中。然后通过重定向到 2FA 设置页面将密钥传递给缓存的项目,该页面不会设置 [authorize]
属性,允许未登录的用户访问它。
在 2FA 设置页面上执行任何操作之前,使用提供的密钥检索缓存的项目并验证令牌和用户名:
userManager.VerifyUserTokenAsync(......)
如果没有通过,return Unauthorized
否则继续并从通过缓存键传递的 url 中提供的用户名中获取当前用户。同时转储缓存的项目和密钥,以便 url 被狡猾的浏览器扩展程序抢走后无法再次使用。
继续将新的缓存密钥传递给新的用户令牌,并将用户名传递给每个 2FA 页面,以便在用户导航时对其进行身份验证。
这是对用户令牌的适当使用吗?这种方法是否足够安全?我担心让用户未登录会带来安全问题,但我认为有必要避免前面提到的在每次请求时都去数据库检查 2FA 的问题,因为尝试导航离开的方法会只需重定向到登录。
我选择实施更现代且对 OAuth 友好的解决方案(与 .Net Core Identity 内联)。
首先,我创建了一个扩展 UserClaimsPrincipalFactory
.
的自定义声明主体工厂
这允许我们在构建运行时用户对象时向用户添加声明(抱歉,我不知道它的正式名称,但它与 User
属性 你在控制器上看到的)。
我在这里添加了一个声明 'amr'(这是 RFC 8176 中描述的身份验证方法的标准名称)。这将被设置为 pwd 或 mfa,具体取决于他们是简单地使用密码还是设置了 mfa。
接下来,我添加了一个自定义授权属性来检查此声明。如果声明设置为 pwd,授权处理程序将失败。然后在与 MFA 无关的所有控制器上设置此属性,这样用户仍然可以进入设置 MFA,但别无其他。
这种技术的唯一缺点是开发人员需要记住将该属性添加到每个非 MFA 控制器,但除此之外,它工作得很好,因为声明存储在用户的 cookie 中(它不是' t 可修改),因此性能影响非常小。
希望这对其他人有帮助,这是我阅读的解决方案的基础:
https://damienbod.com/2019/12/16/force-asp-net-core-openid-connect-client-to-require-mfa/
https://docs.microsoft.com/en-us/aspnet/core/security/authentication/mfa?view=aspnetcore-5.0#force-aspnet-core-openid-connect-client-to-require-mfa
我通过 Filter Method
实现了这个
我有一个 BasePageModel
,我的所有页面都继承了它
public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (!User.Identity.IsAuthenticated)
{
await next.Invoke();
return;
}
var user = await UserManager.GetUserAsync(User);
var allowedPages = new List<string>
{
"Areas_Identity_Pages_Account_ConfirmEmail",
"Areas_Identity_Pages_Account_ConfirmEmailChange",
"Areas_Identity_Pages_Account_Logout",
"Areas_Identity_Pages_Account_Manage_EnableAuthenticator",
"Areas_Identity_Pages_Account_ResetPassword",
"Pages_AllowedPageX",
"Pages_AllowedPageY",
"Pages_Privacy"
};
var page = context.ActionDescriptor.PageTypeInfo.Name;
if (!user.TwoFactorEnabled && allowedPages.All(p => p != page))
{
context.Result = RedirectToPage("/Account/Manage/EnableAuthenticator", new { area = "Identity" });
}
else
{
await next.Invoke();
}
}
然后我更改了 Disable2fa
和 ResetAuthenticator
页面以重定向到主 2fa 页面
public IActionResult OnGet() => RedirectToPage("./TwoFactorAuthentication");
并从该页面删除了 reset/disable 链接
问题:如何强制现有用户在 .Net Core 3.1 Identity 中设置 2FA?
我已经在这里看到了几个答案,但我对它们有如下疑问:
重定向用户以在登录时设置 2FA 页面(如果他们尚未设置)。问题在于用户可以简单地跳转到另一个 url 来避免这种情况,因此实际上并没有强制执行。
执行过滤器检查用户是否启用了 2FA,如果未启用则将他们重定向到 MFA 设置页面。我遇到的问题是,在每次导航时,服务器都必须转到数据库以检查用户是否启用了此字段,从而对每个请求产生重大性能影响。我知道去数据库一次可能听起来不多,但我曾处理过一些应用程序,在这些应用程序中,这是常态,其他事情也使用这种方法,导致操作前的数据库查询堆积如山。除非绝对必要,否则我想避免这种行为。
我目前的想法是登录时:
检查用户凭据但不登录
userManager.CheckPasswordAsync(....)
如果凭据通过,请检查用户是否启用了 2FA。如果他们这样做,请继续登录流程,否则:
生成用户令牌:
userManager.GenerateUserTokenAsync(.......)
并将其与用户名一起存储在服务器端缓存中。然后通过重定向到 2FA 设置页面将密钥传递给缓存的项目,该页面不会设置 [authorize]
属性,允许未登录的用户访问它。
在 2FA 设置页面上执行任何操作之前,使用提供的密钥检索缓存的项目并验证令牌和用户名:
userManager.VerifyUserTokenAsync(......)
如果没有通过,
return Unauthorized
否则继续并从通过缓存键传递的 url 中提供的用户名中获取当前用户。同时转储缓存的项目和密钥,以便 url 被狡猾的浏览器扩展程序抢走后无法再次使用。继续将新的缓存密钥传递给新的用户令牌,并将用户名传递给每个 2FA 页面,以便在用户导航时对其进行身份验证。
这是对用户令牌的适当使用吗?这种方法是否足够安全?我担心让用户未登录会带来安全问题,但我认为有必要避免前面提到的在每次请求时都去数据库检查 2FA 的问题,因为尝试导航离开的方法会只需重定向到登录。
我选择实施更现代且对 OAuth 友好的解决方案(与 .Net Core Identity 内联)。
首先,我创建了一个扩展 UserClaimsPrincipalFactory
.
这允许我们在构建运行时用户对象时向用户添加声明(抱歉,我不知道它的正式名称,但它与 User
属性 你在控制器上看到的)。
我在这里添加了一个声明 'amr'(这是 RFC 8176 中描述的身份验证方法的标准名称)。这将被设置为 pwd 或 mfa,具体取决于他们是简单地使用密码还是设置了 mfa。
接下来,我添加了一个自定义授权属性来检查此声明。如果声明设置为 pwd,授权处理程序将失败。然后在与 MFA 无关的所有控制器上设置此属性,这样用户仍然可以进入设置 MFA,但别无其他。
这种技术的唯一缺点是开发人员需要记住将该属性添加到每个非 MFA 控制器,但除此之外,它工作得很好,因为声明存储在用户的 cookie 中(它不是' t 可修改),因此性能影响非常小。
希望这对其他人有帮助,这是我阅读的解决方案的基础: https://damienbod.com/2019/12/16/force-asp-net-core-openid-connect-client-to-require-mfa/ https://docs.microsoft.com/en-us/aspnet/core/security/authentication/mfa?view=aspnetcore-5.0#force-aspnet-core-openid-connect-client-to-require-mfa
我通过 Filter Method
实现了这个我有一个 BasePageModel
,我的所有页面都继承了它
public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (!User.Identity.IsAuthenticated)
{
await next.Invoke();
return;
}
var user = await UserManager.GetUserAsync(User);
var allowedPages = new List<string>
{
"Areas_Identity_Pages_Account_ConfirmEmail",
"Areas_Identity_Pages_Account_ConfirmEmailChange",
"Areas_Identity_Pages_Account_Logout",
"Areas_Identity_Pages_Account_Manage_EnableAuthenticator",
"Areas_Identity_Pages_Account_ResetPassword",
"Pages_AllowedPageX",
"Pages_AllowedPageY",
"Pages_Privacy"
};
var page = context.ActionDescriptor.PageTypeInfo.Name;
if (!user.TwoFactorEnabled && allowedPages.All(p => p != page))
{
context.Result = RedirectToPage("/Account/Manage/EnableAuthenticator", new { area = "Identity" });
}
else
{
await next.Invoke();
}
}
然后我更改了 Disable2fa
和 ResetAuthenticator
页面以重定向到主 2fa 页面
public IActionResult OnGet() => RedirectToPage("./TwoFactorAuthentication");
并从该页面删除了 reset/disable 链接