使用 AspNet.Security.OpenIdConnect.Server 时用户始终为 null

User is always null when using AspNet.Security.OpenIdConnect.Server

我正在尝试为我的 aspnet 核心网络应用程序生成访问令牌。我创建了以下提供商:

public class CustomOpenIdConnectServerProvider : OpenIdConnectServerProvider
    {
        public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
        {
            // Reject the token requests that don't use grant_type=password or grant_type=refresh_token.
            if (!context.Request.IsPasswordGrantType() && !context.Request.IsRefreshTokenGrantType())
            {
                context.Reject(
                    error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
                    description: "Only the resource owner password credentials and refresh token " +
                                 "grants are accepted by this authorization server");

                return Task.FromResult(0);
            }

            // Since there's only one application and since it's a public client
            // (i.e a client that cannot keep its credentials private), call Skip()
            // to inform the server the request should be accepted without
            // enforcing client authentication.
            context.Skip();

            return Task.FromResult(0);
        }

        public override async Task HandleTokenRequest(HandleTokenRequestContext context)
        {
            // Resolve ASP.NET Core Identity's user manager from the DI container.
            var manager = context.HttpContext.RequestServices.GetRequiredService<UserManager<User>>();

            // Only handle grant_type=password requests and let ASOS
            // process grant_type=refresh_token requests automatically.
            if (context.Request.IsPasswordGrantType())
            {
                var user = await manager.FindByNameAsync(context.Request.Username);
                if (user == null)
                {
                    context.Reject(
                        error: OpenIdConnectConstants.Errors.InvalidGrant,
                        description: "Invalid credentials.");

                    return;
                }

                // Ensure the password is valid.
                if (!await manager.CheckPasswordAsync(user, context.Request.Password))
                {
                    if (manager.SupportsUserLockout)
                    {
                        await manager.AccessFailedAsync(user);
                    }

                    context.Reject(
                        error: OpenIdConnectConstants.Errors.InvalidGrant,
                        description: "Invalid credentials.");

                    return;
                }

                if (manager.SupportsUserLockout)
                {
                    await manager.ResetAccessFailedCountAsync(user);
                }

                var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);

                // Note: the name identifier is always included in both identity and
                // access tokens, even if an explicit destination is not specified.
                identity.AddClaim(ClaimTypes.NameIdentifier, await manager.GetUserIdAsync(user));

                identity.AddClaim(OpenIdConnectConstants.Claims.Subject, await manager.GetUserIdAsync(user));

                // When adding custom claims, you MUST specify one or more destinations.
                // Read "part 7" for more information about custom claims and scopes.
                identity.AddClaim("username", await manager.GetUserNameAsync(user),
                    OpenIdConnectConstants.Destinations.AccessToken,
                    OpenIdConnectConstants.Destinations.IdentityToken);

                var claims = await manager.GetClaimsAsync(user);
                foreach (var claim in claims)
                {
                    identity.AddClaim(claim.Type, claim.Value, OpenIdConnectConstants.Destinations.AccessToken,
                        OpenIdConnectConstants.Destinations.IdentityToken);
                }

                // Create a new authentication ticket holding the user identity.
                var ticket = new AuthenticationTicket(
                    new ClaimsPrincipal(identity),
                    new AuthenticationProperties(),
                    context.Options.AuthenticationScheme);


                // Set the list of scopes granted to the client application.
                ticket.SetScopes(
                    /* openid: */ OpenIdConnectConstants.Scopes.OpenId,
                    OpenIdConnectConstants.Scopes.OfflineAccess,
                    /* email: */ OpenIdConnectConstants.Scopes.Email,
                    /* profile: */ OpenIdConnectConstants.Scopes.Profile);

                // Set the resource servers the access token should be issued for.
                ticket.SetResources("resource_server");

                context.Validate(ticket);
            }
        }

这很好用,我可以获得访问令牌并且用户已成功通过身份验证。我在这里面临的问题是,在我执行此操作时,在任何授权的操作方法中:var user = await _userManager.GetUserAsync(User); user 的值始终为空!当然,我使用有效的访问令牌传递授权 header,请求进入带有 Authorize 注释的操作,没有任何问题。只是 user 的值为空。谁能告诉我我的代码有什么问题吗?

默认情况下,UserManager.GetUserAsync(User) 使用 ClaimTypes.NameIdentifier 声明作为用户标识符。

在您的情况下,ClaimTypes.NameIdentifier - OpenID Connect 服务器中间件不再将其视为 1.0 中的特殊声明 - 未添加到访问令牌,因为它没有适当的目的地。因此,Identity 无法从访问令牌中提取用户标识符。

你有 3 个选项来解决这个问题:

  • 通过在您的 Startup.ConfigureServices() 方法中调用 services.Configure<IdentityOptions>(options => options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject); 替换 Identity 使用的默认用户标识符声明。

  • 继续使用 ClaimTypes.NameIdentifier 声明,但为其提供正确的目的地 (OpenIdConnectConstants.Destinations.AccessToken)。

  • 使用UserManager.FindByIdAsync(User.FindFirstValue(OpenIdConnectConstants.Claims.Subject))代替UserManager.GetUserAsync(User)