简体   繁体   English

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

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

I'm trying to create a sandbox application, using the (legacy) Resource Owner Password flow in IdentityServer4.我正在尝试使用 IdentityServer4 中的(旧版)资源所有者密码流创建一个沙盒应用程序。 I've set up a brand new ASP.NET Core 3 project with these packages:我用这些包建立了一个全新的 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" />

I'm using the following startup sections:我正在使用以下启动部分:

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") },
        },
    })

And then I serve a static index.html file that calls the /connect/token endpoint like this:然后我提供一个 static index.html文件,该文件调用/connect/token端点,如下所示:

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
    }),
});

But it returns me an access_token that (decoded) looks like this:但它返回给我一个 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"
  ]
}

I'm missing e-mail, role, etc. as top-level entries in the access_token .我缺少电子邮件、角色等作为access_token中的顶级条目。

When digging through the source code, I see that the ProfileService for TestUsers should add all requested claims to the token via an extension method .在挖掘源代码时,我看到TestUsers 的 ProfileService应该通过扩展方法将所有请求的声明添加到令牌。 Most questions I found while googling my problem either do what I already do (or tried, see below), or are about other edge cases.我在谷歌搜索我的问题时发现的大多数问题要么是我已经做过的(或尝试过的,见下文),要么是关于其他边缘情况。

Many other threads also lead to Dominick Baier's post on roles , but there the problem is that the API side doesn't recognize the role.许多其他线程也导致Dominick Baier 关于角色的帖子,但问题是API 方面不识别角色。 My problem is that the role isn't included at all in the token.我的问题是该role根本不包含在令牌中。

What I've tried:我试过的:

  • Switching between "role" and JwtClaimTypes.Role in various places.在各个地方的"role"JwtClaimTypes.Role之间切换。
  • With and without IdentityResources有和没有IdentityResources
  • Digging through the IDS4 codebase to find the logic behind this挖掘 IDS4 代码库以找到其背后的逻辑

Footnote about ProfileService关于ProfileService的脚注

I've tried adding this:我试过添加这个:

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);
    }
}

to the AddIdentityServer() builder chain:AddIdentityServer()构建器链:

.AddProfileService<ProfileService>()

but the GetProfileDataAsync(...) method isn't being hit at all, no breakpoint triggers.但是GetProfileDataAsync(...)方法根本没有被命中,没有断点触发。 So that would suggest that the default TestUserProfileService would also never be hit, thus explaining the lack of claims in my tokens.所以这表明默认的TestUserProfileService也永远不会被击中,从而解释了我的令牌中缺少声明。

Is this not supported in Password Flow perhaps because it's an OAuth2 and not an OpenID Connect flow?密码流是否不支持这可能是因为它是 OAuth2 而不是 OpenID Connect 流?


What am I missing?我错过了什么? Do I really need to create a custom ProfileService to add all these claims?真的需要创建一个自定义ProfileService来添加所有这些声明吗? I really felt the default ProfileService for TestUser s should do this already??我真的觉得TestUser的默认ProfileService应该已经这样做了?

I ended up with the following (not sure if it's a solution or a workaround), for what it's worth to future visitors:我最终得到了以下结果(不确定它是解决方案还是解决方法),对于未来的访问者来说是什么价值:

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") },
}

And then retrieving tokens like this:然后像这样检索令牌:

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);

You should be able to clone my sample-asp-net-core-auth-policies repository and run it out of the box to see this at work.您应该能够克隆我的sample-asp-net-core-auth-policies存储库并开箱即用地运行它以查看它的工作情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM