密码流的 IdentityServer4 客户端不包括访问令牌中的 TestUser 声明

IdentityServer4 client for Password Flow not including TestUser claims in access token

我正在尝试使用 IdentityServer4 中的(遗留)资源所有者密码流程创建一个沙箱应用程序。我已经用这些包建立了一个全新的 ASP.NET Core 3 项目:

<PackageReference Include="IdentityServer4" Version="3.1.3" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />

我正在使用以下启动部分:

services.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddInMemoryApiResources(new[] { new ApiResource("foo-api") })
    .AddInMemoryIdentityResources(new[]
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        new IdentityResources.Email(),
        new IdentityResource("role", new[] { JwtClaimTypes.Role }),
    })
    .AddInMemoryClients(new[]
    {
        new Client
        {
            // Don't use RPO if you can prevent it. We use it here
            // because it's the easiest way to demo with users.
            ClientId = "legacy-rpo",
            AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
            AllowAccessTokensViaBrowser = false,
            RequireClientSecret = false,
            AllowedScopes = { "foo-api", "openid", "profile", "email", "role" },

        },
    })
    .AddTestUsers(new List<TestUser>
    {
        new TestUser
        {
            SubjectId = "ABC-123",
            Username = "john",
            Password = "secret",
            Claims = new[]
            {
                new Claim(JwtClaimTypes.Role, "user"),
                new Claim(JwtClaimTypes.Email, "john@example.org"),
                new Claim("x-domain", "foo") },
        },
    })

然后我提供一个调用 /connect/token 端点的静态 index.html 文件,如下所示:

const response = await fetch("/connect/token", {
    method: "POST",
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
    },
    body: new URLSearchParams({
        "grant_type": "password",
        "client_id": "legacy-rpo",
        "username": "john",
        "password": "secret",
        // scope omitted should net *all* scopes in IDS4
    }),
});

但它 returns 我 access_token 它(解码)看起来像这样:

{
  "nbf": 1588582642,
  "exp": 1588586242,
  "iss": "https://localhost:5001",
  "aud": "foo-api",
  "client_id": "legacy-rpo",
  "sub": "ABC-123",
  "auth_time": 1588582642,
  "idp": "local",
  "scope": [
    "email",
    "openid",
    "profile",
    "role",
    "foo-api"
  ],
  "amr": [
    "pwd"
  ]
}

我在 access_token.

中缺少作为顶级条目的电子邮件、角色等

在挖掘源代码时,我看到 the ProfileService for TestUsers should add all requested claims to the token via an extension method。我在谷歌搜索我的问题时发现的大多数问题要么是我已经做过的(或尝试过的,见下文),要么是关于其他边缘情况的。

许多其他线程也导致Dominick Baier's post on roles,但问题是API方无法识别该角色。 我的问题是 role 根本不包含在令牌中。

我尝试过的:


关于 ProfileService

的脚注

我试过添加这个:

public class ProfileService : TestUserProfileService
{
    public ProfileService(TestUserStore users, ILogger<TestUserProfileService> logger) 
        : base(users, logger)
    { }

    public override Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var role = context.Subject.FindFirst(ClaimTypes.Role);
        context.IssuedClaims.Add(role);
        return base.GetProfileDataAsync(context);
    }

    public override Task IsActiveAsync(IsActiveContext context)
    {
        return base.IsActiveAsync(context);
    }
}

AddIdentityServer() 构建器链:

.AddProfileService<ProfileService>()

但是 GetProfileDataAsync(...) 方法根本没有被命中,没有断点触发。所以这表明默认值 TestUserProfileService 也永远不会被命中,从而解释了我的令牌中缺少声明。

密码流是否不支持这可能是因为它是 OAuth2 而不是 OpenID Connect 流?


我错过了什么?我 真的 需要 create a custom ProfileService to add all these claims? I really felt the default ProfileService for TestUsers 应该已经这样做了吗??

我最终得到了以下结果(不确定它是解决方案还是解决方法),因为它对未来的访问者有什么价值:

new ApiResource("foo-api")
{
    Scopes =
    {
        new Scope("foo-api.with.roles", new[] { "role" }),
    }
}
new Client
{
    // Don't use RPO if you can prevent it. We use it here
    // because it's the easiest way to demo with users.
    ClientId = "legacy-rpo",
    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
    AllowAccessTokensViaBrowser = false,
    RequireClientSecret = false,
    AllowedScopes = { "foo-api", "foo-api.with.roles" },
}
new TestUser
{
    SubjectId = "EFG-456",
    Username = "mary",
    Password = "secret",
    Claims = { new Claim("role", "editor") },
}

然后像这样检索令牌:

const response = await fetch("/connect/token", {
    method: "POST",
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
    },
    body: new URLSearchParams({
        "grant_type": "password",
        "client_id": "legacy-rpo",
        "username": "mary",
        "password": "secret",
    }),
});

const json = await response.json();

console.log(json);

您应该能够 clone my sample-asp-net-core-auth-policies repository 和 运行 它开箱即用,以在工作中看到它。