IdentityServer4和ASP.Net Identity:添加其他声明

时间:2018-12-18 17:12:24

标签: c# asp.net identityserver4

我正在尝试将IdentityServer4与提供用户存储的ASP.Net Identity Core一起使用。我大致遵循了本指南https://www.scottbrady91.com/Identity-Server/Getting-Started-with-IdentityServer-4,现在我可以使用ASP.Net Identity在本地注册和验证用户了。

我的问题是我对注册提出了一些要求:

var createUserResult = await _userManager.CreateAsync(user, model.Password);

if (createUserResult.Succeeded)
{
    var claims = new List<Claim>
    {
        new Claim(JwtClaimTypes.Email, user.Email),
        new Claim(JwtClaimTypes.Role, "user"),
        new Claim("test-claim", "loremipsum")
    };

    await _userManager.AddClaimsAsync(user, claims);

    await HttpContext.SignInAsync(user.Id, user.UserName, new AuthenticationProperties());
    return Redirect(model.ReturnUrl ?? "~/");
}

这些已正确保存,我可以在数据库中看到它们。但是,一旦我登录,该用户对象仅具有以下声明:

  • sub
  • 名称
  • auth_time
  • idp
  • amr

在线查看,看来我需要重写UserClaimsPrincipalFactory并使用DI容器注册此实现:

public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
    public CustomUserClaimsPrincipalFactory(
        UserManager<ApplicationUser> userManager,
        RoleManager<IdentityRole> roleManager,
        IOptions<IdentityOptions> options) : base(userManager, roleManager, options)
    {
        Console.WriteLine("Test");
    }

    public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
    {
        var principal = await base.CreateAsync(user);
        ((ClaimsIdentity) principal.Identity).AddClaim(new Claim("yolo", "swag"));

        return principal;
    }

    protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);
        identity.AddClaim(new Claim("yolo", "swag"));

        return identity;
    }
}

按以下方式在DI容器中注册:

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

我的理解是,此自定义主体工厂应将我想要的声明添加到User对象,但这不会发生。我可以看到自定义声明主体工厂是使用构造函数中的断点实例化的,但是从未调用GenerateClaimsAsync函数。

IdentityServer和ASP.Net身份注册如下:

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

services.AddIdentityServer(config =>
    {
        config.PublicOrigin = _configuration.PublicOriginUrl;
    })
    .AddOperationalStore(options =>
    {
        options.ConfigureDbContext = builder => builder.UseNpgsql(
            _sqlConnectionString,
            sqlOptions => sqlOptions.MigrationsAssembly(MigrationsAssembly));
    })
    .AddConfigurationStore(options =>
    {
        options.ConfigureDbContext = builder => builder.UseNpgsql(
            _sqlConnectionString,
            sqlOptions => sqlOptions.MigrationsAssembly(MigrationsAssembly));
    })
    .AddAspNetIdentity<ApplicationUser>()
    .AddDeveloperSigningCredential();

我的自定义用户声称主体工厂在此部分之后注册。看一下AddAspNetIdentity扩展方法here,我可以看到它似乎注册了某种自定义用户声明主体工厂,但是我对实现代码的理解不够充分,无法确切地了解正在发生的事情。

如何实现自定义用户声明主体工厂,以便可以在视图中执行类似的操作?

// "yolo" is a custom claim
@User.FindFirst("yolo")

2 个答案:

答案 0 :(得分:1)

我还实现了类似的功能来为用户设置版权声明。

Claim claim = new Claim(claimType, claimValue, ClaimValueTypes.String);
IdentityResult result = await userManager.AddClaimAsync(user, claim);

这会将特定用户的声明添加到AspNetUserClaims表中

enter image description here

Microsoft.AspNetCore.Identity.SignInManager 提供了内置的身份验证功能。

这还会在登录时自动为用户创建声明。 以下是使用 SignInManager

的示例登录
[HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Login(Login login)
    {
        if (ModelState.IsValid)
        {
            AppUser appUser = await userManager.FindByEmailAsync(login.Email);
            if (appUser != null)
            {
                await signInManager.SignOutAsync();
                Microsoft.AspNetCore.Identity.SignInResult result = await signInManager.PasswordSignInAsync(appUser, login.Password, false, false);
                if (result.Succeeded)
                    return Redirect(login.ReturnUrl ?? "/");
            }
            ModelState.AddModelError(nameof(login.Email), "Login Failed: Invalid Email or password");
        }
        return View(login);
    }

用户成功登录后,我们可以使用 Microsoft.AspNetCore.Mvc.Razor.RazorPageBase.User

访问视图中的所有声明

以下是检索所有声明的示例代码:

     <h2>Claim details</h2>
     <ul>
        @foreach (var claim in User.Claims)
        {
           <li><strong>@claim.Type</strong>: @claim.Value</li>
        }
     </ul>

我遵循了有关身份声明的教程,其中还包括管理自定义声明

  1. https://www.yogihosting.com/aspnet-core-identity-claims/
  2. https://code-maze.com/authentication-aspnet-core-identity/

答案 1 :(得分:0)

我解决了这一问题(至少以一种满足我要求的方式)。在quickstart项目中执行登录的逻辑如下:

...
if (user != null && await _userManager.CheckPasswordAsync(user, model.Password)) {
    await _events.RaiseAsync(
        new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName));


    AuthenticationProperties props = null;
    if (AccountOptions.AllowRememberLogin && model.RememberLogin) {
        props = new AuthenticationProperties {
            IsPersistent = true,
            ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
        };
    };

    await HttpContext.SignInAsync(user.Id, user.UserName, props);
    ...
}

通过查看IdentityServer4添加到HttpContext的extension methods,我发现有几个接受一系列声明。

我在帐户控制器中修改了登录逻辑:

...
var userClaims = await _userManager.GetClaimsAsync(user);
await HttpContext.SignInAsync(user.Id, user.UserName, props, userClaims.toArray());
...

这成功将用户的所有索赔从数据库添加到当前会话。从这里,我只能选择我想要的声明,然后传递这些声明的数组。

现在,所需的声明作为会话的一部分存储了,我可以根据需要从视图或其他控制器中调用@User.FindFirst("yolo")