Наконец-то разобрался. Ознакомьтесь с принятым ответом ниже.
Полный исходный код с моей попыткой по указанию @Jason Pan. https://github.com/affableashish/blazor-server-auth/tree/feature/AddClaimsDuringLogin
Добавлены утверждения во время входа в систему (в файле Login.cshtml.cs ) и доступ к этим утверждениям из Razor Component.
К сожалению, это не сработало. Я получаю только null
в качестве значения претензии. 😔
Я видел несколько похожих вопросов, таких как это и это, но они не помогли для моего сценария.
Мое приложение представляет собой проект Blazor Server, в который я добавил Identity, выполнив шаги, упомянутые здесь.
Теперь это то, чего я хочу достичь:
EmployeeId
, из Active Directory.SignInManager.PasswordSignInAsync
.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; }
}
}
Предыдущее решение может быть обходным путем, если оно будет помещено в промежуточное программное обеспечение. Это очень ужасно.
Мы должны использовать 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);
}
Отредактировал мой вопрос, чтобы связать полный исходный код, показывающий мою попытку в соответствии с вашей инструкцией. Пожалуйста, посмотрите, если у вас есть время. Спасибо!
@AshK Извините, сначала я не тестировал, теперь у меня есть тест, и он работает, пожалуйста, проверьте последний ответ.
Большое спасибо за обновление, но у меня есть одна проблема с ним. Как передать adLookupResult.EmployeeId
методу public async override Task<ClaimsPrincipal> CreateAsync(HMTUser user)
? adLookupResult.EmployeeId
не хранится ни в одной таблице идентификаторов и берется откуда-то еще.
Также похоже, что этот метод вызывается и во время выхода из системы. Хотя это не обязательно.
Разобрался с этим. Я разместил свой ответ ниже. Спасибо за помощь.
Наконец-то понял это, задав вопрос в репозитории aspnetcore github. https://github.com/dotnet/aspnetcore/issues/46558
Большое спасибо @Piotr Stola и @Jason Pan за их помощь!
В методе 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();
}
Доступ к этому из любого компонента Razor. Например: я обращаюсь к нему из компонента TakeABreak.razor
(строка номер 15 на прикрепленном изображении).
https://github.com/affableashish/blazor-server-auth/tree/feature/AddClaimsDuringLogin
Я сделал это, но при получении претензии получаю нулевое значение. В
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
. Что я здесь делаю неправильно?