asp.net-core - 如何防止 SAAS 应用程序中的多次登录?
问题描述
我需要做什么
我正在使用 ASP.NET CORE 开发应用程序,但实际上在使用该Identity
实现时遇到了问题。
在官方文档中实际上没有关于多会话的参考,这很糟糕,因为我开发了一个SaaS
应用程序;特别是用户订阅了付费计划以访问一组特定的功能,他可以将他的凭据提供给其他用户,这样他们就可以免费访问,这是一个非常糟糕的场景,我会损失很多金钱和时间。
我虽然
在网上搜索了很多后,我找到了许多旧版本 ASP.NET CORE 的解决方案,所以我无法测试,但我了解通常这个问题的解决方案与存储用户时间戳有关(这是在登录时生成的 GUID)在数据库内部,因此每次用户访问受限页面并且有更多会话(具有不同的用户时间戳)时,旧会话将关闭。
我不喜欢这个解决方案,因为用户可以轻松地复制cookie
浏览器并将其分享给其他用户。
我虽然将登录用户会话的信息存储在数据库中,但这也需要大量的连接。所以我对 ASP.NET CORE 的缺乏经验和网络资源的缺乏让我感到困惑。
有人可以分享一个通用的想法来实施防止多用户登录的安全解决方案吗?
解决方案
我创建了一个 github 存储库,其中对仅允许单个会话所需的默认 .net core 2.1 模板进行了更改。https://github.com/xKloc/IdentityWithSession
这是要点。
首先,使用自定义类覆盖默认UserClaimsPrincipalFactory<IdentityUser>
类,该类会将您的会话添加到用户声明中。声明只是一个键/值对,将存储在用户的 cookie 中以及AspNetUserClaims
表下的服务器上。
在项目中的任何位置添加此类。
public class ApplicationClaimsPrincipalFactory : UserClaimsPrincipalFactory<IdentityUser>
{
private readonly UserManager<IdentityUser> _userManager;
public ApplicationClaimsPrincipalFactory(UserManager<IdentityUser> userManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, optionsAccessor)
{
_userManager = userManager;
}
public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
{
// find old sessions and remove
var claims = await _userManager.GetClaimsAsync(user);
var session = claims.Where(e => e.Type == "session");
await _userManager.RemoveClaimsAsync(user, session);
// add new session claim
await _userManager.AddClaimAsync(user, new Claim("session", Guid.NewGuid().ToString()));
// create principal
var principal = await base.CreateAsync(user);
return principal;
}
}
接下来,我们将创建一个授权处理程序,它将检查会话在每个请求上是否有效。
处理程序会将来自用户 cookie 的会话声明与存储在数据库中的会话声明进行匹配。如果它们匹配,则授权用户继续。如果它们不匹配,用户将收到拒绝访问消息。
在项目的任何位置添加这两个类。
public class ValidSessionRequirement : IAuthorizationRequirement
{
}
public class ValidSessionHandler : AuthorizationHandler<ValidSessionRequirement>
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
public ValidSessionHandler(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager)
{
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
_signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager));
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidSessionRequirement requirement)
{
// if the user isn't authenticated then no need to check session
if (!context.User.Identity.IsAuthenticated)
return;
// get the user and session claim
var user = await _userManager.GetUserAsync(context.User);
var claims = await _userManager.GetClaimsAsync(user);
var serverSession = claims.First(e => e.Type == "session");
var clientSession = context.User.FindFirst("session");
// if the client session matches the server session then the user is authorized
if (serverSession?.Value == clientSession?.Value)
{
context.Succeed(requirement);
}
return;
}
}
最后,只需在启动时注册这些新类,以便它们被调用。
在方法下将此代码添加到您的Startup
类中ConfigureServices
,就在下面services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
// build default authorization policy
var defaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddRequirements(new ValidSessionRequirement())
.Build();
// add authorization to the pipe
services.AddAuthorization(options =>
{
options.DefaultPolicy = defaultPolicy;
});
// register new claims factory
services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, ApplicationClaimsPrincipalFactory>();
// register valid session handler
services.AddTransient<IAuthorizationHandler, ValidSessionHandler>();
推荐阅读
- python - 回退策略不适用于 RASA 框架
- html - div容器内的HTML表格scollbar问题
- python - 无法使用 TensorFlow 对象检测 API 训练具有更大输入分辨率的 SSD Inception-V2
- mysql - 什么是安全和最好的方式(性能副)上传图片
- json - JSON 嵌套条件失败
- r - 如何在r中一次循环遍历两个变量
- swift - 使用共享用户访问组时,Firebase Auth 用户已过时
- c++ - 使用QT和VS运行CGAL5.2的官方示例
- javascript - firebase__WEBPACK_IMPORTED_MODULE_2___默认错误 react.js
- openssl - 如何为通过 Chrome 要求的 IP 地址创建自签名(或由自己的 CA 签名)SSL 证书