ASP.Net核心MVC身份 - 添加临时(会话)声明

时间:2018-03-30 17:10:05

标签: c# asp.net-mvc asp.net-core-mvc asp.net-identity

对于初学者来说,这里也问了这个问题(Temporary Session Based Claims in ASP.NET Core Identity)和这里(How to add claim to user dynamically?),但这两个问题都没有得到回答,所以我试图让它复活......

我正在创建一个多租户网络应用。用户登录,他们可以看到与他们自己公司相关的数据(但不能查看使用该应用程序的其他公司的数据)。由于具有多个店面等的特许经营集团,单个用户要求访问多个不同公司的情况并不少见,但在这种情况下,他们必须在登录时选择一家公司。

几乎所有数据查询都需要公司ID作为参数,因此我需要一种方便的方法来检查用户当前登录的公司。如果他们退出并登录到其他公司,我需要查看不同的公司ID。我希望将其存储为我在会话期间可以看到的身份声明,但我不一定要将其存储到数据库,因为它可能会随着每次登录而改变。

这是我的登录操作(基于标准的ASP.Net Core MVC模板):

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
    ViewData["ReturnUrl"] = returnUrl;
    if (ModelState.IsValid)
    {
        var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            // BEGIN CUSTOM CODE - Check which companies a user can access
            IEnumerable<int> allCompanies = _myDbService.GetUserCompanies(model.Email);

            if (allCompanies.Count() == 1)
            {
                // then they can only access one company - log them into it automatically.
                // I need easy access to its ID with the User since it is used with almost every db query.

                int companyID = allCompanies[0];
                ((ClaimsIdentity)User.Identity).AddClaim(new Claim("CompanyID", companyID.ToString())); // this shows as null on future controller actions.

                Debug.WriteLine("Set breakpoint here to examine User"); // I see 1 claim (my custom claim) in the debugger, but it is not in the database yet.

                // future controller actions show me 3 claims (nameidentifier, name, and security stamp) but User.Claims.First(c => c.Type == "CompanyID").Value throws error.

                return RedirectToLocal(returnUrl);
            }
            else
            {
                // the user has access to several companies - make them choose one to complete login process
                RedirectToAction("ChooseCompany", new { companyList = allCompanies });
            }
            // END CUSTOM CODE
        }

        // REMAINING CODE OMITTED FOR BREVITY
    }

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

所以我的问题是:为什么没有自定义声明&#34;坚持&#34;这样可以在将来的控制器动作中看到它?如果这是标准行为,为什么不将其保存到数据库中?有没有办法在不使用AddSession()的情况下在会话期间保持此值?如果我将其作为索赔实现,那么每次访问该值时,我是否都会到数据库进行一次往返?

1 个答案:

答案 0 :(得分:1)

对于有此问题的其他人,此处是我到目前为止...要将声明永久存储在数据库中并在登录时自动加载,您必须使用以下内容,并且更改将在下次登录后生效:

await userManager.AddClaimAsync(user, new Claim("your-claim", "your-value"));

使用以下内容只会存储当前会话的声明,因此我在正确的轨道上:

((ClaimsIdentity)User.Identity).AddClaim(new Claim("your-claim", "your-value"));

我发现在控制器操作之间“坚持”声称的唯一方法是覆盖UserClaimsPrincipalFactory,以便ASP.Net使用您的自定义代码作为登录过程的一部分。这里的问题是您只能在相关方法中访问ApplicationUser,并且您无法真正传递任何自定义参数AFAIK。所以我首先修改了ApplicationUser类,如下所示:

public class ApplicationUser : IdentityUser
{
    public long ActiveDealershipID { get; set; }
} 

(您必须应用EF迁移)。现在我创建一个派生自UserClaimsPrincipalFactory的类,如下所示:

public class MyClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
    public MyClaimsPrincipalFactory(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> options) : base(userManager, roleManager, options)
    {
    }

    public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
    {
        var principal = await base.CreateAsync(user);

        long dealershipID = user.ActiveDealershipID;
        ((ClaimsIdentity)principal.Identity).AddClaim(new Claim("DealershipID", dealershipID.ToString()));

        return principal;
    }
}

(您必须在startup.cs ConfigureServices中注册该服务):

services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, MyClaimsPrincipalFactory>();

然后在我的登录操作中,我在登录前设置了新的用户属性,以便在设置声明时UserClaimsPrincipalFactory可以使用它:

public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
    ViewData["ReturnUrl"] = returnUrl;
    if (ModelState.IsValid)
    {
        ApplicationUser user = await _userManager.FindByEmailAsync(model.Email);
        user.ActiveDealershipID = model.ChosenDealership
        await _userManager.UpdateAsync(user);

        var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            _logger.LogInformation("User logged in.");
            ...

我仍然愿意接受更好的建议或其他想法,但这似乎对我有用。