【问题标题】:Problem with JWT Token while Authenticating the user对用户进行身份验证时出现 JWT 令牌问题
【发布时间】:2021-06-09 16:30:11
【问题描述】:

我有经过授权的 web api,我已经实现了 JWT 刷新令牌安全性,一切正常。当我输入有效密码和用户名时,它会回复我访问令牌和刷新令牌。

所以问题是,当我请求令牌时,我将其取回并且该令牌将在 1 天后过期,但是当我更改用户凭据(如密码和角色)时,即使使用旧令牌发送请求,我也会保持身份验证或系统允许我访问资源。

这是我的 API

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet]
        public ActionResult<string> Foo()
        {
            return "Hello World";
        }
    }

这是我的启动类的配置方法

        public void ConfigureServices(IServiceCollection services)
        {
            var tokenKey = "This is my secret key of the token";
            var key = Encoding.ASCII.GetBytes(tokenKey);

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ClockSkew = TimeSpan.FromDays(10)
                };
                options.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = context =>
                    {
                        if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                        {
                            context.Response.Headers.Add("Token-Expired", "true");
                        }
                        return Task.CompletedTask;
                    }
                };
            });

            services.AddControllersWithViews(option =>
            {
                var policy = new AuthorizationPolicyBuilder()
                                .RequireAuthenticatedUser()
                                .RequireRole("Consumer", "Admin")
                                .Build();
                option.Filters.Add(new AuthorizeFilter(policy));
            });
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "CodeFactoryAPI", Version = "v1" });
            });

            services.AddDbContextPool<AdminContext>(opt => opt.UseSqlServer(Configuration.GetConnectionString("CodeFactoryAPI")));
            services.AddSingleton<string>(tokenKey);
        }

这是生成 JWT 令牌的代码

    public class AuthenticationService : IDisposable
    {
        private AdminContext context;
        private bool disposed = false;
        private readonly string tokenKey;

        public AuthenticationService(AdminContext context, string tokenKey)
        {
            this.context = context;
            this.tokenKey = tokenKey;
        }

        public async Task<(string accessToken, string refeshToken)> Authenticate(string userName, string password)
        {
            var consumer = await context.Users.FirstOrDefaultAsync(u => u.ConsumerName == userName &&
                                                                    u.Password == password);
            if (consumer is null)
                return default;

            consumer.Token = GenerateRefreshToken();
            consumer.IssuedToken = DateTime.Today;

            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(tokenKey);

            Claim[] claims = new Claim[] { new(ClaimTypes.Name, userName + ',' + password), new(ClaimTypes.Role, consumer.Role) };

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),
                Expires = DateTime.UtcNow.AddDays(1),
                SigningCredentials = new(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);

            context.Update(consumer);
            await context.SaveChangesAsync().ConfigureAwait(false);

            return (tokenHandler.WriteToken(token), consumer.Token);
        }

        public async Task<(string accessToken, string refeshToken)> ReAuthenticate(string refreshToken)
        {
            var consumer = await context.Users.FirstOrDefaultAsync(u => u.Token == refreshToken)
                                              .ConfigureAwait(false);
            if (consumer is null || consumer.IssuedToken == null || (DateTime.Today - consumer.IssuedToken.Value).Days > 30)
                return default;

            consumer.Token = GenerateRefreshToken();
            consumer.IssuedToken = DateTime.Today;

            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(tokenKey);
            var claims = new Claim[] { new(ClaimTypes.Name, consumer.ConsumerName + ',' + consumer.Password) };

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),
                Expires = DateTime.UtcNow.AddDays(1),
                SigningCredentials = new(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };

            var tokens = tokenHandler.CreateToken(tokenDescriptor);
            var acceessToken = tokenHandler.WriteToken(tokens);

            context.Update(consumer);
            await context.SaveChangesAsync().ConfigureAwait(false);

            return (acceessToken, consumer.Token);
        }

        public string GenerateRefreshToken()
        {
            var randomNumber = new byte[32];
            using var rng = RandomNumberGenerator.Create();
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
        }

        public void Dispose()
        {
            Dispose(true);
        }

        public void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
                context = null;
                disposed = true;
            }
        }
    }

所以当我更新任何用户数据时,即使尝试访问资源我也可以。

这是截图

enter image description here

如果我更新用户名和密码等用户数据

enter image description here

我尝试使用我保持身份验证的旧令牌访问资源

enter image description here

是什么导致了问题以及如何解决这个问题?

【问题讨论】:

  • 你试过设置TokenValidationParameters.ValidateLifetime = true吗?
  • @jegtugado 我试过了,但是没用
  • 如果您希望令牌恰好在到期值时到期,则应将 ClockSkew 设置为 TimeSpan.Zero。很难说为什么当您更改密码或角色时它会起作用。您是否可能没有初始角色,并且您的 ClockSkew 允许似乎已过期的旧令牌工作,因为您已将其设置为 TimeSpan.FromDays(10)
  • @jegtugado 即使我将 ClockSkew 设置为 TimeSpan.Zero 它也不起作用,请您解释一下“您是否可能没有初始角色和您的 ClockSkew”是什么意思。 .谢谢!
  • @SohamPatel 这不是错误。 JWT 在设计上就是这样工作的。除非您将每个颁发的令牌存储在服务器端并针对数据库验证任何传入的令牌,否则令牌将一直有效,直到达到其到期日期。这是 cookie 和 JWT 之间的区别之一

标签: c# .net-core jwt asp.net-core-webapi


【解决方案1】:

正如 cmets 中所解释的,JWT 令牌是经过签名的,这就是与将它们存储在服务器端相比,它们受到信任的原因。出于多种原因,例如无状态以及能够将登录/令牌处理(我称之为身份验证服务)等与实际资源服务分开,这是可取的。

为了能够“撤销”,引入了刷新令牌: 刷新令牌通常是长随机随机数,并且也保存在身份验证服务的服务器端。当用户登录时,服务将返回 short-lived access-token refresh-token。

用户现在可以像往常一样使用访问令牌访问资源服务。如果 access-token 过期,它可以使用他的 refresh-token 从 auth-service 获取新的 access-token,而无需任何用户输入。

现在到您的密码更改场景:在这种情况下,您将删除/无效保存在 auth-service 上的刷新令牌。因此,当当前的 access-token 过期时,用户将无法再获得一个新的。

简而言之:短期访问令牌和长期刷新令牌提供了一种撤销机制。

【讨论】:

  • 我同意你的观点,但是如果我已将访问令牌授予用户,有效期为 1 天,并且 1 小时后我想出于某种原因禁用它的令牌,那我该怎么办?
  • 您可以将访问令牌的生命周期缩短到您仍然认为可以撤销的时间。或者,如果您真的需要该功能,您还可以实现在验证 JWT 令牌时要查询的用户名黑名单。
猜你喜欢
  • 2019-08-04
  • 1970-01-01
  • 2013-08-23
  • 1970-01-01
  • 2019-12-04
  • 2020-03-25
  • 1970-01-01
  • 2018-08-29
  • 1970-01-01
相关资源
最近更新 更多