简体   繁体   English

OpenIddict 密码授予返回状态码 415

[英]OpenIddict Password Grant returns Status Code 415

Good afternoon,下午好,

So I am working on integrating OpenIddict into an existing project.所以我正在努力将 OpenIddict 集成到现有项目中。 I have become stuck on the issuing of the token because I am unable to debug the issue.由于无法调试该问题,我一直卡在令牌的发行上。 So I am getting a two entirely different responses from the controller.所以我从 controller 得到了两个完全不同的响应。 I have tried this through swagger and postman both and gotten the same Status: 415 Unsupported Media Type.我已经通过 swagger 和 postman 尝试了这个并且得到了相同的状态:415 Unsupported Media Type。 Now I was pretty sure I read that the information coming to the token for the password grant had to be "application/x-www-form-urlencoded" So that is what I am passing to the controller, but still receiving a 415. On the flip side of that if I look in the debug log for the project I see the following:现在我很确定我读到密码授予令牌的信息必须是“application/x-www-form-urlencoded”所以这就是我传递给 controller 的信息,但仍然收到 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.

That too me looks like it was working.这也是我看起来像它的工作。 It never went into the token endpoint as I had a breakpoint immediately set.它从未进入令牌端点,因为我立即设置了断点。 Here is my setup:这是我的设置:

Startup.cs启动.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:授权控制器.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;
            }
        } 
    }

Here is how I have postman set for this, you can see the 415 return这是我为此设置 postman 的方法,您可以看到 415 返回在此处输入图像描述

Not sure what I missed in the setup, but I am receiving conflicting information, plus I want to troubleshoot the controller to determine what else I need to finish off the controller and ensure I am getting the information that I is needed for the rest of the program.不确定我在设置中遗漏了什么,但我收到了相互矛盾的信息,另外我想对 controller 进行故障排除,以确定完成 controller 还需要什么,并确保我获得了 Z65E8800B5C6800A 的 Z65E8800B5C68BA6A 所需的信息程序。 The 415 is not allowing me to troubleshoot the issue, but something is saying that the information is correct? 415 不允许我对问题进行故障排除,但有人说信息是正确的?

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

According to the above code, since you are setting the Produces attribute as application/json , when you send a request using PostMan, try to set the Content-Type as application/json (in the request's Body panel, select Raw and choose JSON format).根据上面的代码,由于您将Produces属性设置为application/json ,因此当您使用 PostMan 发送请求时,请尝试将Content-Type设置为application/json (在请求的Body面板中,Z99938282F04071859941E18F16EFZCF42Z Raw并选择JSON格式)。

Refer the follow sample screenshot:请参阅以下示例屏幕截图:

在此处输入图像描述

Besides, I also tried to change the Produces attribute to application/x-www-form-urlencoded , but it will show http 406 error or 415 error.此外,我还尝试将 Produces 属性更改为application/x-www-form-urlencoded ,但它会显示 http 406 错误或 415 错误。 So, try to use the above method and set the Content-Type as application/json .因此,尝试使用上述方法并将Content-Type设置为application/json

Turns out the issue was at the head of the controller.原来问题出在 controller 的头上。 The previous answer helped me see that while they saw the produces("application/json"), it was not exactly that.上一个答案帮助我看到,虽然他们看到了产品(“application/json”),但事实并非如此。 At the top of the controller I had a Consumes("application/json").在 controller 的顶部,我有一个 Consumes("application/json")。 Removed this and it went into the method.删除它并进入方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM