身份验证后,如何向 ASP.Net Core 中的 OpenIdConnect 中间件生成的身份验证 cookie 添加自定义声明?

Har*_*aka 6 c# asp.net-core-mvc asp.net-core identityserver4

我有一个使用 IdentityServer4 Hybrid Auth Flow 的 ASP.Net Core 应用程序项目。设置如下,

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = "Cookies";
        options.DefaultChallengeScheme = "oidc";
    }).AddCookie("Cookies")
      .AddOpenIdConnect("oidc", options =>
      {   
          options.Authority = IdentityServerUrl;
          options.RequireHttpsMetadata = false;

          options.ClientId = ClientId;
          options.ClientSecret = ClientSecret;
          options.ResponseType = "code id_token";
          options.SaveTokens = true;

          options.GetClaimsFromUserInfoEndpoint = true;
          options.Scope.Add("openid");
          options.Scope.Add("profile");
          options.Scope.Add("email");
          options.Scope.Add("offline_access");
          options.Scope.Add("ApiAuthorizedBasedOnIdentity");
          options.GetClaimsFromUserInfoEndpoint = true;
          options.TokenValidationParameters.NameClaimType = JwtClaimTypes.Name;
          options.TokenValidationParameters.RoleClaimType = JwtClaimTypes.Role;                  
      });

    //Setup Tenant Role based authorization
    services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();

    services.AddProxy();
}
Run Code Online (Sandbox Code Playgroud)

我能够进行身份验证并且 SaveTokens=true 成功地将访问令牌保存在 ASP.Net 身份验证 cookie 中。现在,我需要在我的 ASP.Net Core 客户端项目的控制器操作(不是通过中间件)中向这个相同的身份验证 cookie 添加自定义声明。以 HomeController 的 Index 动作为例。

我还需要将此声明保留在身份验证 cookie 中,以便它可以跨请求和控制器操作保留。

我做了一些挖掘并注意到我可以用 ASP.Net Identity 做到这一点

if (User.Identity.IsAuthenticated)
{
    var claimsIdentity = ((ClaimsIdentity)User.Identity);
    if (!claimsIdentity.HasClaim(c => c.Type == "your-claim"))
    {
        ((ClaimsIdentity)User.Identity).AddClaim(new Claim("your-claim", "your-value"));

        var appUser = await userManager.GetUserAsync(User).ConfigureAwait(false);
        await signInManager.RefreshSignInAsync(appUser).ConfigureAwait(false);
    }
}
Run Code Online (Sandbox Code Playgroud)

身份验证由 IdentityServer 使用在该项目中设置的 ASP.Net Identity 完成。但是,要在客户端项目中使用 SignInManager、UserManager 等,我需要将 ASP.Net Identity 引入其中。还在客户端项目中设置 ASP.Net 身份和存储,只是用额外的声明更新身份验证 cookie 似乎有点矫枉过正。有没有其他方法可以做到这一点?

Kir*_*kin 11

您当然不需要在您的客户端项目中包含 ASP.NET Core Identity,但您可以使用它来获得灵感,了解如何实现您正在寻找的东西。让我们先看看 的实现RefreshSignInAsync

public virtual async Task RefreshSignInAsync(TUser user)
{
    var auth = await Context.AuthenticateAsync(IdentityConstants.ApplicationScheme);
    var authenticationMethod = auth?.Principal?.FindFirstValue(ClaimTypes.AuthenticationMethod);
    await SignInAsync(user, auth?.Properties, authenticationMethod);
}
Run Code Online (Sandbox Code Playgroud)

从上面可以看出,这也会调用 into SignInAsync,它看起来像这样:

public virtual async Task SignInAsync(TUser user, AuthenticationProperties authenticationProperties, string authenticationMethod = null)
{
    var userPrincipal = await CreateUserPrincipalAsync(user);
    // Review: should we guard against CreateUserPrincipal returning null?
    if (authenticationMethod != null)
    {
        userPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod));
    }
    await Context.SignInAsync(IdentityConstants.ApplicationScheme,
        userPrincipal,
        authenticationProperties ?? new AuthenticationProperties());
}
Run Code Online (Sandbox Code Playgroud)

我们最感兴趣的两个调用是:

  1. Context.AuthenticateAsync,它创建一个AuthenticateResult同时包含ClaimsPrincipalAuthenticationProperties从cookie中读取的。
  2. Context.SignInAsync,最终用 aClaimsPrincipal和相关联的AuthenticationProperties.

ASP.NET Core Identity 创建一个全新的ClaimsPrincipal,通常从数据库中获取,以便“刷新”它。您不需要这样做,因为您只是想使用现有 ClaimsPrincipal的附加声明。这是满足您要求的完整解决方案:

var authenticateResult = await HttpContext.AuthenticateAsync();

if (authenticateResult.Succeeded)
{
    var claimsIdentity = (ClaimsIdentity)authenticateResult.Principal.Identity;

    if (!claimsIdentity.HasClaim(c => c.Type == "your-claim"))
    {
        claimsIdentity.AddClaim(new Claim("your-claim", "your-value"));

        await HttpContext.SignInAsync(authenticateResult.Principal, authenticateResult.Properties);
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,要求HttpContext.AuthenticateAsync将使用你已经在你的配置设置默认方案("Cookies")以访问这两个ClaimsPrincipalAuthenticationProperties。之后,只需添加新声明并执行对 的调用HttpContext.SignInAsync,这也将使用默认方案 ( "Cookies")。