You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

IdentityServer4令牌过期后登出/重登触发ArgumentNullException问题求助

解决IdentityServer4中Access Token过期后登出/重登触发ArgumentNullException的问题

从你的异常堆栈信息能直接定位问题根源:在生成用户Claims的环节,Claim构造函数接收到了null值的value参数,而默认的UserClaimsPrincipalFactory(包括IdentityServer4.AspNetIdentity中的实现)没有做null值校验,刚好你的用户实体里某个用于生成Claim的属性为null,在token过期后重新验证身份时触发了这个异常。

下面是不需要修改NuGet包代码的可行解决方案:

1. 自定义UserClaimsPrincipalFactory,添加Null检查

创建一个自定义的Claims工厂,继承原有的UserClaimsPrincipalFactory,重写GenerateClaimsAsync方法,在生成Claim前先过滤掉null或空值的情况:

无角色版本(适用于未启用角色的项目)

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Linq;
using System.Threading.Tasks;

public class CustomUserClaimsPrincipalFactory<TUser> 
    : UserClaimsPrincipalFactory<TUser> 
    where TUser : class
{
    public CustomUserClaimsPrincipalFactory(
        UserManager<TUser> userManager,
        IOptions<IdentityOptions> optionsAccessor)
        : base(userManager, optionsAccessor)
    {
    }

    protected override async Task<ClaimsIdentity> GenerateClaimsAsync(TUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);
        
        // 移除所有值为null/空字符串的无效Claim
        var invalidClaims = identity.Claims.Where(c => string.IsNullOrWhiteSpace(c.Value)).ToList();
        foreach (var claim in invalidClaims)
        {
            identity.RemoveClaim(claim);
        }

        // 如果需要自定义生成额外Claim,这里也可以加入null检查
        // 示例:处理可能为null的自定义用户属性
        // var nickname = await UserManager.GetUserAsync(user, "Nickname");
        // if (!string.IsNullOrWhiteSpace(nickname))
        // {
        //     identity.AddClaim(new Claim("nickname", nickname));
        // }

        return identity;
    }
}

带角色版本(适用于启用角色的项目)

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Linq;
using System.Threading.Tasks;

public class CustomUserClaimsPrincipalFactory<TUser, TRole> 
    : UserClaimsPrincipalFactory<TUser, TRole> 
    where TUser : class
    where TRole : class
{
    public CustomUserClaimsPrincipalFactory(
        UserManager<TUser> userManager,
        RoleManager<TRole> roleManager,
        IOptions<IdentityOptions> optionsAccessor)
        : base(userManager, roleManager, optionsAccessor)
    {
    }

    protected override async Task<ClaimsIdentity> GenerateClaimsAsync(TUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);
        
        // 过滤无效Claim
        var invalidClaims = identity.Claims.Where(c => string.IsNullOrWhiteSpace(c.Value)).ToList();
        foreach (var claim in invalidClaims)
        {
            identity.RemoveClaim(claim);
        }

        return identity;
    }
}

2. 在依赖注入容器中注册自定义Factory

替换默认的Claims工厂实现,让系统使用我们的自定义版本:

.NET 5及之前(Startup.cs)

public void ConfigureServices(IServiceCollection services)
{
    // ... 其他服务注册代码 ...

    // 替换默认工厂(根据你的项目选择对应版本)
    services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomUserClaimsPrincipalFactory<ApplicationUser>>();
    
    // 如果启用了角色,使用带角色的版本
    // services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomUserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>();
}

.NET 6+(Program.cs)

builder.Services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomUserClaimsPrincipalFactory<ApplicationUser>>();

// 启用角色的项目替换为:
// builder.Services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomUserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>();

3. 额外排查:确保用户实体属性的完整性

检查你的自定义User实体(比如ApplicationUser),确认哪些属性会被用来生成Claims(比如UserName、Email、PhoneNumber等)。如果数据库中存在这些属性为null的用户记录,要么修改数据库约束确保非空,要么在自定义Factory中针对性处理(比如跳过该Claim或用空字符串替代)。

方案原理

默认的UserClaimsPrincipalFactory是NuGet包中的受保护代码无法直接修改,但我们可以通过依赖注入替换的方式,用自定义工厂覆盖默认实现,在生成Claims的环节加入null值校验,从根源避免触发ArgumentNullException,再也不用手动清除浏览器缓存和Cookie来临时解决问题。

内容的提问来源于stack exchange,提问作者BinaryNexus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:28:46