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

时间:2019-05-02 10:18:47

标签: c# asp.net-core asp.net-core-mvc identityserver4

我有一个使用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似乎有些过头。还有其他方法吗?

3 个答案:

答案 0 :(得分:2)

您当然不需要在客户端项目中包括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"的调用将使用您已在配置(ClaimsPrincipal)中设置的默认方案来同时访问AuthenticationPropertiesHttpContext.SignInAsync 。之后,仅是添加新声明并执行对"Cookies"的调用的情况,该调用还将使用默认方案(library(dplyr) library(fuzzywuzzyR) set.seed(42) rm(list = ls()) options(scipen = 999) init = FuzzMatcher$new() data <- data.frame(string = c("hello world", "hello vorld", "hello world 1", "hello world", "hello world hello world")) data$string <- as.character(data$string) distance_function <- function(string_1, string_2) { init$Token_set_ratio(string1 = string_1, string2 = string_2) } combinations <- combn(nrow(data), 2) distances <- matrix(, nrow = 1, ncol = ncol(combinations)) distance_matrix <- matrix(NA, nrow = nrow(data), ncol = nrow(data), dimnames = list(data$string, data$string)) for (i in 1:ncol(combinations)) { distance <- distance_function(data[combinations[1, i], 1], data[combinations[2, i], 1]) #print(data[combinations[1, i], 1]) #print(data[combinations[2, i], 1]) #print(distance) distance_matrix[combinations[1, i], combinations[2, i]] <- distance distance_matrix[combinations[2, i], combinations[1, i]] <- distance } distance_matrix )。

答案 1 :(得分:0)

如果您的项目中没有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,则可以将其删除。

答案 2 :(得分:-1)

选项1

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

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事件

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