Добавление утверждений пользователю во время входа в систему с использованием удостоверения в веб-приложении ASP.NET Core

Редактировать 2:

Наконец-то разобрался. Ознакомьтесь с принятым ответом ниже.

Редактировать:

Полный исходный код с моей попыткой по указанию @Jason Pan. https://github.com/affableashish/blazor-server-auth/tree/feature/AddClaimsDuringLogin

Добавлены утверждения во время входа в систему (в файле Login.cshtml.cs ) и доступ к этим утверждениям из Razor Component.

К сожалению, это не сработало. Я получаю только null в качестве значения претензии. 😔


Оригинальный вопрос

Я видел несколько похожих вопросов, таких как это и это, но они не помогли для моего сценария.

Мое приложение представляет собой проект Blazor Server, в который я добавил Identity, выполнив шаги, упомянутые здесь.

Теперь это то, чего я хочу достичь:

  1. Пользователь вводит свои учетные данные.
  2. Если имя пользователя допустимо (в нашей Active Directory), я получаю поле, известное как EmployeeId, из Active Directory.
  3. Аутентифицируйте пользователя с помощью SignInManager.PasswordSignInAsync.
  4. Добавьте EmployeeId в качестве претензии к ClaimsPrincipal. (Чтобы я мог использовать EmployeeId от Razor Components вот так).

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

Мой метод OnPostAsync в Login.cshtml.cs выглядит так:

public class LoginModel : PageModel
{
    private readonly SignInManager<MMTUser> _signInManager;
    private readonly ILogger<LoginModel> _logger;

    public LoginModel(SignInManager<MMTUser> signInManager, ILogger<LoginModel> logger)
    {
        _signInManager = signInManager;
        _logger = logger;
    }

    [BindProperty]
    public InputModel Input { get; set; }

    public string ReturnUrl { get; set; }

    [TempData]
    public string ErrorMessage { get; set; }

    public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    {
        returnUrl ??= Url.Content("~/");

        if (ModelState.IsValid)
        {
            // Step 1: Check if this user exists in our AD
            // If YES: Grab the Employee Id and go to next step
            // If NO: Terminate the process
            var adLookupResult = ADHelper.ADLookup(Input.Username);
            if (adLookupResult == null || string.IsNullOrEmpty(adLookupResult.EmployeeId))
            {
                ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                return Page();
            }

            // Step 2: SignIn the user
            var result = await _signInManager.PasswordSignInAsync(Input.Username, Input.Password, isPersistent: Input.RememberMe, lockoutOnFailure: false);

            // Step 3: How do I add adLookupResult.EmployeeId to the ClaimsPrincipal?

            if (result.Succeeded)
            {
                _logger.LogInformation("User logged in.");
                return LocalRedirect(returnUrl);
            }
            else
            {
                ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                return Page();
            }
        }

        // If we got this far, something failed, redisplay form
        return Page();
    }
    
    public class InputModel
    {
        [Required]
        public string Username { get; set; }

        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }

        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
145
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

ОБНОВЛЯТЬ

Предыдущее решение может быть обходным путем, если оно будет помещено в промежуточное программное обеспечение. Это очень ужасно.

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

CustomClaimsPrincipalFactory.cs

using HMT.Web.Server.Areas.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Security.Claims;

namespace HMT.Web.Server
{
    public class CustomClaimsPrincipalFactory :
        UserClaimsPrincipalFactory<HMTUser>
    {
        public CustomClaimsPrincipalFactory(
            UserManager<HMTUser> userManager,
            IOptions<IdentityOptions> optionsAccessor)
                : base(userManager, optionsAccessor)
        {
        }

        // This method is called only when login. It means that "the drawback   
        // of calling the database with each HTTP request" never happen.  
        public async override Task<ClaimsPrincipal> CreateAsync(HMTUser user)
        {
            var principal = await base.CreateAsync(user);

            if (principal.Identity != null)
            {
                //((ClaimsIdentity)principal.Identity).AddClaims(
                //    new[] { new Claim("EmpId", user.EmpId) });
                ((ClaimsIdentity)principal.Identity).AddClaims(
                    new[] { new Claim("EmpId", "EmpId123") });
            }

            return principal;
        }
    }
}

Program.cs или Startup.cs

// Learned the hard way that this needs to be added after setting up Blazor (i.e. AddRazorPages, AddServerSideBlazor) - AshishK
builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<HMTUser>>();
builder.Services.AddScoped<IUserClaimsPrincipalFactory<HMTUser>, CustomClaimsPrincipalFactory>();

Для получения более подробной информации вы можете проверить ссылку.


Когда вы успешно входите в систему, вы можете использовать HttpContext.User.AddIdentity для достижения этого. Образец, как показано ниже:

