OpenIddict 密码授予 returns 状态码 415

OpenIddict Password Grant returns Status Code 415

下午好,

所以我正在努力将 OpenIddict 集成到现有项目中。因为我无法调试问题,所以我陷入了令牌的发行。所以我从控制器那里得到了两个完全不同的响应。我已经通过 swagger 和 postman 进行了尝试,并获得了相同的 Status: 415 Unsupported Media Type。现在我很确定我读到用于密码授予的令牌信息必须是“application/x-www-form-urlencoded”所以这就是我传递给控制器​​的信息,但仍然收到 415。另一方面如果我查看项目的调试日志,我会看到以下内容:

OpenIddict.Server.OpenIddictServerDispatcher: Information: The request address matched a server endpoint: Token.
OpenIddict.Server.OpenIddictServerDispatcher: Information: The token request was successfully extracted: {
  "grant_type": "password",
  "username": "Administrator@MRM2Inc.com",
  "password": "[redacted]"
}.

OpenIddict.Server.OpenIddictServerDispatcher: Information: The token request was successfully validated.

这对我来说也是有效的。它从未进入令牌端点,因为我立即设置了断点。 这是我的设置:

Startup.cs

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddDbContext<IdentDbContext>(options =>
            {
                //options.UseSqlServer(
                //    Configuration.GetConnectionString("IdentityDB"));

                options.UseOpenIddict<Guid>();
            });
                

            
            // Add the Identity Services we are going to be using the Application Users and the Application Roles
            services.AddIdentity<ApplicationUsers, ApplicationRoles>(config =>
            {
                config.SignIn.RequireConfirmedEmail = true;
                config.SignIn.RequireConfirmedAccount = true;
                config.User.RequireUniqueEmail = true;
                config.Lockout.MaxFailedAccessAttempts = 3;
            }).AddEntityFrameworkStores<IdentDbContext>()
            .AddUserStore<ApplicationUserStore>()
            .AddRoleStore<ApplicationRoleStore>()
            .AddRoleManager<ApplicationRoleManager>()
            .AddUserManager<ApplicationUserManager>()
            .AddErrorDescriber<ApplicationIdentityErrorDescriber>()
            .AddDefaultTokenProviders()
            .AddDefaultUI();

            services.AddDataLibrary();    
            
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
            });                

            //services.AddSingleton<IGenerateTokens, GenerateTokens>();

            // Add in the email
            var emailConfig = Configuration.GetSection("EmailConfiguration").Get<EmailConfiguration>();
            services.AddSingleton(emailConfig);
            services.AddEmailLibrary();

            services.AddOpenIddict()
                // Register the OpenIddict core components.
                .AddCore(options =>
                {
                    // Configure OpenIddict to use the Entity Framework Core stores and models.
                    // Note: call ReplaceDefaultEntities() to replace the default entities.
                    options.UseEntityFrameworkCore()
                    .UseDbContext<IdentDbContext>()
                    .ReplaceDefaultEntities<Guid>();
                })
                // Register the OpenIddict server components.
                .AddServer(options =>
                {
                    // Enable the token endpoint.  What other endpoints?
                    options.SetTokenEndpointUris("/Token");

                    // Enable the client credentials flow.  Which flow do I need?
                    options.AllowPasswordFlow();

                    options.AcceptAnonymousClients();

                    // Register the signing and encryption credentials.
                    options.AddDevelopmentEncryptionCertificate()
                          .AddDevelopmentSigningCertificate();

                    // Register the ASP.NET Core host and configure the ASP.NET Core options.
                    options.UseAspNetCore()                               
                           .EnableTokenEndpointPassthrough();
                })
                // Register the OpenIddict validation components.
                .AddValidation(options =>
                {
                    // Import the configuration from the local OpenIddict server instance.
                    options.UseLocalServer();

                    // Register the ASP.NET Core host.
                    options.UseAspNetCore();
                });

            // Register the Swagger generator, defining 1 or more Swagger documents
            services.AddSwaggerGen(swagger => 
            {
                swagger.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
                {
                    Name = "Authorization",
                    Type = SecuritySchemeType.Http,
                    Scheme = "Bearer",
                    BearerFormat = "JWT",
                    In = ParameterLocation.Header,
                    Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer'[space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\""
                });
                swagger.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                            }
                        },
                        new string[] {}
                    }
                });
                swagger.OperationFilter<SwaggerDefaultValues>();
                swagger.OperationFilter<AuthenticationRequirementOperationFilter>();

                // Set the comments path for the Swagger JSON and UI.
                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                swagger.IncludeXmlComments(xmlPath);
            });
            services.AddApiVersioning();
            services.AddVersionedApiExplorer(options =>
            {
                options.GroupNameFormat = "'v'VVVV";
                options.DefaultApiVersion = ApiVersion.Parse("0.6.alpha");
                options.AssumeDefaultVersionWhenUnspecified = true;
            });
            services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            // Enable middleware to serve generated Swagger as a JSON endpoint.
            app.UseSwagger();

            // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
            // specifying the Swagger JSON endpoint.
            app.UseSwaggerUI(c =>
            {               
                c.DisplayOperationId();
                var versionDescription = provider.ApiVersionDescriptions;
                foreach (var description in provider.ApiVersionDescriptions.OrderByDescending(_ => _.ApiVersion))
                {
                    c.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", $"MRM2 Identity API {description.GroupName}");
                }
            });

            app.UseRouting();

            app.UseAuthentication();

            app.UseAuthorization();

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

