代码之家  ›  专栏  ›  技术社区  ›  Zane Claes

Identity Server 4令牌中缺少UserID子声明,导致UserManager。让用户失败

  •  0
  • Zane Claes  · 技术社区  · 6 年前

    我正在用Identity Server 4和dotnetcore 3.1构建React SPA。Identity Server客户端在中定义 appsettings.json 使用 IdentityServerSPA 轮廓使用 oidc-client 在前端,我可以成功登录。检查 oidc 对象,我可以确认:

    • 令牌类型: Bearer
    • 范围: openid profile OpenWorkShopAPI
    • 轮廓sub:(我的用户ID)
    • 轮廓姓名:(我的用户名)

    如果我随后尝试调用API(使用 Authorization: Bearer 作为 id_token 或者 access_token ),没有 sub 提出索赔。因此 _userManager.GetUserAsync(User); 失败。

    列举并打印索赔,我看到:

    Claim: "System.Security.Claims.ClaimsIdentity" "nbf" "1601597732"
    Claim: "System.Security.Claims.ClaimsIdentity" "exp" "1601598032"
    Claim: "System.Security.Claims.ClaimsIdentity" "iss" "http://dev.openwork.shop:5000"
    Claim: "System.Security.Claims.ClaimsIdentity" "aud" "OpenWorkShopAPI"
    Claim: "System.Security.Claims.ClaimsIdentity" "iat" "1601597732"
    Claim: "System.Security.Claims.ClaimsIdentity" "at_hash" "MoAqNfND0ct1mUFKpUtgcg"
    Claim: "System.Security.Claims.ClaimsIdentity" "s_hash" "D81HZF_ii2r0i5-4_ZxnLA"
    Claim: "System.Security.Claims.ClaimsIdentity" "sid" "cdMH0nFKdikldL1Gy3S3Eg"
    Claim: "System.Security.Claims.ClaimsIdentity" "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" "fdf0023d-7ae9-4aaf-87fe-0f320b869171"
    Claim: "System.Security.Claims.ClaimsIdentity" "auth_time" "1601582136"
    Claim: "System.Security.Claims.ClaimsIdentity" "http://schemas.microsoft.com/identity/claims/identityprovider" "local"
    Claim: "System.Security.Claims.ClaimsIdentity" "http://schemas.microsoft.com/claims/authnmethodsreferences" "pwd"
    

    基于此,我可以通过访问名称声明直接查找ID来解决这个问题:

    string CurrentUserId => User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    

    但这似乎既不正确也不理想。

    我注意到我所有的MySQL表(除了 AspNetUsers )保持空着。这里面没有价值 AspNetUserClaims 例如然而,正如我在上面展示的那样 oidc客户端 能够成功地看到 附属的 声明用户ID。

    因为我用的是 DefaultIdentity 和 AddApiAuthorization ,我不希望需要添加任何特殊的逻辑来实现这些子声明,也不确定为什么API调用中缺少它。

    配置:

    services.AddDefaultIdentity<UserProfile>(options => {
            options.SignIn.RequireConfirmedAccount = true;
            options.SignIn.RequireConfirmedEmail = true;
            options.Stores.MaxLengthForKeys = 64;
          }).AddEntityFrameworkStores<OWSData>();
    
          WebHostOptions webHost = configuration.GetSection("WebHost").Get<WebHostOptions>();
          string root = webHost.Url;
    
          services.AddIdentityServer((options) => {
            options.PublicOrigin = root;
            options.IssuerUri = root;
            options.UserInteraction.LoginUrl = $"{root}/account/login";
            options.UserInteraction.LogoutUrl = $"{root}/account/logout";
            options.UserInteraction.ErrorUrl = $"{root}/account/error";
            options.UserInteraction.ConsentUrl = $"{root}/account/terms-of-service";
            options.UserInteraction.DeviceVerificationUrl = $"{root}/account/device-verification";
          }).AddApiAuthorization<UserProfile, OWSData>(options => {
            // Serilog.Log.Information("Clients: {@clients}", options.Clients);
          });
          services.AddScoped<ICurrentUser, CurrentUser>();
          services.AddTransient<IReturnUrlParser, AuthReturnUrl>();
    
          // Auth (JWT)
          services.AddAuthentication(options => {
                     // options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                   })
                  .AddCookie((options) => {
                     webHost.ConfigureCookie(CookieName, options.Cookie);
                     options.LoginPath = "/account/login";
                     options.AccessDeniedPath = "/account/denied";
                     options.LogoutPath = "/account/logout";
                     options.SlidingExpiration = true;
                   })
                  .AddIdentityServerJwt()
                  .AddGoogle(options => {
                     ConfigureOAuth(options, "Google", configuration, webHost);
                   })
                  .AddGitHub(options => {
                     ConfigureOAuth(options, "GitHub", configuration, webHost);
                     options.Scope.Add("user:email");
                     options.EnterpriseDomain = configuration["GitHub:EnterpriseDomain"];
                   });
    
          services.AddAuthorization(options => {
            // options.DefaultPolicy = new AuthorizationPolicy();
          });
    
          // Auth (Password)
          services.Configure<IdentityOptions>(options => {
            //Password settings
            options.Password.RequireDigit = true;
            options.Password.RequireLowercase = true;
            options.Password.RequireNonAlphanumeric = true;
            options.Password.RequireUppercase = true;
            options.Password.RequiredLength = 6;
            options.Password.RequiredUniqueChars = 1;
    
            //Lockout settings
            options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
            options.Lockout.MaxFailedAccessAttempts = 5;
            options.Lockout.AllowedForNewUsers = true;
    
            //User settings
            options.User.AllowedUserNameCharacters =
              "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@";
            options.User.RequireUniqueEmail = false;
          });
    
          // Cookies
          services.ConfigureApplicationCookie(options => {
            webHost.ConfigureCookie(CookieName, options.Cookie);
          });
    
    0 回复  |  直到 6 年前
        1
  •  3
  •   nahidf tgooch327    6 年前

    这个 JwtSecurityTokenHandler 在…上 JwtBearerAuthentication 中间件在默认情况下映射了一些声明。沿着这些映射, sub 索赔被映射到 ClaimTypes.NameIdentifier 。将列出映射 here .

    您可以更改API上的代码,将名称标识符声明设置为 NameClaimType . 姓名索赔类型 用于设置 Identity.Name .

    以下是API所需的代码更改:

    services.AddAuthentication("Bearer").AddJwtBearer("Bearer",
                    options =>
                    {
                        options.Authority = "http://localhost:5000";
                        options.Audience = "api1";
                        options.RequireHttpsMetadata = false;
    
                        
                        options.TokenValidationParameters = new TokenValidationParameters()
                        {
                            NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"//To set Identity.Name 
                        };         
                    });
    

    下面是一个示例工作代码: https://github.com/nahidf/IdentityServer4-adventures/blob/ids4-4/src/CoreApi/Startup.cs#L36

    编辑:如果你正在使用 IdentityServerSPA 模板创建项目,它只是一个模板,有一些扩展方法,内部使用我们在手动设置中使用的相同方法

    例如 Docs IdentityServerJwt 要做到这一点:

    表示与IdentityServer一起托管的API。 应用程序被配置为具有默认为应用程序名称的单一作用域。

    为了达到上述目的 IdentityServerJwt 他在打电话 AddJwtBearer 内部。

    而且它还使用 IdentityServerJwtDescriptor 添加api资源。

    因此,如果我们使用手动设置或仅使用IDS4模板,我们可以在代码中看到调用。

    在这种情况下,您可以访问 AddJwtBearer 在扩展方法中调用它时,可以更改 JwtBearerOptions 然后像这样:

    services.Configure<JwtBearerOptions>("Bearer",
                    options =>
                    {
                        new TokenValidationParameters()
                        {
                            NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"//To set Identity.Name 
                        };
                    });
    

    您还有另一个选项,它将删除我上面提到的所有映射,即在第一行启动时添加此代码。

    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();