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

Unity JWT认证引发内存泄漏:BuildServiceProvider调用问题求助

游戏服务器Unity JWT认证模块内存泄漏排查问题

我正在排查游戏服务器的内存泄漏问题,已定位问题源于Unity JWT令牌的玩家认证模块。通过对需要认证的/global-ranking接口发起数千次请求并对比内存快照,发现关闭认证后泄漏停止,快照差异显示有大量CancellationTokenSource、日志对象等实例增长。


服务器认证配置代码

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
    {        
        options.IncludeErrorDetails = true;

        options.TokenValidationParameters = new TokenValidationParameters
        {
            IssuerSigningKeyResolver = GetRsaKey,
            ValidAudience = "TODO",
            ValidIssuer = "https://player-auth.services.api.unity.com",
            RequireSignedTokens = true,
            RequireExpirationTime = true,
            ValidateLifetime = true, 
            ValidateAudience = false,
            ValidateIssuer = true,
        };

        options.Events = new JwtBearerEvents()
        {
            OnTokenValidated = tokenValidationContext =>
            {
                if (!tokenValidationContext.Principal.HasClaim(x => x.Type == "sub"))
                    tokenValidationContext.Fail("Token does not contain data required to identify user");

                var userService = builder.Services.BuildServiceProvider().GetRequiredService<IUserService>();
                var user = userService.GetUserByUnityId(tokenValidationContext.Principal.UnityUserId()).Result;

                if (user == null)
                {
                    tokenValidationContext.Fail("Token does not match any known user");
                }
                else
                {
                    var claimsIdentity = (ClaimsIdentity)tokenValidationContext.Principal.Identity;
                    claimsIdentity.AddClaim(new Claim("userid", user.Id.ToString()));
                }

                return Task.CompletedTask;
            }
        };
    })
.AddJwtBearer("UserRegisterScheme", options =>
    // 用于注册接口,无需数据库中存在用户即可验证通过
    { 
        options.IncludeErrorDetails = true;

        options.TokenValidationParameters = new TokenValidationParameters
        {
            IssuerSigningKeyResolver = GetRsaKey,
            ValidAudience = "TODO",
            ValidIssuer = "https://player-auth.services.api.unity.com",
            RequireSignedTokens = true,
            RequireExpirationTime = true,
            ValidateLifetime = true,
            ValidateAudience = false,
            ValidateIssuer = true,
        };

        options.Events = new JwtBearerEvents()
        {
            OnTokenValidated = tokenValidationContext =>
            {
                if (!tokenValidationContext.Principal.HasClaim(x => x.Type == "sub"))
                    tokenValidationContext.Fail("Token does not contain data required to identify user");

                return Task.CompletedTask;
            }
        };
    });

内存泄漏根源与警告

两个JWT Bearer认证的TokenValidationParameters中,IssuerSigningKeyResolver需要从Unity HTTP接口获取动态更新的RSA密钥,因此通过服务器元数据服务实现该逻辑。但在Program.cs的GetRsaKey方法中,使用builder.Services.BuildServiceProvider()获取IServerMetadataService,这会创建多个单例实例并引发内存泄漏,同时收到以下警告:

Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'. server

对应的GetRsaKey代码:

IEnumerable<SecurityKey> GetRsaKey(
    string token,
    SecurityToken securityToken,
    string kid,
    TokenValidationParameters tokenValidationParameters)
{
    var keyParameters = builder.Services.BuildServiceProvider()
        .GetRequiredService<IServerMetadataService>()
        .GetUgsTokenKeySignatureParameters().Result;

    var rsaParameters = new RSAParameters()
    {
        Modulus = Base64UrlEncoder.DecodeBytes(keyParameters.modulus),
        Exponent = Base64UrlEncoder.DecodeBytes(keyParameters.exponent),
    };
    RSA rsa = RSA.Create(rsaParameters);
    RsaSecurityKey rsaKey = new RsaSecurityKey(rsa);
    return new List<SecurityKey>() { rsaKey };
}

当前困境

尝试多种解决方案后均未奏效:要么无法解决内存泄漏,要么触发“应用构建后无法修改ServiceCollection”的错误。陷入两难:应用构建前调用服务会导致单例重复创建引发泄漏,构建完成后又无法再修改认证配置。


相关控制器代码

[HttpGet("global-ranking")]
public async Task<ActionResult<IEnumerable<UserSeasonViewModel>>> GetGlobalRanking()
{
    var ranking = await _seasonService.GetGlobalRanking();

    if (ranking == null)
    {
        return BadRequest($"Ranking is empty");
    }
    return Ok(ranking);
}

相关服务代码

public async Task<IEnumerable<UserSeasonViewModel>> GetGlobalRanking()
{
    return await _context.UsersSeasonViewModel
        .FromSqlRaw(/* 查询语句 */)
        .AsNoTracking()
        .ToListAsync();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 07:00:21