首页 > 解决方案 > IdentityServer4 System.ArgumentNullException:值不能为空。参数名称:尝试刷新令牌时的类型

问题描述

我将 identityserver4 v2.4 设置为用户 ASP.Net 身份和实体框架作为操作存储。当我tokenClient.RequestRefreshTokenAsync(oldRefreshToken);如下调用时,我总是invalid_granttokenResult

TokenClient tokenClient = new TokenClient(tokenEndpoint, clientId, clientSecret);
string oldRefreshToken = await context.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
TokenResponse tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);

IDS4 日志在日志中显示 ArgumentException(请注意,在我的情况下,参数名称存在很多类似的问题,value而不是与Parameter name: type我的情况相同。我认为它们不相关,因为为这些建议的修复对我不起作用)。IDS4 ClaimsConverter 中似乎出现了异常。我确实检查了我的 MySQL 数据库中 asp.net 用户和声明表中的任何空值字段,并确认没有:

[14:17:12 Debug] IdentityServer4.Validation.TokenRequestValidator
Start token request validation

[14:17:12 Debug] IdentityServer4.Validation.TokenRequestValidator
Start validation of refresh token request

[14:17:12 Debug] IdentityServer4.EntityFramework.Stores.PersistedGrantStore
pY3Q91B7RFXV2ilzuJtI+ggqkOg9xiRx4HcGZMfJf+0= found in database: True

[14:17:12 Error] IdentityServer4.Stores.DefaultRefreshTokenStore
Failed to deserialize JSON from grant store.
System.ArgumentNullException: Value cannot be null.
Parameter name: type
   at System.Security.Claims.Claim..ctor(String type, String value, String valueType, String issuer, String originalIssuer, ClaimsIdentity subject, String propertyKey, String propertyValue)
   at System.Security.Claims.Claim..ctor(String type, String value, String valueType)
   at IdentityServer4.Stores.Serialization.ClaimConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer) in C:\local\identity\server4\IdentityServer4\src\Storage\src\Stores\Serialization\ClaimConverter.cs:line 24
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
   at IdentityServer4.Stores.DefaultGrantStore`1.GetItemAsync(String key) in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\src\Stores\Default\DefaultGrantStore.cs:line 92

[14:17:12 Warning] IdentityServer4.Validation.TokenValidator
Invalid refresh token

[14:17:12 Warning] IdentityServer4.Validation.TokenRequestValidator
Refresh token validation failed. aborting.

我在 IDS4 中的客户端配置如下

new Client
{
    ClientId = "AspNetClient",
    ClientName = "ASP.Net Client",

    RedirectUris           = { "http://localhost:5003/signin-oidc" },
    PostLogoutRedirectUris = { "http://localhost:5003/signout-callback-oidc" },

    AllowedGrantTypes = GrantTypes.Hybrid,
    RequireClientSecret = true,
    ClientSecrets =
    {
        new Secret("AspNetClientSecret".Sha256())
    },                    

    RequireConsent = false,
    AlwaysSendClientClaims = true,
    AlwaysIncludeUserClaimsInIdToken = true,
    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        IdentityServerConstants.StandardScopes.Email,
        IdentityServerConstants.StandardScopes.OfflineAccess,
        "AccessTokenAuthorizedApi"
    },

    AllowOfflineAccess = true,                    
    AccessTokenLifetime = (int) TimeSpan.FromSeconds(10).TotalSeconds,
    RefreshTokenUsage = TokenUsage.ReUse,
    RefreshTokenExpiration = TokenExpiration.Absolute,
    AbsoluteRefreshTokenLifetime = (int) TimeSpan.FromDays(30).TotalSeconds,
    UpdateAccessTokenClaimsOnRefresh = true
}

我究竟做错了什么?

更新 2019-05-15:

所以我做了更多的挖掘,并使用在日志中找到的键检索了 PersistedGrants 表中的实际记录,即pY3Q91B7RFXV2ilzuJtI+ggqkOg9xiRx4HcGZMfJf+0=. 我可以在该字段的 JSON 文本中看到 4 个空声明,Data这肯定是导致问题的原因。但是为什么 IDS4 会添加这样的空声明呢?

{
   "CreationTime":"2019-05-15T08:47:03Z",
   "Lifetime":2592000,
   "AccessToken":{
      "Audiences":[
         "http://localhost:5000/resources",
         "AccessTokenAuthorizedApi"
      ],
      "Issuer":"http://localhost:5000",
      "CreationTime":"2019-05-15T08:47:03Z",
      "Lifetime":10,
      "Type":"access_token",
      "ClientId":"AspNetClient",
      "AccessTokenType":0,
      "Claims":[
         {
            "Type":"client_id",
            "Value":"AspNetClient",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"scope",
            "Value":"openid",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"scope",
            "Value":"profile",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"scope",
            "Value":"email",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"scope",
            "Value":"AccessTokenAuthorizedApi",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"scope",
            "Value":"offline_access",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"sub",
            "Value":"0170490c-24c9-4be4-ae2a-5d4b1c55346e",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"auth_time",
            "Value":"1557910023",
            "ValueType":"http://www.w3.org/2001/XMLSchema#integer"
         },
         {
            "Type":"idp",
            "Value":"local",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"amr",
            "Value":"pwd",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"AspNet.Identity.SecurityStamp",
            "Value":"IF6RRMICBOOITA56FEJLPTFBXCIUKW3Z",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"role",
            "Value":"Driver",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"role",
            "Value":"Helper",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"preferred_username",
            "Value":"hybrid1",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {
            "Type":"name",
            "Value":"hybrid1",
            "ValueType":"http://www.w3.org/2001/XMLSchema#string"
         },
         {

         },
         {

         },
         {

         },
         {

         }
      ],
      "Version":4
   },
   "Version":4
}

标签: asp.net-identityidentityserver4claims-based-identityrefresh-tokenidentitymodel

解决方案


好的,看起来 IDS4 没有像我预期的那样序列化派生的声明对象。我正在使用一个自定义的 TenantClaim 类,它继承了 Claim 并在 IdentityServer 项目的自定义 IProfileService 实现中添加了 4 个这样的对象,如下所示:

public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
    // Other Code

    var tenantRoleClaim = new TenantRoleClaim(tenantRole.TenantId, tenantRole.Role);
    claims.Add(tenantRoleClaim);

    context.IssuedClaims = claims;
}

一旦我将其更改为使用普通的旧 Claim 类型对象,代码就起作用了。

public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
    // Other Code

    var tenantRoleClaim = new TenantRoleClaim(tenantRole.TenantId, tenantRole.Role);
    claims.Add(new Claim(tenantRoleClaim.Type, tenantRoleClaim.Value));

    context.IssuedClaims = claims;
}

推荐阅读