Как предотвратить множественный вход в приложение SAAS? - PullRequest
0 голосов
/ 04 сентября 2018

Что мне нужно сделать

Я разрабатываю приложение с использованием ASP.NET CORE, и на самом деле я столкнулся с проблемой при использовании реализации Identity.

В официальном документе нет упоминаний о множественном сеансе, и это плохо, потому что я разработал приложение SaaS; в частности, пользователь подписывается на платный план для доступа к определенному набору функций, и он может передавать свои учетные данные другим пользователям, чтобы они могли получить бесплатный доступ, это действительно плохой сценарий, и я потеряю много денег и времени.

Что я хоть

После большого поиска в Интернете я нашел много решений для более старой версии ASP.NET CORE, поэтому я не смог протестировать, но я понял, что обычно решение этой проблемы связано с хранением пользователя отметка времени (которая представляет собой идентификатор GUID, сгенерированный при входе в систему) внутри базы данных, поэтому каждый раз, когда пользователь получает доступ к закрытой странице и существует больше сеансов (с другой отметкой времени пользователя), старый сеанс будет закрыт.

Мне не нравится это решение, потому что пользователь может легко скопировать cookie браузера и поделиться им с другими пользователями.

Я, хотя и храню информацию о сеансе вошедшего в систему пользователя в базе данных, но это также потребует большого количества соединений. Так что мой неопытность в ASP.NET CORE и нехватка ресурсов в сети отправили меня в спутанность сознания.

Кто-то может поделиться общей идеей о реализации безопасного решения для предотвращения входа нескольких пользователей?

Ответы [ 4 ]

0 голосов
/ 07 сентября 2018

Существует несколько решений для управления доступом (ForgeRock, Oracle Access Management), которые реализуют эту функциональность квоты сеанса. ForgeRock имеет версию сообщества, и ее исходный код доступен на Github, возможно, вы можете посмотреть, как он реализован там. Из них также есть запись в блоге, в которой дан широкий обзор функциональности (https://blogs.forgerock.org/petermajor/2013/01/session-quota-basics/)

Если это слишком сложно для вашего варианта использования, я бы советовал объединить описанный вами подход «разделяемой памяти» с функцией идентификации, аналогично тому, что Фабио указал в другом ответе.

0 голосов
/ 06 сентября 2018

Лучший способ - сделать что-то похожее на то, что делают Google, Facebook и другие - определить, входит ли пользователь в систему с другого устройства. В вашем случае, я полагаю, вы хотели бы иметь немного другое поведение - вместо того, чтобы спрашивать доступ, вы, вероятно, будете это отрицать. Это похоже на то, что вы создаете лицензию «на устройство» или «лицензию на одного клиента».

Этот поток переполнения стека говорит об этом решении.

Самый надежный способ обнаружить изменение устройства - это создать отпечаток пальца браузера / устройства, на котором работает браузер. Это сложная тема, чтобы получить 100% права, и есть коммерческие предложения это чертовски хорошо, но не безупречно.

Примечание: если вы хотите начать с простого, вы можете начать с Secure cookie , который с меньшей вероятностью подвергнется краже cookie при перехвате. Например, вы можете хранить хешированный отпечаток пальца.

0 голосов
/ 07 сентября 2018

Я создал репозиторий 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>();
0 голосов
/ 04 сентября 2018

Вы можете использовать UpdateSecurityStamp для аннулирования любых существующих файлов cookie аутентификации. Например:

public async Task<IActionResult> Login(LoginViewModel model)
{
    var user = await _userManager.FindByEmailAsync(model.Email);
    if (user == null)
    {
        ModelState.AddModelError(string.Empty, "Invalid username/password.");
        return View();
    }

    if (await _userManager.ValidatePasswordAsync(user, model.Password))
    {
        await _userManager.UpdateSecurityStampAsync(user);
        var result = await _signInManager.SignInAsync(user, isPersistent: false);
        // handle `SignInResult` cases
    }
}

Обновление штампа безопасности приведет к тому, что все существующие файлы cookie авторизации станут недействительными, в основном выйдя из системы на всех других устройствах, на которых зарегистрирован пользователь. Затем вы войдете в систему на этом текущем устройстве.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...