   // Step 3: Add the EmployeeId to ClaimsPrincipal
    if (result.Succeeded)
    {
        HttpContext.User.AddIdentity(new ClaimsIdentity(new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, adLookupResult.EmployeeId)
        }));
        _logger.LogInformation("User logged in.");
        return LocalRedirect(returnUrl);
    }

Я сделал это, но при получении претензии получаю нулевое значение. В Login.cshtml.cs я написал это после result.Succeeded: HttpContext.User.AddIdentity(new ClaimsIdentity(new List<Claim> { new Claim("NewClaim", "Emp123") })); И в компоненте Razor я пытаюсь получить его с помощью [CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; } и вызываю это в коде: var empId = (await authenticationStateTask).User.FindFirstValue("NewClaim"); я получаю empId как null. Что я здесь делаю неправильно?

Ash K 08.02.2023 19:14

Отредактировал мой вопрос, чтобы связать полный исходный код, показывающий мою попытку в соответствии с вашей инструкцией. Пожалуйста, посмотрите, если у вас есть время. Спасибо!

Ash K 08.02.2023 22:55

@AshK Извините, сначала я не тестировал, теперь у меня есть тест, и он работает, пожалуйста, проверьте последний ответ.

Jason Pan 09.02.2023 08:39

Большое спасибо за обновление, но у меня есть одна проблема с ним. Как передать adLookupResult.EmployeeId методу public async override Task<ClaimsPrincipal> CreateAsync(HMTUser user)? adLookupResult.EmployeeId не хранится ни в одной таблице идентификаторов и берется откуда-то еще.

Ash K 09.02.2023 17:34

Также похоже, что этот метод вызывается и во время выхода из системы. Хотя это не обязательно.

Ash K 09.02.2023 18:37

Разобрался с этим. Я разместил свой ответ ниже. Спасибо за помощь.

Ash K 10.02.2023 23:35
Ответ принят как подходящий

Наконец-то понял это, задав вопрос в репозитории aspnetcore github. https://github.com/dotnet/aspnetcore/issues/46558

Большое спасибо @Piotr Stola и @Jason Pan за их помощь!

Шаг 1:

В методе OnPostAsync в Login.cshtml.cs используйте _signInManager.SignInWithClaimsAsync вместо _signInManager.PasswordSignInAsync.

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl ??= Url.Content("~/");

    if (ModelState.IsValid)
    {
        // Step 1: Check if this user exists in our AD
        // If YES: Grab the Employee Id and go to next step
        // If NO: Terminate the process
        var adLookupResult = // Call Active Directory and get EmployeeId of this user here
        if (adLookupResult == null || string.IsNullOrEmpty(adLookupResult.EmployeeId))
        {
            ModelState.AddModelError(string.Empty, "User doesn't exist.");
            return Page();
        }

        // Step 2: Check if the user exists in our Identity database (AspNetUsers table)
        var user = await _signInManager.UserManager.FindByNameAsync(Input.Email);
        if (user == null)
        {
            ModelState.AddModelError(string.Empty, "User doesn't exist.");
            return Page();
        }
        
        // Step 3: Check the credentials of this user.
        var result = await _signInManager.CheckPasswordSignInAsync(user, Input.Password, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            // Access 'EmployeeId' from Areas/Identity/Components/TakeABreak.razor
            // For eg: Emp123 is adLookupResult.EmployeeId that I retrieved from Active Directory in Step 1.
            var customClaims = new[] { new Claim("EmployeeId", "Emp123") };
            await _signInManager.SignInWithClaimsAsync(user, Input.RememberMe, customClaims);
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

Шаг 2:

Доступ к этому из любого компонента Razor. Например: я обращаюсь к нему из компонента TakeABreak.razor (строка номер 15 на прикрепленном изображении).

Полный исходный код

https://github.com/affableashish/blazor-server-auth/tree/feature/AddClaimsDuringLogin

Другие вопросы по теме