为什么在登录操作中添加声明后,无法在其他控制器中访问?

zmo*_*edi 2 asp.net-mvc login claims asp.net-identity asp.net-identity-2

我正在使用ASP.NET MVC 5和Identity 2与Entity Framework 6的系统.当用户登录时,我向该登录会话添加一些声明.我不想使用索赔表.

对于我的一个主张,我确实喜欢这样:

public class User : IdentityUser<int, UserLogin, UserRole, UserClaim>
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User, int> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

        //We add the display name so that the _LoginPartial can pick it up;
        userIdentity.AddClaim(new Claim("DisplayName", FirstName + " " + LastName));

        // Add custom user claims here
        return userIdentity;
    }
    public virtual ICollection<UserInsurance> UserInsurances { get; set; }
    public User()
    {
        UserInsurances = new List<UserInsurance>();
    }
}
Run Code Online (Sandbox Code Playgroud)

并且为了获得索赔:

var claimsIdentity = User.Identity as System.Security.Claims.ClaimsIdentity;
var displayNameClaim = claimsIdentity != null
    ? claimsIdentity.Claims.SingleOrDefault(x => x.Type == "DisplayName")
    : null;
var nameToDisplay = displayNameClaim == null ? User.Identity.Name : displayNameClaim.Value;
Run Code Online (Sandbox Code Playgroud)

这很好用.但问题是当我需要一个不在User表中的字段时.实际上,它是用户导航属性(UserInsurances)中的一条记录,我需要一个linq查询来访问它.

var lastUserInsurance = UserInsurances.OrderByDescending(x => x.CompanyInsuranceId).First();
userIdentity.AddClaim(new Claim("CompanyInsuranceId", lastUserInsurance.CompanyInsuranceId.ToString()));
Run Code Online (Sandbox Code Playgroud)

如果我将此代码放在GenerateUserIdentityAsync方法(如"DisplayName")中,则UserInsurances为null.所以我应该将此代码添加到登录操作并在用户成功登录后添加.但我试过了,它不起作用.我不知道为什么,但是当我想要访问该声明时,它就不存在了.

public virtual async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }
        var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);

        switch (result)
        {
            case SignInStatus.Success:
                var user = _user.Include(x => x.UserInsurances).FirstOrDefault(x => x.NationalCode == model.UserName);
                var identity = await SignInManager.CreateUserIdentityAsync(user);
                var lastUserInsurance = user.UserInsurances.OrderByDescending(x => x.CompanyInsuranceId).FirstOrDefault();
                identity.AddClaim(new Claim("CompanyInsuranceId", lastUserInsurance.CompanyInsuranceId.ToString()));
                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:
                return View(model);
        }
    }
Run Code Online (Sandbox Code Playgroud)

任何人都可以告诉我为什么我无法访问此声明并且它不存在?我不知道如何实现此方案并在我的应用程序的所有部分中访问"CompanyInsuranceId"声明.

Sam*_*ari 6

您必须在签署用户之前添加您的声明.因此,如果由于任何原因您无法填写您的声明GenerateUserIdentityAsync方法.只需Identity在登录操作方法中生成对象,然后登录即可.考虑这个例子:

public async Task<ActionResult> Login(LoginViewModel model,string returnUrl)
{
    var user = UserManager.Find(model.Email, model.Password);
    // now you have the user object do what you to gather claims

    if(user!=null)
    {
        var ident = UserManager.CreateIdentity(user, 
            DefaultAuthenticationTypes.ApplicationCookie);
            ident.AddClaims(new[] {
                new Claim("MyClaimName","MyClaimValue"),
                new Claim("YetAnotherClaim","YetAnotherValue"),
        });
        AuthenticationManager.SignIn(
            new AuthenticationProperties() { IsPersistent = true }, 
            ident);
        return RedirectToLocal(returnUrl);
    }
    ModelState.AddModelError("", "Invalid login attempt.");
    return View(model);
} 
Run Code Online (Sandbox Code Playgroud)

如您所见,您可以执行任何想要收集声明并填写身份的内容,然后登录用户.

但是如果你想使用SignInManager.PasswordSignInAsync()方法只是简单地覆盖SignInManager.CreateUserIdentityAsync()方法,那么你可以生成所需的声明.例如,如果你需要DbContext为你的喂养要求简单,你可以取注入额外的信息DbContextSignInManager,并在使用CreateUserIdentityAsync()这样的方法:

public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
    private readonly ApplicationDbContext _context;

    public ApplicationSignInManager(
        ApplicationUserManager userManager, 
        IAuthenticationManager authenticationManager,
        ApplicationDbContext context)
        : base(userManager, authenticationManager)
    {
        _context=context;
    }

    public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
    {
        var companyInsuranceId=_context.Users
            .Where(u=>u.NationalCode == user.UserName)
            .Select(u=>u.UserInsurances
                .OrderByDescending(x => x.CompanyInsuranceId)
                .Select(x=>x.CompanyInsuranceId)
                .FirstOrDefault())
            .FirstOrDefault();

        var ident=user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        ident.AddClaim(new Claim("CompanyInsuranceId",
            companyInsuranceId.ToString()));
        return ident;
    }

    public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
    {
        return new ApplicationSignInManager(
            context.GetUserManager<ApplicationUserManager>(),
            context.Authentication,
            context.Get<ApplicationDbContext>());
    }
}
Run Code Online (Sandbox Code Playgroud)

现在只是写作

var result = await SignInManager.PasswordSignInAsync(
    model.UserName, model.Password, 
    model.RememberMe, shouldLockout: false);
Run Code Online (Sandbox Code Playgroud)

您可以签署用户并注入其他声明.