繁体   English   中英

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

[英]How do I add a custom claim to authentication cookie generated by OpenIdConnect middleware in ASP.Net Core after authentication?

我有一个使用IdentityServer4混合身份验证流的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();
}

我能够进行身份验证,并且SaveTokens = true可以成功将访问令牌保存在ASP.Net身份验证cookie中。 现在,我需要从ASP.Net Core客户端项目中的Controller Action( 不是通过中间件 )中向同一身份验证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);
    }
}

身份验证由IdentityServer使用该项目中设置的ASP.Net Identity完成。 但是,要在客户端项目中使用SignInManager,UserManager等,我需要将ASP.Net Identity引入其中。 设置ASP.Net身份并将其存储在客户端项目中,只是为了通过附加声明更新身份验证cookie似乎有些过头。 还有其他方法吗?

您当然不需要在客户端项目中包括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);
}

从上面可以看出,这也调用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());
}

我们最感兴趣的两个电话是:

  1. Context.AuthenticateAsync ,它创建一个AuthenticateResult ,其中包含从cookie读取的ClaimsPrincipalAuthenticationProperties
  2. Context.SignInAsync ,最终使用ClaimsPrincipal和关联的AuthenticationProperties重写cookie。

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);
    }
}

HttpContext.AuthenticateAsync的调用将使用您已经在配置中设置的默认方案( "Cookies" )来访问ClaimsPrincipalAuthenticationProperties 之后,只是添加新声明并执行对HttpContext.SignInAsync的调用的一种情况,该调用还将使用默认方案( "Cookies" )。

如果您的项目中没有UserStore,而我只是在使用UserManager,则可以通过继承UserManager并重写GetClaimsAsync方法轻松完成此操作

    public override Task<IList<Claim>> GetClaimsAsync(T user)
        {
            // here you can return a list of claims and it will be in the cookie
            return base.GetClaimsAsync(user);
        }

顺便说一下,base.GetClaimsAsync(user)的默认实现; 就是调用相关的UserStore。 因此,如果您没有UserManager的UserStore,则可以将其删除。

选项1

您将需要使用HttpContext.SignInAsync方法。 另外,一旦您向用户添加了其他声明,就需要使用新的ClaimsPrincipal更新HttpContext.User 请参见下面的代码:

var identity = (ClaimsIdentity)User.Identity;
identity.AddClaim(new Claim("your-claim", "your-value"));

// genereate the new ClaimsPrincipal
var claimsPrincipal = new ClaimsPrincipal(identity);

// store the original tokens in the AuthenticationProperties
var props = new AuthenticationProperties();

// get the current tokens
var accessToken = await HttpContext.GetTokenAsync("access_token");
var refreshToken = await HttpContext.GetTokenAsync("refresh_token");

// create the enumerable list
var tokens = new List<AuthenticationToken>
{
    new AuthenticationToken {Name = "access_token", Value = accessToken},
    new AuthenticationToken {Name = "refresh_token", Value = refreshToken}
};

//store the tokens
props.StoreTokens(tokens);

// update the thread's current principal as it is changed, otherwise 
// System.Security.Claims.ClaimsPrincipal.Current is referring to the 
// ClaimsPrincipal created from the cookie on the initial request. This is required 
// so that the next instance of HttpContext will be injected with the updated claims
HttpContext.User = claimsPrincipal;
Thread.CurrentPrincipal = claimsPrincipal;

// sign in using the built-in Authentication Manager and ClaimsPrincipal
// this will create a cookie as defined in CookieAuthentication middleware
await HttpContext.SignInAsync("your scheme", claimsPrincipal, props);

请确保用您使用的方案名称替换“您的方案”。 希望对您有所帮助。

选项2

@Ruard van Elburg正​​确指出,上述解决方案将覆盖访问令牌。 已更新以允许存储原始令牌

如果您是在用户OnTokenValidated后立即添加声明,则可以使用OnTokenValidated事件

.AddOpenIdConnect("oidc", options =>
{
    options.Events = new OpenIdConnectEvents
    {
        OnTokenValidated = async ctx =>
        {
            var claim = new Claim("your-claim", "your-value");

            var identity = new ClaimsIdentity(new[] { claim });

            ctx.Principal.AddIdentity(identity);

            await Task.CompletedTask;
        }
    };
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM