IdentityServer - 如何向客户端凭证令牌添加额外声明?
IdentityServer - How to add additional claim to client credential token?
理想功能:用户登录A网站并通过身份验证。他们单击一个按钮,后端从数据库中查找网站B中的帐户ID,然后将此信息发送到IdentityServer以创建JWT包含 "user_id" 字段。然后用于调用网站 B 上的 REST 端点并进行身份验证,然后 "user_id" 用于创建一个登录 cookie,该 cookie 被发送回网站 A。然后用户被重定向。
我们是 运行 IdentityServer 4,但使用 IdentityServer3 作为我们的主要代码库与它通信是在 .NET Framework 上。我试过在 extras 参数中包含 "user_id" 字段,但这似乎没有任何作用。
var client = new TokenClient(requestPath, CLIENT_ID, CLIENT_SECRET,
AuthenticationStyle.PostValues);
var test = new Dictionary<string, string>
{
{ "user_id", "123123" }
};
// request token
var tokenResponse = await client
.RequestClientCredentialsAsync(apiScope, test)
.ConfigureAwait(false);
我也尝试过使用 client.RequestCustomAsync 和 client.RequestAsync,但没有成功。
我收到了一个没有问题的令牌,但它不包含 user_id 信息 - 只有普通受众、范围、到期时间等。
对于自定义问题字段,您需要创建继承IProfileService
的class,然后实现方法GetProfileDataAsync
。看起来像:
public class IdentityProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
// Call UserManager or any database to get more fields
var user = await _userManager.FindByIdAsync(sub);
// Set returned claims (System.Security.Claims.Claim) by setting context.IssuedClaims
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
}
}
并且在你注册Identity Service 4时,你需要声明它(示例在.NET Core中,与.NET Framework相同)
var identityBuilder = services.AddIdentityServer().AddProfileService<IdentityProfileService>();
我认为您应该检查 ApiResource 配置。无论您在 ApiResource 配置的 UserClaims 属性 中添加什么声明,这些声明都将出现在访问令牌中。例如
public IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1")
{
UserClaims = new[] { "CustomClaim1", "CustomClaim2"},
},
}
}
在上面的代码中,访问代码将包含CustomClaim1 和CustomClaim2。因此,如果您不提及它们,它们将不会出现在访问令牌中。
以下是对我有用的,主要是following this example
在 IdentityServer 中,创建一个新的 class 实现 IExtensionGrantValidator 的 UserInfoGrant,从请求中提取自定义声明,将它们添加到声明中,然后继续
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var userId = context.Request.Raw[UserIdKey];
...
var claims = new List<Claim>
{
new Claim(UserIdKey, userId)
}
context.Result = new GrantValidationResult(sub, GrantType, claims);
}
然后我将 class 添加到依赖注入中
builder.AddExtensionGrantValidator<UserInfoGrant>();
我还有一个 class ProfileService,它实现了 IProfileService,它将声明添加到返回的令牌中
public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var authenticationType = context.Subject.Identities.First().AuthenticationType;
var isCustomAuthenticationType = authenticationType.Equals(CustomGrantName,
StringComparison.OrdinalIgnoreCase);
if (isCustomAuthenticationType)
{
var claimsToAdd = context.Subject.Identities.First().Claims;
context.IssuedClaims = claimsToAdd.ToList();
}
else { ... }
此配置文件服务也已添加到 DI
builder.AddProfileService<Helpers.ProfileService<TUser>>();
我还向将要使用它的客户端添加了自定义授权类型。
最后,在网站 A 的调用代码中,我请求令牌是这样的:
var tokenResponse = await client.RequestTokenAsync(new TokenRequest {
Address = disco.TokenEndpoint,
ClientId = CLIENTID,
ClientSecret = CLIENTSECRET,
GrantType = "custom_grant_name",
Parameters = {
{ "scope", PROTECTED_RESOURCE_NAME },
{ "user_id", "26616" }
}
}).ConfigureAwait(false);
理想功能:用户登录A网站并通过身份验证。他们单击一个按钮,后端从数据库中查找网站B中的帐户ID,然后将此信息发送到IdentityServer以创建JWT包含 "user_id" 字段。然后用于调用网站 B 上的 REST 端点并进行身份验证,然后 "user_id" 用于创建一个登录 cookie,该 cookie 被发送回网站 A。然后用户被重定向。
我们是 运行 IdentityServer 4,但使用 IdentityServer3 作为我们的主要代码库与它通信是在 .NET Framework 上。我试过在 extras 参数中包含 "user_id" 字段,但这似乎没有任何作用。
var client = new TokenClient(requestPath, CLIENT_ID, CLIENT_SECRET,
AuthenticationStyle.PostValues);
var test = new Dictionary<string, string>
{
{ "user_id", "123123" }
};
// request token
var tokenResponse = await client
.RequestClientCredentialsAsync(apiScope, test)
.ConfigureAwait(false);
我也尝试过使用 client.RequestCustomAsync 和 client.RequestAsync,但没有成功。
我收到了一个没有问题的令牌,但它不包含 user_id 信息 - 只有普通受众、范围、到期时间等。
对于自定义问题字段,您需要创建继承IProfileService
的class,然后实现方法GetProfileDataAsync
。看起来像:
public class IdentityProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
// Call UserManager or any database to get more fields
var user = await _userManager.FindByIdAsync(sub);
// Set returned claims (System.Security.Claims.Claim) by setting context.IssuedClaims
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
}
}
并且在你注册Identity Service 4时,你需要声明它(示例在.NET Core中,与.NET Framework相同)
var identityBuilder = services.AddIdentityServer().AddProfileService<IdentityProfileService>();
我认为您应该检查 ApiResource 配置。无论您在 ApiResource 配置的 UserClaims 属性 中添加什么声明,这些声明都将出现在访问令牌中。例如
public IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1")
{
UserClaims = new[] { "CustomClaim1", "CustomClaim2"},
},
}
}
在上面的代码中,访问代码将包含CustomClaim1 和CustomClaim2。因此,如果您不提及它们,它们将不会出现在访问令牌中。
以下是对我有用的,主要是following this example
在 IdentityServer 中,创建一个新的 class 实现 IExtensionGrantValidator 的 UserInfoGrant,从请求中提取自定义声明,将它们添加到声明中,然后继续
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var userId = context.Request.Raw[UserIdKey];
...
var claims = new List<Claim>
{
new Claim(UserIdKey, userId)
}
context.Result = new GrantValidationResult(sub, GrantType, claims);
}
然后我将 class 添加到依赖注入中
builder.AddExtensionGrantValidator<UserInfoGrant>();
我还有一个 class ProfileService,它实现了 IProfileService,它将声明添加到返回的令牌中
public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var authenticationType = context.Subject.Identities.First().AuthenticationType;
var isCustomAuthenticationType = authenticationType.Equals(CustomGrantName,
StringComparison.OrdinalIgnoreCase);
if (isCustomAuthenticationType)
{
var claimsToAdd = context.Subject.Identities.First().Claims;
context.IssuedClaims = claimsToAdd.ToList();
}
else { ... }
此配置文件服务也已添加到 DI
builder.AddProfileService<Helpers.ProfileService<TUser>>();
我还向将要使用它的客户端添加了自定义授权类型。
最后,在网站 A 的调用代码中,我请求令牌是这样的:
var tokenResponse = await client.RequestTokenAsync(new TokenRequest {
Address = disco.TokenEndpoint,
ClientId = CLIENTID,
ClientSecret = CLIENTSECRET,
GrantType = "custom_grant_name",
Parameters = {
{ "scope", PROTECTED_RESOURCE_NAME },
{ "user_id", "26616" }
}
}).ConfigureAwait(false);