用户似乎没有在 dotnet core 2.0 中的“Use”管道中进行身份验证

User doesn't seem authenticated in the pipline inside`Use` in dotnetcore 2.0

我正在尝试向 Serilog 提供 ActiveUser 属性。
不幸的是,我似乎无法找到正确的位置来检查当前用户。

在下面的代码中 httpContext.User.Identity.IsAuthenticated 总是 false?

但仅当使用不记名令牌登录时

当不记名令牌被验证时,用户不是立即设置?

项目是aspnetcore 2.0

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{

    ... (other configuration items)

    app.UseIdentityServer();
    app.UseAuthentication();

    app.Use(async (httpContext, next) =>
    {
        // HERE IsAuthenticated IS ALWAYS FALSE
        // HERE THE CLAIMS ARE ALWAYS EMPTY, UNLESS
        // I LOGIN USING THE COOKIE AS WELL - THEN IT WORKS
        var userName = httpContext.User.Identity.IsAuthenticated 
            ? httpContext.User.GetClaim("name")
            : "(unknown)";
        LogContext.PushProperty(
            "ActiveUser",
            !string.IsNullOrWhiteSpace(userName)
                 ? userName
                 : "(unknown)");
        await next.Invoke();
    });

    app.UseMvc(
        routes =>
        {
            routes.MapRoute(
                "default",
                "{controller=Home}/{action=Index}/{id?}");
        });

在我的控制器方法中,用户设置正确,并且经过身份验证。

[Authorize]
[HttpGet("user")]
public object UserDetail()
{
    // HERE THE CLAIMS ARE SET, IsAuthenticated IS ALWAYS TRUE
    // AS THE USER MUST BE AUTHENTICATED TO GET HERE
    Debug.Assert(this.User.Identity.IsAuthenticated == true)

编辑
进一步深入研究这个问题,似乎在我的中间件已经执行之后验证了 JWTBearer 令牌。中间件需要在验证令牌后执行。

TL;DR
(完整配置)

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();
    app.UseIdentityServer();
    app.UseAuthentication();
    app.Use(async (httpContext, next) =>
                    {
                        var userName = httpContext.User.Identity.IsAuthenticated 
                        ? httpContext.User.GetClaim("email")
                        : "(unknown)";
                        LogContext.PushProperty("ActiveUser", !string.IsNullOrWhiteSpace(userName) ? userName : "(unknown)");
                        await next.Invoke();
                    });

    app.UseMvc(
        routes =>
        {
            routes.MapRoute(
                "default",
                "{controller=Home}/{action=Index}/{id?}");
        });

}

(更多配置)

   public void ConfigureServices(IServiceCollection services)
   {
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        services.AddAuthentication()
            .AddOpenIdConnect(
                o =>
                {
                    o.Authority = "https://localhost:44319";
                    o.ClientId = "api";
                    o.ClientSecret = "secret";
                    o.RequireHttpsMetadata = false;
                    o.ResponseType = "code id_token token";
                    o.GetClaimsFromUserInfoEndpoint = true;
                })
            .AddJwtBearer(
                o =>
                {
                    o.Authority = "https://localhost:44319";
                    o.Audience = "api";
                    o.RequireHttpsMetadata = false;
                    //o.SaveToken = true;
                });

        services.AddMemoryCache();
        services.AddIdentity<ApplicationUser, ApplicationRole>(
                x =>
                {
                    x.Password.RequireNonAlphanumeric = false;
                    x.Password.RequireUppercase = false;
                })
            .AddEntityFrameworkStores<FormWorkxContext>()
            .AddDefaultTokenProviders()
            .AddIdentityServer();

        // NB
        services.Configure<IdentityOptions>(
            options =>
            {
                options.ClaimsIdentity.RoleClaimType = ClaimTypes.Role;
                options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name;
            });

        services.ConfigureApplicationCookie(
            options =>
            {
                options.LoginPath = "/login";
                options.LogoutPath = "/logout";
                options.Events.OnRedirectToLogin = this.ProcessStatusCodeResponse;
            });

        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryApiResources(Config.GetApis())
            .AddInMemoryClients(Config.GetClients())
            .AddAspNetIdentity<ApplicationUser>();

        services.AddTransient<IEmailSender, EmailSender>();

        services.AddMvc(
                _ =>
                {
                    _.Filters.Add(
                        new AuthorizeFilter(
                            new AuthorizationPolicyBuilder(
                                    JwtBearerDefaults.AuthenticationScheme,
                                    IdentityConstants.ApplicationScheme)
                                .RequireAuthenticatedUser()
                                .Build()));
                    _.Filters.Add(new ExceptionFilter());
                    _.ModelBinderProviders.Insert(0, new PartyModelBinderProvider());
                    _.ModelBinderProviders.Insert(0, new DbGeographyModelBinder());
                    _.ModelMetadataDetailsProviders.Add(new KeyTypeModelMetadataProvider());
                })
            .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>())
            .AddJsonOptions(json => json.SerializerSettings.Converters.Add(new DbGeographyJsonConverter()));
    }

我在使用如下设置的主体登录时重现了这个问题:

var principal = new ClaimsPrincipal(new ClaimsIdentity(claims));

然后我使用 SignInAsync 登录。这也导致 User.Identity.Name 有一个值,但 User.Identity.IsAuthenticated 没有被设置为 true。

现在,当我像这样将 authenticationType 参数添加到 ClaimsIdentity 时:

var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "local"));

IsAuthenticated 现在设置为 true。

我不完全确定您的登录将如何工作,您可以在某处提及此 authenticationType 或者您可以在创建 JWT 时传递它。我就是这样做的。

更新 好吧,刚刚注意到您对名称的评论也没有显示,但您仍然可以尝试设置身份验证类型。另外只要你的主张是正确的,你应该能够使用 AuthenticateAsync 提取原理。一旦您可以从 Context.User 对象访问主体,您始终可以自定义身份验证方案以强制主体。

更新 2 在你的情况下,在你的 AddJwtBearer 中,尝试包括这个:

o.Events.OnTokenValidated = async (context) => {
    context.Principal = new ClaimsPrincipal(new ClaimsIdentity(context.Principal.Claims, "local"));
};

正在复制 以防有人遇到此问题并想知道发生了什么:

Since you have multiple authentication schemes registered and none is the default, authentication does not happen automatically as the request goes through the pipeline. That's why the HttpContext.User was empty/unauthenticated when it went through your custom middleware. In this "passive" mode, the authentication scheme won't be invoked until it is requested. In your example, this happens when the request passes through your AuthorizeFilter. This triggers the JWT authentication handler, which validates the token, authenticates and sets the Identity, etc. That's why the User is populated correctly by the time it gets to your controller action.

通过添加以下代码行在自定义中间件中显式验证用户:

var result = await context.Request.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);//AuthenticationOptions.DefaultAuthenticateScheme)
            if (result.Succeeded)
            {
                //context.User.AddIdentity(result.Principal);
                context.User = result.Principal;
            }