详解ASP.NET Core Web Api之JWT刷新Token

如题,本节我们进入JWT最后一节内容,JWT本质上就是从身份认证服务器获取访问令牌,继而对于用户后续可访问受保护资源,但是关键问题是:访问令牌的生命周期到底设置成多久呢?见过一些使用JWT的童鞋会将JWT过期时间设置成很长,有的几个小时,有的一天,有的甚至一个月,这么做当然存在问题,如果被恶意获得访问令牌,那么可在整个生命周期中使用访问令牌,也就是说存在冒充用户身份,此时身份认证服务器当然也就是始终信任该冒牌访问令牌,若要使得冒牌访问令牌无效,唯一的方案则是修改密钥,但是如果我们这么做了,则将使得已授予的访问令牌都将无效,所以更改密钥不是最佳方案,我们应该从源头尽量控制这个问题,而不是等到问题呈现再来想解决之道,刷新令牌闪亮登场。

RefreshToken

什么是刷新令牌呢?刷新访问令牌是用来从身份认证服务器交换获得新的访问令牌,有了刷新令牌可以在访问令牌过期后通过刷新令牌重新获取新的访问令牌而无需客户端通过凭据重新登录,如此一来,既保证了用户访问令牌过期后的良好体验,也保证了更高的系统安全性,同时,若通过刷新令牌获取新的访问令牌验证其无效可将受访者纳入黑名单限制其访问,那么访问令牌和刷新令牌的生命周期设置成多久合适呢?这取决于系统要求的安全性,一般来讲访问令牌的生命周期不会太长,比如5分钟,又比如获取微信的AccessToken的过期时间为2个小时。接下来我将用两张表来演示实现刷新令牌的整个过程,可能有更好的方案,欢迎在评论中提出,学习,学习。我们新建一个:5000的WebApi用于身份认证,再新建一个:5001的客户端,首先点击【模拟登录获取Toen】获取访问令牌和刷新令牌,然后点击【调用客户端获取当前时间】,如下:

详解ASP.NET Core Web Api之JWT刷新Token

接下来我们新建一张用户表(User)和用户刷新令牌表(UserRefreshToken),结构如下:

public class User { public string Id { get; set; } public string Email { get; set; } public string UserName { get; set; } private readonly List<UserRefreshToken> _userRefreshTokens = new List<UserRefreshToken>(); public IEnumerable<UserRefreshToken> UserRefreshTokens => _userRefreshTokens; /// <summary> /// 验证刷新token是否存在或过期 /// </summary> /// <param></param> /// <returns></returns> public bool IsValidRefreshToken(string refreshToken) { return _userRefreshTokens.Any(d => d.Token.Equals(refreshToken) && d.Active); } /// <summary> /// 创建刷新Token /// </summary> /// <param></param> /// <param></param> /// <param></param> public void CreateRefreshToken(string token, string userId, double minutes = 1) { _userRefreshTokens.Add(new UserRefreshToken() { Token = token, UserId = userId, Expires = DateTime.Now.AddMinutes(minutes) }); } /// <summary> /// 移除刷新token /// </summary> /// <param></param> public void RemoveRefreshToken(string refreshToken) { _userRefreshTokens.Remove(_userRefreshTokens.FirstOrDefault(t => t.Token == refreshToken)); }

public class UserRefreshToken { public string Id { get; private set; } = Guid.NewGuid().ToString(); public string Token { get; set; } public DateTime Expires { get; set; } public string UserId { get; set; } public bool Active => DateTime.Now <= Expires; }

如上可以看到对于刷新令牌的操作我们将其放在用户实体中,也就是使用EF Core中的Back Fields而不对外暴露。接下来我们将生成的访问令牌、刷新令牌、验证访问令牌、获取用户身份封装成对应方法如下:

/// <summary> /// 生成访问令牌 /// </summary> /// <param></param> /// <returns></returns> public string GenerateAccessToken(Claim[] claims) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)); var token = new JwtSecurityToken( issuer: "http://localhost:5000", audience: "http://localhost:5001", claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddMinutes(1), signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) ); return new JwtSecurityTokenHandler().WriteToken(token); } /// <summary> /// 生成刷新Token /// </summary> /// <returns></returns> public string GenerateRefreshToken() { var randomNumber = new byte[32]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(randomNumber); return Convert.ToBase64String(randomNumber); } } /// <summary> /// 从Token中获取用户身份 /// </summary> /// <param></param> /// <returns></returns> public ClaimsPrincipal GetPrincipalFromAccessToken(string token) { var handler = new JwtSecurityTokenHandler(); try { return handler.ValidateToken(token, new TokenValidationParameters { ValidateAudience = false, ValidateIssuer = false, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)), ValidateLifetime = false }, out SecurityToken validatedToken); } catch (Exception) { return null; } }

当用户点击登录,访问身份认证服务器,登录成功后我们创建访问令牌和刷新令牌并返回,如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/5d8768a460568b52d81c999d92982fd5.html