首页 > 解决方案 > 如何防止 SAAS 应用程序中的多次登录?

问题描述

我需要做什么

我正在使用 ASP.NET CORE 开发应用程序,但实际上在使用该Identity实现时遇到了问题。

在官方文档中实际上没有关于多会话的参考,这很糟糕,因为我开发了一个SaaS应用程序;特别是用户订阅了付费计划以访问一组特定的功能,他可以将他的凭据提供给其他用户,这样他们就可以免费访问,这是一个非常糟糕的场景,我会损失很多金钱和时间。

我虽然

在网上搜索了很多后,我找到了许多旧版本 ASP.NET CORE 的解决方案,所以我无法测试,但我了解通常这个问题的解决方案与存储用户时间戳有关(这是在登录时生成的 GUID)在数据库内部,因此每次用户访问受限页面并且有更多会话(具有不同的用户时间戳)时,旧会话将关闭。

我不喜欢这个解决方案,因为用户可以轻松地复制cookie浏览器并将其分享给其他用户。

我虽然将登录用户会话的信息存储在数据库中,但这也需要大量的连接。所以我对 ASP.NET CORE 的缺乏经验和网络资源的缺乏让我感到困惑。

有人可以分享一个通用的想法来实施防止多用户登录的安全解决方案吗?

标签: 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>();

推荐阅读