AuthorizationController.cs:

[Route("api/[controller]/[action]")]
    [ApiController]
    [ApiVersion("0.8.alpha")]
    [Produces(MediaTypeNames.Application.Json)]
    [Consumes(MediaTypeNames.Application.Json)]
    public class AuthorizationController : ControllerBase
    {
        private readonly IConfiguration _configuration;
        private readonly IdentDbContext _context;
        private readonly ApplicationUserManager _userManager;
        private readonly ApplicationRoleManager _roleManager;
        private readonly IGenerateTokens _tokens;
        private readonly IOpenIddictApplicationManager _applicationManager;
        private readonly IOpenIddictAuthorizationManager _authorizationManager;
        private readonly IOpenIddictScopeManager _scopeManager;
        private readonly SignInManager<ApplicationUsers> _signInManager;
        private HttpClient _client;

        public AuthorizationController(IConfiguration configuration, IdentDbContext context, ApplicationUserManager userManager, 
            ApplicationRoleManager roleManager, IGenerateTokens tokens, IOpenIddictApplicationManager applicationManager, IOpenIddictAuthorizationManager authorizationManager,
            IOpenIddictScopeManager scopeManager, SignInManager<ApplicationUsers> signInManager)
        {            
            _configuration = configuration;
            _context = context;
            _userManager = userManager;
            _roleManager = roleManager;
            _tokens = tokens;
            _applicationManager = applicationManager;
            _authorizationManager = authorizationManager;
            _scopeManager = scopeManager;
            _signInManager = signInManager;
        }
        



        [HttpPost("/token"), Produces("application/json")]
        public async Task<IActionResult> Exchange()
        {
            var request = HttpContext.GetOpenIddictServerRequest() ??
                throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");  //does not even hit this breakpoint.

            ClaimsPrincipal claimsPrincipal;
            

            if (request.IsPasswordGrantType())
            {
                var user = await _userManager.FindByNameAsync(request.Username);                
                
                var roleList = await _userManager.GetRolesListAsync(user);
                var databaseList = await _userManager.GetDatabasesAsync(user);
                string symKey = _configuration["Jwt:Symmetrical:Key"];
                string jwtSub = _configuration["Jwt:Subject"];
                string issuer = _configuration["Jwt:Issuer"];
                string audience = _configuration["Jwt:Audience"];

                var claims = new List<Claim>
                {
                    new Claim(JwtRegisteredClaimNames.Sub, jwtSub, issuer),
                    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), issuer),
                    new Claim(ClaimTypes.Name, user.UserName, issuer)
                };

                foreach (var role in roleList)
                {
                    claims.Add(new Claim(ClaimTypes.Role, role.Name));
                }

                foreach (var database in databaseList)
                {
                    claims.Add(new Claim(type: "DatabaseName", database));
                }
                var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
                identity.AddClaim(OpenIddictConstants.Claims.Name, user.UserName, OpenIddictConstants.Destinations.AccessToken);
                identity.AddClaim(OpenIddictConstants.Claims.Subject, jwtSub, OpenIddictConstants.Destinations.AccessToken);
                identity.AddClaim(OpenIddictConstants.Claims.Audience, audience, OpenIddictConstants.Destinations.AccessToken);
                foreach (var cl in claims)
                {
                    identity.AddClaim(cl.Type, cl.Value, OpenIddictConstants.Destinations.AccessToken);                    
                }

                claimsPrincipal = new ClaimsPrincipal(identity);
                
            }

            if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
            {
                // Retrieve the claims principal stored in the authorization code/device code/refresh token.
                var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;

                // Retrieve the user profile corresponding to the authorization code/refresh token.
                // Note: if you want to automatically invalidate the authorization code/refresh token
                // when the user password/roles change, use the following line instead:
                // var user = _signInManager.ValidateSecurityStampAsync(info.Principal);
                var user = await _userManager.GetUserAsync(principal);
                if (user == null)
                {
                    return Forbid(
                        authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                        properties: new AuthenticationProperties(new Dictionary<string, string>
                        {
                            [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
                            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid."
                        }));
                }

                // Ensure the user is still allowed to sign in.
                if (!await _signInManager.CanSignInAsync(user))
                {
                    return Forbid(
                        authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                        properties: new AuthenticationProperties(new Dictionary<string, string>
                        {
                            [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
                            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
                        }));
                }

                foreach (var claim in principal.Claims)
                {
                    claim.SetDestinations(GetDestinations(claim, principal));
                }

                // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
                return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            }

            throw new InvalidOperationException("The specified grant type is not supported.");
        }

        private IEnumerable<string> GetDestinations(Claim claim, ClaimsPrincipal principal)
        {
            // Note: by default, claims are NOT automatically included in the access and identity tokens.
            // To allow OpenIddict to serialize them, you must attach them a destination, that specifies
            // whether they should be included in access tokens, in identity tokens or in both.

            switch (claim.Type)
            {
                case Claims.Name:
                    yield return Destinations.AccessToken;

                    if (principal.HasScope(Scopes.Profile))
                        yield return Destinations.IdentityToken;

                    yield break;

                case Claims.Email:
                    yield return Destinations.AccessToken;

                    if (principal.HasScope(Scopes.Email))
                        yield return Destinations.IdentityToken;

                    yield break;

                case Claims.Role:
                    yield return Destinations.AccessToken;

                    if (principal.HasScope(Scopes.Roles))
                        yield return Destinations.IdentityToken;

                    yield break;

                // Never include the security stamp in the access and identity tokens, as it's a secret value.
                case "AspNet.Identity.SecurityStamp": yield break;

                default:
                    yield return Destinations.AccessToken;
                    yield break;
            }
        } 
    }

这是我为此设置邮递员的方式,您可以看到 415 return

不确定我在设置中遗漏了什么,但我收到了相互矛盾的信息,而且我想对控制器进行故障排除以确定我还需要什么来完成控制器并确保我得到我需要的信息对于程序的其余部分。 415 不允许我解决问题,但有人说信息是正确的?

   [HttpPost("/token"), Produces("application/json")]
    public async Task<IActionResult> Exchange()

根据上面的代码,由于您将 Produces 属性设置为 application/json,当您使用 PostMan 发送请求时,尝试将 Content-Type 设置为 [=12] =](在请求的 Body 面板中,select Raw 并选择 JSON 格式)。

参考以下示例屏幕截图:

此外,我也试过将Produces属性改成application/x-www-form-urlencoded,但是会显示http 406错误或者415错误。因此,尝试使用上述方法并将 Content-Type 设置为 application/json.

原来问题出在控制器的头上。之前的回答帮助我看到,虽然他们看到了产品(“application/json”),但事实并非如此。在控制器的顶部,我有一个 Consumes("application/json")。删除了它,它进入了方法。