使用 AspNet.Security.OpenIdConnect.Server 时用户始终为 null
User is always null when using AspNet.Security.OpenIdConnect.Server
我正在尝试为我的 aspnet 核心网络应用程序生成访问令牌。我创建了以下提供商:
public class CustomOpenIdConnectServerProvider : OpenIdConnectServerProvider
{
public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
{
// Reject the token requests that don't use grant_type=password or grant_type=refresh_token.
if (!context.Request.IsPasswordGrantType() && !context.Request.IsRefreshTokenGrantType())
{
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
description: "Only the resource owner password credentials and refresh token " +
"grants are accepted by this authorization server");
return Task.FromResult(0);
}
// Since there's only one application and since it's a public client
// (i.e a client that cannot keep its credentials private), call Skip()
// to inform the server the request should be accepted without
// enforcing client authentication.
context.Skip();
return Task.FromResult(0);
}
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
// Resolve ASP.NET Core Identity's user manager from the DI container.
var manager = context.HttpContext.RequestServices.GetRequiredService<UserManager<User>>();
// Only handle grant_type=password requests and let ASOS
// process grant_type=refresh_token requests automatically.
if (context.Request.IsPasswordGrantType())
{
var user = await manager.FindByNameAsync(context.Request.Username);
if (user == null)
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid credentials.");
return;
}
// Ensure the password is valid.
if (!await manager.CheckPasswordAsync(user, context.Request.Password))
{
if (manager.SupportsUserLockout)
{
await manager.AccessFailedAsync(user);
}
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid credentials.");
return;
}
if (manager.SupportsUserLockout)
{
await manager.ResetAccessFailedCountAsync(user);
}
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
// Note: the name identifier is always included in both identity and
// access tokens, even if an explicit destination is not specified.
identity.AddClaim(ClaimTypes.NameIdentifier, await manager.GetUserIdAsync(user));
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, await manager.GetUserIdAsync(user));
// When adding custom claims, you MUST specify one or more destinations.
// Read "part 7" for more information about custom claims and scopes.
identity.AddClaim("username", await manager.GetUserNameAsync(user),
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
var claims = await manager.GetClaimsAsync(user);
foreach (var claim in claims)
{
identity.AddClaim(claim.Type, claim.Value, OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
// Set the list of scopes granted to the client application.
ticket.SetScopes(
/* openid: */ OpenIdConnectConstants.Scopes.OpenId,
OpenIdConnectConstants.Scopes.OfflineAccess,
/* email: */ OpenIdConnectConstants.Scopes.Email,
/* profile: */ OpenIdConnectConstants.Scopes.Profile);
// Set the resource servers the access token should be issued for.
ticket.SetResources("resource_server");
context.Validate(ticket);
}
}
这很好用,我可以获得访问令牌并且用户已成功通过身份验证。我在这里面临的问题是,在我执行此操作时,在任何授权的操作方法中:var user = await _userManager.GetUserAsync(User);
user
的值始终为空!当然,我使用有效的访问令牌传递授权 header,请求进入带有 Authorize
注释的操作,没有任何问题。只是 user
的值为空。谁能告诉我我的代码有什么问题吗?
默认情况下,UserManager.GetUserAsync(User)
使用 ClaimTypes.NameIdentifier
声明作为用户标识符。
在您的情况下,ClaimTypes.NameIdentifier
- OpenID Connect 服务器中间件不再将其视为 1.0 中的特殊声明 - 未添加到访问令牌,因为它没有适当的目的地。因此,Identity 无法从访问令牌中提取用户标识符。
你有 3 个选项来解决这个问题:
通过在您的 Startup.ConfigureServices()
方法中调用 services.Configure<IdentityOptions>(options => options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject);
替换 Identity 使用的默认用户标识符声明。
继续使用 ClaimTypes.NameIdentifier
声明,但为其提供正确的目的地 (OpenIdConnectConstants.Destinations.AccessToken
)。
使用UserManager.FindByIdAsync(User.FindFirstValue(OpenIdConnectConstants.Claims.Subject))
代替UserManager.GetUserAsync(User)
。
我正在尝试为我的 aspnet 核心网络应用程序生成访问令牌。我创建了以下提供商:
public class CustomOpenIdConnectServerProvider : OpenIdConnectServerProvider
{
public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
{
// Reject the token requests that don't use grant_type=password or grant_type=refresh_token.
if (!context.Request.IsPasswordGrantType() && !context.Request.IsRefreshTokenGrantType())
{
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
description: "Only the resource owner password credentials and refresh token " +
"grants are accepted by this authorization server");
return Task.FromResult(0);
}
// Since there's only one application and since it's a public client
// (i.e a client that cannot keep its credentials private), call Skip()
// to inform the server the request should be accepted without
// enforcing client authentication.
context.Skip();
return Task.FromResult(0);
}
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
// Resolve ASP.NET Core Identity's user manager from the DI container.
var manager = context.HttpContext.RequestServices.GetRequiredService<UserManager<User>>();
// Only handle grant_type=password requests and let ASOS
// process grant_type=refresh_token requests automatically.
if (context.Request.IsPasswordGrantType())
{
var user = await manager.FindByNameAsync(context.Request.Username);
if (user == null)
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid credentials.");
return;
}
// Ensure the password is valid.
if (!await manager.CheckPasswordAsync(user, context.Request.Password))
{
if (manager.SupportsUserLockout)
{
await manager.AccessFailedAsync(user);
}
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid credentials.");
return;
}
if (manager.SupportsUserLockout)
{
await manager.ResetAccessFailedCountAsync(user);
}
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
// Note: the name identifier is always included in both identity and
// access tokens, even if an explicit destination is not specified.
identity.AddClaim(ClaimTypes.NameIdentifier, await manager.GetUserIdAsync(user));
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, await manager.GetUserIdAsync(user));
// When adding custom claims, you MUST specify one or more destinations.
// Read "part 7" for more information about custom claims and scopes.
identity.AddClaim("username", await manager.GetUserNameAsync(user),
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
var claims = await manager.GetClaimsAsync(user);
foreach (var claim in claims)
{
identity.AddClaim(claim.Type, claim.Value, OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
// Set the list of scopes granted to the client application.
ticket.SetScopes(
/* openid: */ OpenIdConnectConstants.Scopes.OpenId,
OpenIdConnectConstants.Scopes.OfflineAccess,
/* email: */ OpenIdConnectConstants.Scopes.Email,
/* profile: */ OpenIdConnectConstants.Scopes.Profile);
// Set the resource servers the access token should be issued for.
ticket.SetResources("resource_server");
context.Validate(ticket);
}
}
这很好用,我可以获得访问令牌并且用户已成功通过身份验证。我在这里面临的问题是,在我执行此操作时,在任何授权的操作方法中:var user = await _userManager.GetUserAsync(User);
user
的值始终为空!当然,我使用有效的访问令牌传递授权 header,请求进入带有 Authorize
注释的操作,没有任何问题。只是 user
的值为空。谁能告诉我我的代码有什么问题吗?
默认情况下,UserManager.GetUserAsync(User)
使用 ClaimTypes.NameIdentifier
声明作为用户标识符。
在您的情况下,ClaimTypes.NameIdentifier
- OpenID Connect 服务器中间件不再将其视为 1.0 中的特殊声明 - 未添加到访问令牌,因为它没有适当的目的地。因此,Identity 无法从访问令牌中提取用户标识符。
你有 3 个选项来解决这个问题:
通过在您的
Startup.ConfigureServices()
方法中调用services.Configure<IdentityOptions>(options => options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject);
替换 Identity 使用的默认用户标识符声明。继续使用
ClaimTypes.NameIdentifier
声明,但为其提供正确的目的地 (OpenIdConnectConstants.Destinations.AccessToken
)。使用
UserManager.FindByIdAsync(User.FindFirstValue(OpenIdConnectConstants.Claims.Subject))
代替UserManager.GetUserAsync(User)
。