JwtSecurityToken 过期时间无效 .NET Core 3.1

Posted

技术标签:

【中文标题】JwtSecurityToken 过期时间无效 .NET Core 3.1【英文标题】:JwtSecurityToken Expiry Time Invalid .NET Core 3.1 【发布时间】:2021-07-05 11:20:13 【问题描述】:

我将我的应用程序从 .NET Core 2.2 升级到了 .NET Core 3.1。当我尝试使用 PostMan 测试我的端点时,我注意到我收到了 401 Unauth 错误。当我查看标题时,我发现到期时间无效:

我拿了以下不记名令牌:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiQm9iIiwibmJmIjoiMTYxNzk3Nzg1MSIsImV4cCI6IjE2MjMxNjE4NTYiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiQmFza2V0YmFsbCIsIlJ1Z2J5IiwiRm9vdGJhbGwiXX0.QRLuXFeopf7QZ1NUzWcctuSfnNXiPgc2UH7NxAuHYvw

我正在生成令牌端点并使用jwt.io 对其进行解码,exp 字段为“1623161856”。 我将其转换为 javascript 中的日期对象,它等于未来 60 天。

所以令牌肯定没有过期。不确定我在升级到 .NET Core 3.1 时是否遗漏了任何内容,但这里是相关代码:

Startup.cs我有

   public void ConfigureServices(IServiceCollection services)
    


        // Initial Setup
        services.AddMvc();
        services.AddSingleton<IConfiguration>(Configuration);
        // Call this in case you need aspnet-user-authtype/aspnet-user-identity
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        // Register the Swagger generator, defining one or more Swagger documents 
        services.AddSwaggerGen(c =>
        
            c.SwaggerDoc(Configuration["v1"], new OpenApiInfo  Title = Configuration["Sports"], Version = Configuration["v1] );
        );

        services.AddDataProtection();
        
        //Authentication Setup
        services.AddAuthentication(options =>
        
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        ).AddJwtBearer(options =>
        
            options.TokenValidationParameters = new TokenValidationParameters
            
                ValidateAudience = false,
                ValidateIssuer = false,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("AnythingYouWant")),
                ValidateLifetime = true,
                ClockSkew = TimeSpan.FromMinutes(5)
            ;
            options.SaveToken = true;
            options.Events = new JwtBearerEvents()
            
                OnTokenValidated = context =>
                
                    var accessToken = context.SecurityToken as JwtSecurityToken;
                    if (accessToken != null)
                    
                        ClaimsIdentity identity = context.Principal.Identity as ClaimsIdentity;
                        if (identity != null)
                        
                            identity.AddClaim(new Claim("access_token", accessToken.RawData));
                        
                    

                    return Task.CompletedTask;
                
            ;
        );
        services.AddAuthorization();
    

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
    
        if (env.IsDevelopment())
        
            app.UseDeveloperExceptionPage();
            app.UseBrowserLink();
        
        else
        
            app.UseExceptionHandler("/Home/Error");
        

        app.UseSwagger();

        app.UseSwaggerUI(c =>
        
            c.SwaggerEndpoint("/swagger/" + Configuration["v1"] + "/swagger.json", Configuration["Sports"]);
        );
        
        app.UseStaticFiles();
        app.UseRouting();
        app.UseCors();

        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        
            endpoints.MapControllerRoute("default", "controller=Home/action=Index/id?");
            endpoints.MapControllerRoute("swagger", "swagger/");
        );

        app.UseWelcomePage("/swagger");
    

并且令牌是由我的 api 端点之一生成的。该代码如图所示:

 [HttpPost("SportApi/Token")]
    [ServiceFilter(typeof(SportResourceFilter))]
    public IActionResult Create(string key)
    
       return new ObjectResult(GenerateToken(key));
    
 private string GenerateToken(string someKey)
    
        JwtSecurityToken token = new JwtSecurityToken();
        List<SportAPIKey> ro = new List<SportAPIKey>();

        if (!string.IsNullOrEmpty(someKey))
        

            using (StreamReader r = new StreamReader("keys.json"))
            
                string json = r.ReadToEnd();
                ro = JsonConvert.DeserializeObject<List<SportAPIKey>>(json);
            
            
            if (ro.Exists(sak => sak.SportAPIKeyValue.Equals(someKey)))
            
                SportAPIKey sportapikey = ro.Find(sak => sak.SportAPIKeyValue.Equals(someKey));
                List<Claim> lc = new List<Claim>();
                Claim claimClient = new Claim(ClaimTypes.Name, sportapikey.Client);
                lc.Add(claimClient);
                foreach (string team in sportapikey.Teams)
                    
                        lc.Add(new Claim(ClaimTypes.System, team.Trim()));
                    
                Claim claimEffDate = new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString());
                lc.Add(claimEffDate);
                int tokenLifespan = 60;
                Claim claimExpDate = new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddDays(tokenLifespan)).ToUnixTimeSeconds().ToString());
                lc.Add(claimExpDate);

                foreach (string sport in sportapikey.Sports.Split(","))
                
                    lc.Add(new Claim(ClaimTypes.Role, sport.Trim()));
                

                var claims = lc.ToArray();
                
                token = new JwtSecurityToken(
                    new JwtHeader(new SigningCredentials(
                        new SymmetricSecurityKey(Encoding.UTF8.GetBytes("AnythingYouWant")),
                                                 SecurityAlgorithms.HmacSha256)),
                    new JwtPayload(claims));
            
        

        return new JwtSecurityTokenHandler().WriteToken(token);
    

【问题讨论】:

【参考方案1】:

您的令牌包含时间戳nbfexp作为字符串:

  "nbf": "1617977851",
  "exp": "1623161856",

这是无效的。在 https:\jwt.io 上,当您将鼠标悬停在这些值上时,您会发现出现了问题。通常它显示时间戳,但在您的示例中它显示:“无效日期”:

JWT 中的时间戳应该是numerical values:

数字日期 一个 JSON 数值,表示从 1970-01-01T00:00:00Z UTC 直到指定的 UTC 日期/时间

值本身是正确的:

在生成声明期间发生错误:

Claim claimExpDate = new Claim(JwtRegisteredClaimNames.Exp, 
                               new DateTimeOffset(DateTime.Now.AddDays(tokenLifespan)).ToUnixTimeSeconds().ToString());
lc.Add(claimExpDate);

改为使用this constructor,它可以让您设置ClaimValueType:

new Claim(JwtRegisteredClaimNames.Exp, 
          new DateTimeOffset(DateTime.Now.AddDays(tokenLifespan)).ToUnixTimeSeconds().ToString(), 
          ClaimValueTypes.Integer64)

或者更好的是,让框架为你添加过期时间戳,使用this constructor:

public JwtSecurityToken (string issuer = default, string audience = default, System.Collections.Generic.IEnumerable<System.Security.Claims.Claim> claims = default, DateTime? notBefore = default, DateTime? expires = default, Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials = default);

例如:

JwtSecurityToken(expires: DateTime.UtcNow.AddDays(60),  ...)

【讨论】:

以上是关于JwtSecurityToken 过期时间无效 .NET Core 3.1的主要内容,如果未能解决你的问题,请参考以下文章

JwtSecurityToken 有最短过期时间吗?

System.IdentityModel.Tokens.JwtSecurityToken 自定义属性

JwtSecurityToken 理解与异常

无法从 JwtSecurityToken 获取签名

将 JwtSecurityToken 与 HttpClient 一起使用

是否将 AspNetUserClaims 中的添加声明添加到 JwtSecurityToken?