[英]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.