> 文档中心 > 访问令牌(AccessToken)与刷新令牌(RefreshToken)

访问令牌(AccessToken)与刷新令牌(RefreshToken)

源码点我

闲着无聊的我又来了,今天介绍一下访问令牌(AccessToken)与刷新令牌(RefreshToken)。

最近公司来了几个Java,然后空气顿时充满了学术的氛围,每天都弥漫着垃圾回收,内存分配,堆栈等等各种技术问题的讨论,竖起了我的小耳朵,我偶然间听到了这个Token过期的问题,今天来说一说。

众所周知,JWT是我们常用的Token类型之一,但是JWT是无状态的,所有的信息,包括超时时间都会编码在JWT中。如果Token设置的超时时间过长会显得有些不安全,如果设置的过短则必须频繁的登录。

在IdentityServer4中,我们可以通过设置AllowOfflineAccess = true为Client设置一个刷新令牌,该令牌可以在Token失效返回401的时候去访问刷新接口,从而获得一个新的访问令牌和一个刷新令牌,但是我们的项目中好像没有用IdentityServer4啊,往下看。

1、打开我们之前的NET6.API项目,我们在LoginView中增加一个字段RefreshToken,并且在登录的时候生成一个Guid(你可以随意定义)作为RefreshToken返回给客户端,通过cache或者redis将该RefreshToken缓存起来,设置一个较长的缓存时间,例如30天。
访问令牌(AccessToken)与刷新令牌(RefreshToken)
访问令牌(AccessToken)与刷新令牌(RefreshToken)
2、调用登录接口,发现除了AccessToken,RefreshToken也返回了。
访问令牌(AccessToken)与刷新令牌(RefreshToken)
3、接着我们新增一个接口,叫做刷新访问令牌。

 ///  /// 刷新访问令牌 ///  ///  ///  [AllowAnonymous] [HttpPost("refresh")] [ProducesResponseType(typeof(LoginView), StatusCodes.Status200OK)] public async Task<IActionResult> RefreshAsync(RefreshTokenDto dto) {     try     {  var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(dto.AccessToken);  var userid = jwtToken.Claims.FirstOrDefault(a => a.Type == ClaimTypes.NameIdentifier)?.Value;  var username = jwtToken.Claims.FirstOrDefault(a => a.Type == ClaimTypes.Name)?.Value;  var refreshtoken = CacheHelper.Get<string>($"{CacheEnum.刷新令牌}_{userid}");  if (refreshtoken == null) return Ok(JsonView("未找到该刷新令牌"));  if (refreshtoken != dto.RefreshToken) return Ok(JsonView("刷新令牌不正确"));  #region 签发JWT  //生成一个新的刷新令牌  refreshtoken = CommonFun.GUID;  CacheHelper.Set($"{CacheEnum.刷新令牌}_{userid}", refreshtoken, TimeSpan.FromDays(30));  var view = new LoginView  {      Expires = DateTime.Now.AddDays(7),      RefreshToken = refreshtoken  };  var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["JwtSecurityKey"]));  var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);  var token = new JwtSecurityToken(      issuer: "net6api.com",      audience: "net6api.com",      claims: jwtToken.Claims,      expires: view.Expires,      signingCredentials: creds);  view.AccessToken = new JwtSecurityTokenHandler().WriteToken(token);  return Ok(JsonView(view));  #endregion     }     catch (Exception)     {  return Ok(JsonView("访问令牌解析失败"));     } }

4、我们调用刷新访问令牌接口,传入旧的AccessToken和RefreshToken,发现接口已经可以成功返回了一组新的Token。
访问令牌(AccessToken)与刷新令牌(RefreshToken)
【注意】重点来了!重点来了!重点来了!因为无论是Token是失效还是过期,API返回的也都是401未授权状态,那么前端如何能知道当前的AccessToken是失效还是过期呢,我们可以通过以下手段来实现
打开Program,在AddAuthentication中新增一个JwtBearerEvents,用来监听Toke过期事件,并且往Header中添加一个自定义keyValu,然后我们调整Token的失效时间为5秒,再次尝试。

访问令牌(AccessToken)与刷新令牌(RefreshToken)
访问令牌(AccessToken)与刷新令牌(RefreshToken)
至此,前端可以通过Response中是否存在自定义Header来判断当前AccessToken是否过期。

前端逻辑如下:
调用login接口登录获取AccessToken和RefreshToken,然后各种调接口…
突然发现HTTPStateCode返回401,此时判断是否存在自定义Header,如果存在则调用refresh刷新Token,继续各种调接口…
如果不存在自定义Header,则重新调用login接口进行登录。
结束,继续摸鱼…

安全期查询