request.IsPasswordGrantType() 在 ASP.NET Core 3.1 中的 openiddict 中总是失败

request.IsPasswordGrantType() always fails in openiddict in ASP.NET Core 3.1

我的 ASP.NET Core 2.2 Web API 已经配置 openiddict。现在,在使用 openiddict 2.0.1 将项目更新为 asp.net core 3.1 之后,request.IsPasswordGrantType() 在我的令牌端点中总是失败。

这是我在 Startup.cs

中的 ConfigureServices()
services.AddDbContext<AppIdentityDbContext>(options => {
    options.UseInMemoryDatabase("Identity");
    options.UseOpenIddict<Guid>();
});

// Add OpenIddict services
services.AddOpenIddict()
    .AddCore(options =>
    {
        options.UseEntityFrameworkCore()
            .UseDbContext<AppIdentityDbContext>()
            .ReplaceDefaultEntities<Guid>();
    })
    .AddServer(options =>
    {
        //options.UseMvc();

        options.EnableTokenEndpoint("/api/token");

        options.AllowPasswordFlow();
        options.AcceptAnonymousClients();
    })
    .AddValidation();

// ASP.NET Core Identity should use the same claim names as OpenIddict
services.Configure<IdentityOptions>(options =>
{
    options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
    options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
    options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});

services.AddAuthentication(options =>
{
    options.DefaultScheme = OpenIddictValidationDefaults.AuthenticationScheme;
});

这是我在 Startup.cs,

中的 Configure() 方法
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler(appBuilder =>
    {
        appBuilder.Run(async context =>
        {
            context.Response.StatusCode = 500;
            await context.Response.WriteAsync("An unexpected fault happened. Try again later.");
        });
    });
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

这是我在 TokenController

中的 TokenExchangeEndPoint
[HttpPost(Name = nameof(TokenExchange))]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
public async Task<IActionResult> TokenExchange([FromForm]OpenIdConnectRequest request)
{
    if (!request.IsPasswordGrantType())
    {
        return BadRequest(new OpenIdConnectResponse
        {
            Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
            ErrorDescription = "The specified grant type is not supported."
        });
    }

    var user = await _userManager.FindByNameAsync(request.Username);
    if (user == null)
    {
        return BadRequest(new OpenIdConnectResponse
        {
            Error = OpenIdConnectConstants.Errors.InvalidGrant,
            ErrorDescription = "The username or password is invalid."
        });
    }

    // Ensure the user is allowed to sign in
    if (!await _signInManager.CanSignInAsync(user))
    {
        return BadRequest(new OpenIdConnectResponse
        {
            Error = OpenIdConnectConstants.Errors.InvalidGrant,
            ErrorDescription = "The specified user is not allowed to sign in."
        });
    }

    // Ensure the user is not already locked out
    if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user))
    {
        return BadRequest(new OpenIdConnectResponse
        {
            Error = OpenIdConnectConstants.Errors.InvalidGrant,
            ErrorDescription = "The username or password is invalid."
        });
    }

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

        return BadRequest(new OpenIdConnectResponse
        {
            Error = OpenIdConnectConstants.Errors.InvalidGrant,
            ErrorDescription = "The username or password is invalid."
        });
    }

    // Reset the lockout count
    if (_userManager.SupportsUserLockout)
    {
        await _userManager.ResetAccessFailedCountAsync(user);
    }

    // Look up the user's roles (if any)
    var roles = new string[0];
    if (_userManager.SupportsUserRole)
    {
        roles = (await _userManager.GetRolesAsync(user)).ToArray();
    }

    // Create a new authentication ticket w/ the user identity
    var ticket = await CreateTicketAsync(request, user, roles);

    return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}

这是我的提琴手请求,

我将 Content-Type 用作 application/x-www-form-urlencoded

我是 openiddict 的新手。请帮助我解决我做错的事情。 openiddict 示例与 asp.net core 3.x 的任何链接将不胜感激。

OpenIdConnectRequest 并不意味着与 [FromForm] 一起使用,因为它应该从 HTTP 上下文中解析(在由 OpenIddict 验证和填充之后),这需要使用注册 MVC 绑定器options.UseMvc().

2 个选项来解决您的问题:

  • 取消注释 options.UseMvc() 并删除 [FromForm]。这将适用于 OpenIddict 2.x,但在 3.x 中不受支持,我们鼓励您使用其他选项。

  • 删除 OpenIdConnectRequest 参数并使用 var request = HttpContext.GetOpenIdConnectRequest() 解析请求。在这种情况下,您可以删除 options.UseMvc()