使用 AspNet.Security.OpenIdConnect.Server 发行代币 (RC2) - 无效的客户端?
Issuing Tokens with AspNet.Security.OpenIdConnect.Server (RC2) - Invalid Client?
我能够在 RC1 中使用它。 OpenIdConnectServerProvider
变化很大。
我对资源所有者流程感兴趣,所以我的 AuthorizationProvider 如下所示:
public sealed class AuthorizationProvider : OpenIdConnectServerProvider
{
public override Task MatchEndpoint(MatchEndpointContext context)
{
if (context.Options.AuthorizationEndpointPath.HasValue &&
context.Request.Path.StartsWithSegments(context.Options.AuthorizationEndpointPath))
{
context.MatchesAuthorizationEndpoint();
}
return Task.FromResult<object>(null);
}
public override async Task ValidateAuthorizationRequest(ValidateAuthorizationRequestContext context)
{
context.Validate();
await Task.FromResult<object>(null);
}
public override async Task ValidateTokenRequest(ValidateTokenRequestContext context)
{
if (!context.Request.IsAuthorizationCodeGrantType() &&
!context.Request.IsRefreshTokenGrantType() &&
!context.Request.IsPasswordGrantType())
{
context.Reject(
error: "unsupported_grant_type",
description: "Only authorization code, refresh token, and ROPC grant types " +
"are accepted by this authorization server");
}
/* This is where the problem is. This context.Validate()
will automatically return a 400, server_error, with
message "An internal server error occurred."
If I commented this out, I will get a 400, invalid_client.
If I put in an arbitrary client like "any_client", it
goes to GrantResourceOwnerCredentials, as I expect.
However, I get a 500 with no explanation when it executes.
See the function below for more details.
*/
context.Validate();
await Task.FromResult<object>(null);
}
public override Task HandleUserinfoRequest(HandleUserinfoRequestContext context)
{
context.SkipToNextMiddleware();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context)
{
MYDbContext db = context.HttpContext.RequestServices.GetRequiredService<MyDbContext>();
UserManager<MyUser> UM = context.HttpContext.RequestServices.GetRequiredService<UserManager<MyUser>>();
MyUser user = await UM.FindByNameAsync(context.Request.Username);
if (user == null)
{
context.Reject(
error: "user_not_found",
description: "User not found");
return;
}
bool passwordsMatch = await UM.CheckPasswordAsync(user, context.Request.Password);
if (!passwordsMatch)
{
context.Reject(
error: "invalid_credentials",
description: "Password is incorrect");
return;
}
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.Name, user.UserName, "id_token token");
/* I set the breakpoint on this line, and the execution
does not hit this breakpoint. I immediately get a 500.
My output says 'System.ArgumentException' in
AspNet.Security.OpenIdConnect.Extensions.dll
*/
List<string> roles = (await UM.GetRolesAsync(user)).ToList();
roles.ForEach(role =>
{
identity.AddClaim(ClaimTypes.Role, role, "id_token token");
});
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
ticket.SetResources(new[] { "mlm_resource_server" });
ticket.SetAudiences(new[] { "mlm_resource_server" });
ticket.SetScopes(new[] { "defaultscope" });
context.Validate(ticket);
}
}
顺便说一下,我正在尝试 运行 在 Fiddler 上执行此操作:
POST /token HTTP/1.1
Host: localhost:56785
Content-Type: application/x-www-form-urlencoded
username=user&password=pw&grant_type=password
当密码不正确时,我得到了预期的 400 拒绝,但当密码正确时,我得到了 500。
我错过了什么?我现在建立该用户身份的方式不正确吗?我应该重写另一个函数吗?
注意 - 我没有提供我的启动文件,因为我认为它无关紧要。如果绝对需要,我稍后会 post。
如果您启用了日志记录,您会立即明白发生了什么:OpenID Connect 服务器中间件不允许您在 client_id
时将令牌请求标记为 "fully validated"请求中缺少:
if (context.IsValidated && string.IsNullOrEmpty(request.ClientId)) {
Logger.LogError("The token request was validated but the client_id was not set.");
return await SendTokenResponseAsync(request, new OpenIdConnectMessage {
Error = OpenIdConnectConstants.Errors.ServerError,
ErrorDescription = "An internal server error occurred."
});
}
如果您想让客户端身份验证可选,请改为调用 context.Skip()
。
请注意,您的提供商存在一些问题:
ValidateAuthorizationRequest
不验证任何东西,这是 可怕的 因为任何 redirect_uri
都会被认为是有效的(= 一个巨大的开放重定向缺陷)。幸运的是,由于您只对 ROPC 授权感兴趣,因此您可能不会实施任何交互式流程。我建议删除此方法(您也可以删除 MatchEndpoint
)。
您在 ValidateTokenRequest
中的初始授权检查存在错误,因为您在调用 context.Reject()
后没有停止代码的执行,最终导致 context.Validate()
被调用。
identity.AddClaim(ClaimTypes.Name, user.UserName, "id_token token")
不再是有效语法。 ArgumentException
可能是由此检查引起的:
if (destinations.Any(destination => destination.Contains(" "))) {
throw new ArgumentException("Destinations cannot contain spaces.", nameof(destinations));
}
改用这个:
identity.AddClaim(ClaimTypes.Name, user.UserName,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
如果您仍然不确定您的提供商应该是什么样子,请不要犹豫,看看这些具体示例:
我能够在 RC1 中使用它。 OpenIdConnectServerProvider
变化很大。
我对资源所有者流程感兴趣,所以我的 AuthorizationProvider 如下所示:
public sealed class AuthorizationProvider : OpenIdConnectServerProvider
{
public override Task MatchEndpoint(MatchEndpointContext context)
{
if (context.Options.AuthorizationEndpointPath.HasValue &&
context.Request.Path.StartsWithSegments(context.Options.AuthorizationEndpointPath))
{
context.MatchesAuthorizationEndpoint();
}
return Task.FromResult<object>(null);
}
public override async Task ValidateAuthorizationRequest(ValidateAuthorizationRequestContext context)
{
context.Validate();
await Task.FromResult<object>(null);
}
public override async Task ValidateTokenRequest(ValidateTokenRequestContext context)
{
if (!context.Request.IsAuthorizationCodeGrantType() &&
!context.Request.IsRefreshTokenGrantType() &&
!context.Request.IsPasswordGrantType())
{
context.Reject(
error: "unsupported_grant_type",
description: "Only authorization code, refresh token, and ROPC grant types " +
"are accepted by this authorization server");
}
/* This is where the problem is. This context.Validate()
will automatically return a 400, server_error, with
message "An internal server error occurred."
If I commented this out, I will get a 400, invalid_client.
If I put in an arbitrary client like "any_client", it
goes to GrantResourceOwnerCredentials, as I expect.
However, I get a 500 with no explanation when it executes.
See the function below for more details.
*/
context.Validate();
await Task.FromResult<object>(null);
}
public override Task HandleUserinfoRequest(HandleUserinfoRequestContext context)
{
context.SkipToNextMiddleware();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context)
{
MYDbContext db = context.HttpContext.RequestServices.GetRequiredService<MyDbContext>();
UserManager<MyUser> UM = context.HttpContext.RequestServices.GetRequiredService<UserManager<MyUser>>();
MyUser user = await UM.FindByNameAsync(context.Request.Username);
if (user == null)
{
context.Reject(
error: "user_not_found",
description: "User not found");
return;
}
bool passwordsMatch = await UM.CheckPasswordAsync(user, context.Request.Password);
if (!passwordsMatch)
{
context.Reject(
error: "invalid_credentials",
description: "Password is incorrect");
return;
}
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.Name, user.UserName, "id_token token");
/* I set the breakpoint on this line, and the execution
does not hit this breakpoint. I immediately get a 500.
My output says 'System.ArgumentException' in
AspNet.Security.OpenIdConnect.Extensions.dll
*/
List<string> roles = (await UM.GetRolesAsync(user)).ToList();
roles.ForEach(role =>
{
identity.AddClaim(ClaimTypes.Role, role, "id_token token");
});
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
ticket.SetResources(new[] { "mlm_resource_server" });
ticket.SetAudiences(new[] { "mlm_resource_server" });
ticket.SetScopes(new[] { "defaultscope" });
context.Validate(ticket);
}
}
顺便说一下,我正在尝试 运行 在 Fiddler 上执行此操作:
POST /token HTTP/1.1
Host: localhost:56785
Content-Type: application/x-www-form-urlencoded
username=user&password=pw&grant_type=password
当密码不正确时,我得到了预期的 400 拒绝,但当密码正确时,我得到了 500。
我错过了什么?我现在建立该用户身份的方式不正确吗?我应该重写另一个函数吗?
注意 - 我没有提供我的启动文件,因为我认为它无关紧要。如果绝对需要,我稍后会 post。
如果您启用了日志记录,您会立即明白发生了什么:OpenID Connect 服务器中间件不允许您在 client_id
时将令牌请求标记为 "fully validated"请求中缺少:
if (context.IsValidated && string.IsNullOrEmpty(request.ClientId)) {
Logger.LogError("The token request was validated but the client_id was not set.");
return await SendTokenResponseAsync(request, new OpenIdConnectMessage {
Error = OpenIdConnectConstants.Errors.ServerError,
ErrorDescription = "An internal server error occurred."
});
}
如果您想让客户端身份验证可选,请改为调用 context.Skip()
。
请注意,您的提供商存在一些问题:
ValidateAuthorizationRequest
不验证任何东西,这是 可怕的 因为任何 redirect_uri
都会被认为是有效的(= 一个巨大的开放重定向缺陷)。幸运的是,由于您只对 ROPC 授权感兴趣,因此您可能不会实施任何交互式流程。我建议删除此方法(您也可以删除 MatchEndpoint
)。
您在 ValidateTokenRequest
中的初始授权检查存在错误,因为您在调用 context.Reject()
后没有停止代码的执行,最终导致 context.Validate()
被调用。
identity.AddClaim(ClaimTypes.Name, user.UserName, "id_token token")
不再是有效语法。 ArgumentException
可能是由此检查引起的:
if (destinations.Any(destination => destination.Contains(" "))) {
throw new ArgumentException("Destinations cannot contain spaces.", nameof(destinations));
}
改用这个:
identity.AddClaim(ClaimTypes.Name, user.UserName,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
如果您仍然不确定您的提供商应该是什么样子,请不要犹豫,看看这些具体示例: