IdentityServer4 - 访问令牌仅包含子声明

IdentityServer4 - access token only contains sub claim

我终于能够从 IdentityServer 获得访问令牌,

然而,我从 IdentityServer 获得的访问令牌似乎只包含 sub 声明。

这是一个示例令牌

eyJhbGciOiJSUzI1NiIsImtpZCI6IjVCOTBDN0JBNkExMjI2RjEyMEU0QzJGOEQzMjIwMzAxIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MzcwNTI5MDIsImV4cCI6MTYzNzEzOTMwMiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzNTkiLCJhdWQiOiJ3ZWF0aGVyZm9yZWNhc3RzIiwiY2xpZW50X2lkIjoiU3NvQXBwbGljYXRpb25DbGllbnQiLCJjZW50cmFsLXRoZWNsaWVudCI6IlRoZSBTU08gY2xpZW50Iiwic3ViIjoiYTUxYjBkZWMtODQyYS00ZWMyLTgwMGEtMzRmYWQyNTRjZTBlIiwiYXV0aF90aW1lIjoxNjM3MDUyMDcyLCJpZHAiOiJsb2NhbCIsImp0aSI6IkQ3Rjc2MzgwQzNERDkzMzVERDVFMTE1NjE4MDkzNEUwIiwic2lkIjoiOEY1OUZFMjRDNkY1NTM2ODhEQzVCMTM5QjNFQUU1MTMiLCJpYXQiOjE2MzcwNTI5MDIsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJlbWFpbCIsIndlYXRoZXJmb3JlY2FzdHMucmVhZCIsIndlYXRoZXJmb3JlY2FzdHMud3JpdGUiXSwiYW1yIjpbInB3ZCJdfQ.MiDmgc7AzbVpogbp8ID3WvJ0eo4a30_taxR9EI9ylyJASSdOiNSsk-sGuW-YnJIzf668EQGkTym6FMIvOyTxem9DxxIs8nI_rboHLvuvj4e7CtJeELwbZyraZtAxjVjm9tn0BVRZxuskzb6XSq4xGrt2ag_E0Re5MeQOjtyL0EeMS5md5IEywfD7ThH7pIu8SofFvV5tAYbwO-OPd5YyqpPGKXslRtFlyc7lj9faQh-e2CRMql5rSwhJRqCiaIaLxvXk8ZwISfdhmuyzHA88xrzXkqTK_RElhq4PY_GqpRe64nMvIBrkSeoOGLzlQNE9wa58UypZFFV4l8Cpy3_P2Q

这是解码后的令牌

我应该能够使用此令牌调用 /connect/userinfo 端点

我可以在输出中看到这个 window:

IdentityServer4.Hosting.EndpointRouter: Debug: Request path /connect/userinfo matched to endpoint type Userinfo
IdentityServer4.Hosting.EndpointRouter: Debug: Endpoint enabled: Userinfo, successfully created handler: IdentityServer4.Endpoints.UserInfoEndpoint
IdentityServer4.Hosting.IdentityServerMiddleware: Information: Invoking IdentityServer endpoint: IdentityServer4.Endpoints.UserInfoEndpoint for /connect/userinfo
IdentityServer4.Endpoints.UserInfoEndpoint: Debug: Start userinfo request
IdentityServer4.Validation.BearerTokenUsageValidator: Debug: Bearer token found in header
IdentityServer4.Endpoints.UserInfoEndpoint: Trace: Calling into userinfo request validator: IdentityServer4.Validation.UserInfoRequestValidator
IdentityServer4.Validation.TokenValidator: Trace: Start access token validation
IdentityServer4.EntityFramework.Stores.ClientStore: Debug: SsoApplicationClient found in database: True
IdentityServer4.Stores.ValidatingClientStore: Trace: Calling into client configuration validator: IdentityServer4.Validation.DefaultClientConfigurationValidator
IdentityServer4.Stores.ValidatingClientStore: Debug: client configuration validation for client SsoApplicationClient succeeded.
IdentityServer4.EntityFramework.Stores.ClientStore: Debug: SsoApplicationClient found in database: True
IdentityServer4.Stores.ValidatingClientStore: Trace: Calling into client configuration validator: IdentityServer4.Validation.DefaultClientConfigurationValidator
IdentityServer4.Stores.ValidatingClientStore: Debug: client configuration validation for client SsoApplicationClient succeeded.
IdentityServer4.Validation.TokenValidator: Debug: Calling into custom token validator: IdentityServer4.Validation.DefaultCustomTokenValidator
IdentityServer4.Validation.TokenValidator: Debug: Token validation success
{
  "ValidateLifetime": true,
  "AccessTokenType": "Jwt",
  "ExpectedScope": "openid",
  "JwtId": "2FB8F8A941528DAF18D8C523BCC9A770",
  "Claims": {
    "nbf": 1637062004,
    "exp": 1637148404,
    "iss": "https://localhost:44359",
    "aud": "weatherforecasts",
    "client_id": "SsoApplicationClient",
    "central-theclient": "The SSO client",
    "sub": "959c9bfa-ed30-4638-9986-63cf1589eff8",
    "auth_time": 1637060908,
    "idp": "local",
    "jti": "2FB8F8A941528DAF18D8C523BCC9A770",
    "sid": "75C294FC15A544FE60E361B495EE5BCA",
    "iat": 1637062004,
    "scope": [
      "openid",
      "profile",
      "email",
      "weatherforecasts.read",
      "weatherforecasts.write"
    ],
    "amr": "pwd"
  }
}
IdentityServer4.Endpoints.UserInfoEndpoint: Trace: Calling into userinfo response generator: IdentityServer4.ResponseHandling.UserInfoResponseGenerator
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Creating userinfo response
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Scopes in access token: openid profile email weatherforecasts.read weatherforecasts.write
IdentityServer4.EntityFramework.Stores.ResourceStore: Debug: Found openid, profile, email identity scopes in database
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Requested claim types: 
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Information: Profile service returned the following claim types: 
IdentityServer4.Endpoints.UserInfoEndpoint: Debug: End userinfo request

您甚至可以在令牌验证期间看到我的自定义专用客户端声明。 但是只有 sub 声明从 identityserver userinfo 端点返回...

我该如何解决这个问题?

编辑

我添加了 ProfileService,它正在被调用。但正如 Dejan 已经提到的,RequestedClaimTypes 是空的。但是我通过添加

更新了数据库
[{"type":"email","identityResourceId":1003},{"type":"sub","identityResourceId":1003}]

记录到 IdentityResourceClaim table。在输出 window 中,我现在可以看到 email and sub claims are requested:

IdentityServer4.Validation.TokenValidator: Debug: Token validation success
{
  "ValidateLifetime": true,
  "AccessTokenType": "Jwt",
  "ExpectedScope": "openid",
  "JwtId": "C37AF164BF3A7DE6A28FA7538683248F",
  "Claims": {
    "nbf": 1637069285,
    "exp": 1637155685,
    "iss": "https://localhost:44359",
    "aud": "weatherforecasts",
    "client_id": "SsoApplicationClient",
    "central-theclient": "The SSO client",
    "sub": "959c9bfa-ed30-4638-9986-63cf1589eff8",
    "auth_time": 1637067659,
    "idp": "local",
    "email": "pieterjan@example.com",
    "name": "Pieterjan",
    "jti": "C37AF164BF3A7DE6A28FA7538683248F",
    "sid": "BBFA9FD0A06824FA4E982DB1D3669A86",
    "iat": 1637069285,
    "scope": [
      "openid",
      "profile",
      "email",
      "weatherforecasts.read",
      "weatherforecasts.write"
    ],
    "amr": "pwd"
  }
}
IdentityServer4.Endpoints.UserInfoEndpoint: Trace: Calling into userinfo response generator: IdentityServer4.ResponseHandling.UserInfoResponseGenerator
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Creating userinfo response
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Scopes in access token: openid profile email weatherforecasts.read weatherforecasts.write
IdentityServer4.EntityFramework.Stores.ResourceStore: Debug: Found openid, profile, email identity scopes in database
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Requested claim types: email sub
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Information: Profile service returned the following claim types: email name
IdentityServer4.Endpoints.UserInfoEndpoint: Debug: End userinfo request
IdentityServer4.Hosting.IdentityServerMiddleware: Trace: Invoking result: IdentityServer4.Endpoints.Results.UserInfoResult

代码终于进入了ExternalLoginCallback,但是await signInManager.GetExternalLoginInfoAsync() returns null now.

来自https://localhost:44359/connect/userinfo的回复是

{
    "email": "pieterjan@example.com",
    "name": "Pieterjan",
    "sub": "959c9bfa-ed30-4638-9986-63cf1589eff8"
}

userinfo 端点从 IdentityServer4.Services.IProfileService 调用 GetProfileDataAsync 以获取请求的声明值。因此,简单的解决方案是实施该服务。

假设您将用户管理器定义为 IMyUserManager 并且它具有此处引用的方法(GetClaimsForUserIsActive),简化的实现可能如下所示:

public class MyProfileService : IProfileService
{
    private readonly IMyUserManager userManager;

    public MyProfileService(IMyUserManager usermanager)
    {
        this.userManager = userManager;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        // context.RequestedClaimTypes will contain the claims you requested when invoking the token endpoint
        var myClaims = await userManager.GetClaimsForUser(context.Subject, context.RequestedClaimTypes);
        context.IssuedClaims = myClaims;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        context.IsActive = await userManager.IsActive(context.Subject);
    }
}

然后您需要配置 IdentityServer4 以使用此实现,方法是在启动时调用 AddProfileService,如下所示:

services.AddIdentityServer()
    // ...
    .AddProfileService<MyProfileService>();

我还应该补充一点,为了让您想要的声明出现在 context.RequestedClaimTypes 中,它需要与您在调用令牌端点时直接请求的范围相关联。

启动时,在配置 IdentityServer4 时,确保您提供给 AddIdentityResources 调用的身份资源包含所需的声明。