代码之家  ›  专栏  ›  技术社区  ›  Dave Smash

ASP。Net Core MVC标识-添加临时(会话)声明

  •  3
  • Dave Smash  · 技术社区  · 7 年前

    首先,这里也提出了这个问题( Temporary Session Based Claims in ASP.NET Core Identity )还有这里( How to add claim to user dynamically? ),但这两个问题都没有得到答案,所以我正在尝试恢复它。。。

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

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

    以下是我的登录操作(基于标准ASP.Net核心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);
    }
    

    所以我的问题是:为什么自定义不声明“粘滞”,以便在将来的控制器操作中可以看到它?如果这是标准行为,为什么不保存到数据库中?是否有方法在会话期间保持此值不变,而不使用AddSession()?如果我将此实现为一个声明,那么每次访问该值时是否都要往返数据库?

    1 回复  |  直到 7 年前
        1
  •  2
  •   Dave Smash    7 年前

    对于其他有这个问题的人,我现在的处境是。。。要将声明永久存储在数据库中并在登录时自动加载,必须使用以下命令,并且更改在下次登录之前不会生效:

    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.");
                ...
    

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