在成功登录后添加声明

14

用户成功登录后,我需要向用户身份添加一个声明。这是我认为应该进行此操作的位置:

public async Task<ActionResult> Login(LoginViewModel model, string returnUrl, string myClaimValue)
{
   if (!ModelState.IsValid)
   {
      return View(model);
   }

   var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
   switch (result)
   {
      case SignInStatus.Success:
         UserManager.AddClaim(User.Identity.GetUserId(), new Claim("MyClaim", myClaimValue));
         return RedirectToLocal(returnUrl);
      case SignInStatus.LockedOut:
         return View("Lockout");
      case SignInStatus.RequiresVerification:
         return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
      case SignInStatus.Failure:
      default:
         ModelState.AddModelError("", "Invalid login attempt.");
         return View(model);
   }
}

我认为这是正确的方法,但调用User.Identity.GetUserId()会抛出异常。看起来User.Identity没有被成功登录更新。鉴于这个事实,我最好的方式是什么,以便我可以添加声明并获取新登录用户的ID?

或者我做错了吗?


@John Saunders 谢谢!非常清晰易懂。 - itslittlejohn
越想越觉得我做错了。在我看来,声明是用于识别用户的东西。我的目标是存储一些关于用户的真实数据,这些数据在客户端登录时计算。这些数据不应该存储在数据库中,而应该与已登录的用户相关联,只要他保持登录状态即可。 - itslittlejohn
声明不仅仅是用于识别用户的。它们是用户断言为真的一组“命题”。例如,“我在以下角色集合中”是一个合理的声明。 - John Saunders
@JohnSaunders 感谢您的解释。这很有道理,我认为这证明了我尝试使用它们的方式是合理的。 - itslittlejohn
3个回答

5

这将把声明存储到数据库中 UserManager.AddClaim(User.Identity.GetUserId(), new Claim("MyClaim", myClaimValue));

如果您想在用户登录时将声明与已登录用户关联,您必须重写SignInManagerSignInAsync方法。

public override async Task SignInAsync(ApplicationUser user, bool isPersistent, bool rememberBrowser) { var userIdentity = await CreateUserIdentityAsync(user); // your code here userIdentity.AddClaim(new Claim(ClaimTypes.Gender, "male")); // AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.TwoFactorCookie); if (rememberBrowser) { var rememberBrowserIdentpublic override async Task SignInAsync(ApplicationUser user, bool isPersistent, bool rememberBrowser)
{
    var userIdentity = await CreateUserIdentityAsync(user);

    // add your claims here
    userIdentity.AddClaim(new Claim(ClaimTypes.Gender, "male"));
    //

    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.TwoFactorCookie);
    if (rememberBrowser)
    {
        var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id));
        AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity, rememberBrowserIdentity);
    }
    else
    {
        AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity);
    }
}

我明白了,谢谢。问题在于当我尝试在其他控制器中访问该声明时,它不存在。此外,当我像您上面所做的那样创建一个新的ClaimIdentity时,User.Identity.IsAuthenticated为false。我不确定问题是User.Identity似乎没有完全初始化,还是我创建了这个新标识后没有对其进行任何操作。 - itslittlejohn
@itslittlejohn,你说的不起作用,正在更新答案。 - tmg
谢谢更新。我会尽快测试它。 - itslittlejohn
我最终只使用了会话变量。这仍然不起作用,因为我的声明值是从登录请求动态传入的。因为使用声明似乎很困难,所以我认为这是错误的方法。如果我错了,请纠正我。 - itslittlejohn

5

在触发SignInManager.PasswordSignInAsync之前,必须设置其他声明。可以通过自定义的ClaimsIdentityFactory完成此操作:

public class ApplicationClaimsIdentityFactory : ClaimsIdentityFactory<ApplicationUser>
{
    // This claim value is taken from Login View
    public static readonly string MyClaimKey = "app:MyClaimKey";
    public string MyClaimValue { get; set; }

    public async override Task<ClaimsIdentity> CreateAsync(UserManager<ApplicationUser, string> manager, ApplicationUser user, string authenticationType)
    {
        var identity = await base.CreateAsync(manager, user, authenticationType);
        identity.AddClaim(new Claim(MyClaimKey, MyClaimValue));
        return identity;
    }
}

在登录步骤之前应用此工厂:

UserManager.ClaimsIdentityFactory = new ApplicationClaimsIdentityFactory() 
        { 
            MyClaimValue = loginModel.MyClaimValue
        };
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);

问题不是关于如何在成功登录后添加声明吗?回答声称“必须在”登录之前“设置声明,却没有提供任何支持该说法的内容,对阅读答案的人并不真正有帮助。 - Mladen B.
1
@MladenB. 在登录之前,您需要为signinmanager设置claims factory,以便在登录发生时使用您准备好的factory。您是正确的,这与问题有关,但不是问题的答案。 - Paulo Neves

1
我做了以下操作,成功了:
  1. 使用用户名获取用户
  2. 使用SignInManager.CheckPasswordSignInAsync()检查密码
  3. 添加自定义声明并使用SignInManager.SignInWithClaimsAsync()进行登录

这只在.NET Core中可用,而在.NET Framework中不可用。 - fletchsod

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接