微信公众号:趣编程ACE
关注可了解.NET日常开发技巧。如需源码,请公众号留言 源码;
上文回顾
【鉴权/授权】一步一步实现一个简易JWT鉴权
【鉴权/授权】自定义一个身份认证Handler
【鉴权/授权】基于角色的简单授权认证
如何基于JWT实现RefreshToken
在前面的几篇文章中,我一点一点分享了如何利用JWT鉴权、自定义鉴权、基于角色的授权等知识点,如果想了解的请参阅上文回顾
今天这篇文章将分享如何在.net6项目下生成并利用refreshToken进行身份鉴权,亲测可用。。。。素来围观。。。。
创建RefreshToken
1 /// <summary>2 /// 生成refreshToken的接口3 /// </summary>4 public interface IGenerateRefreshToken5 {6 string GenerateRefreshTokenStr();7 }89 /// <summary>
10 /// 生成RefreshToken的接口实例
11 /// </summary>
12 public class GenerateRefreshTokenImpl: IGenerateRefreshToken
13 {
14 /// <summary>
15 /// 生成32位随机字符的refreshToken
16 /// </summary>
17 /// <returns></returns>
18 public string GenerateRefreshTokenStr()
19 {
20 var refreshBytes = new byte[32];
21 using (var number = RandomNumberGenerator.Create())
22 {
23 number.GetBytes(refreshBytes);
24 return Convert.ToBase64String(refreshBytes);
25 }
26 }
27 }
上面就是一个32位随机字符的refreshToken的接口及其实现方法,有了这个接口服务之后,我们需要在内置的依赖容器里面注入这个服务,于是来到Program.cs文件里面
1// 注入生成refreshToken的服务
2builder.Services.AddSingleton<IGenerateRefreshToken, GenerateRefreshTokenImpl>();
接着我们在登录的逻辑里面除了要生成Token同时也要调用我们上方的refreshToken接口来生成RefreshToken,生成成功后将Token和RefreshToken包裹成一个实体--ResponseModel一并返回给客户端,于是我们来到AuthenticateImpl这个类里面的Login方法。
1 // 生成RefreshToken 其中_generateRefreshToken 是从构造函数中依赖注入进来
2 var refreshToken = _generateRefreshToken.GenerateRefreshTokenStr();
3 // 将refreshToken 缓存起来 这一步可以存到数据库,也可以采用分布式缓存来替代,后续再分享
4 RefreshTokens[userName] = refreshToken;
其中RefreshTokens这个字典类型是这样产生的
1public interface IAuthenticate
2{
3 ResponseModel Login(string userName,string password); // 登录方法
4 Dictionary<string, string> RefreshTokens { get; set; } // refreshToken 缓存
5 ResponseModel Refresh(string userName, RefreshDto refreshDto); // token失效后刷新方法
6}
然后AuthenticateImpl.cs方法实现这个属性。
Token失效后的Refresh方法(核心逻辑)
1[AllowAnonymous]
2[HttpPost("refresh")]
3public IActionResult Refresh(string userName ,RefreshDto refreshDto)
4{
5 return Ok(_authenticate.Refresh(userName, refreshDto));
6}
创建一个控制器,并书写一个Refresh的刷新方法,当我们前端发现Token失效的时候,我们将会携带失效的JWT以及之前登录时产生的RefreshToken来重新生成一个Token和RefreshToken ,以此来减轻对数据库查询压力。。。
那么这个Refresh方法的逻辑是什么呢?
1/// <summary>2/// 刷新验证 生成接口服务3/// </summary>4public interface IRefreshManager5{6 ResponseModel Refresh(RefreshDto refreshDto, Dictionary<string, string> dic, Claim[] claims);7}8/// <summary>9/// 刷新验证 生成接口实现
10/// </summary>
11public class RefreshManagerImpl:IRefreshManager
12 {
13 private readonly string _key;
14 private readonly IGenerateRefreshToken _generateRefreshToken;
15
16 public RefreshManagerImpl(string key, IGenerateRefreshToken generateRefreshToken)
17 {
18 this._key = key;
19 this._generateRefreshToken = generateRefreshToken;
20 }
21
22 public ResponseModel Refresh(RefreshDto refreshDto,Dictionary<string,string> dic,Claim[] claims)
23 {
24 var tokenHandler = new JwtSecurityTokenHandler();
25 SecurityToken validatedToken;
26 // 根据过期token获取一个 SecurityToken
27 var principal =tokenHandler.ValidateToken(refreshDto.JwtToken, new TokenValidationParameters()
28 {
29 ValidateIssuerSigningKey = true,
30 IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_key)),
31 ValidateIssuer = false,
32 ValidateAudience = false,
33 ValidateLifetime = false, // 不对tojen过期进行验证
34 }, out validatedToken);
35 var jwtToken = validatedToken as JwtSecurityToken;
36 // 算法验证
37 if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256,StringComparison.InvariantCultureIgnoreCase))
38 {
39 throw new SecurityTokenException("token 不合法");
40 }
41 // 判读refreshToken是否是此用户生成的
42 var userName = principal.Identity.Name;
43 if(refreshDto.RefreshToken!=dic[userName] && refreshDto.RefreshToken==null)
44 {
45 throw new SecurityTokenException("refreshToken 不合法");
46 }
47
48 // 生成新token
49 var jwtSecurityToken = new JwtSecurityToken(
50 claims: claims, // 需要一个Claims对象 需要根据用户信息自定义生成生成
51 expires: DateTime.UtcNow.AddSeconds(60),
52 signingCredentials: new SigningCredentials(
53 new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_key)),
54 SecurityAlgorithms.HmacSha256Signature)
55 );
56
57 // 生成新token 和 refreshToken
58 var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
59 var refreshToken = _generateRefreshToken.GenerateRefreshTokenStr();
60 // 返回结果
61 return new ResponseModel() { JwtToken = token, RefreshToken = refreshToken };
62 }
63
64
65 }
上述就是Refresh方法的核心逻辑(详情见注释),我们将这个接口服务注入到容器中,以供控制器Api接口调用
1builder.Services.AddSingleton<IRefreshManager>(x => new RefreshManagerImpl(key, x.GetService<IGenerateRefreshToken>()));
调用此服务 由IAuthenticate.cs里面的方法调用
1public ResponseModel Refresh(string userName,RefreshDto refreshDto)
2 {
3 var claims = new Claim[] { new Claim(ClaimTypes.Name, userName) };
4 var tokenModel = _refreshManager.Refresh(refreshDto, RefreshTokens, claims);
5 RefreshTokens[userName] = tokenModel.RefreshToken;
6 return tokenModel;
7 }
以上便是JWT 生成RefreshToken的方法的大概逻辑,加上前面写的几篇文章,身份鉴权授权就暂分享这么多,后面再补充,最后感谢趣编程ACE公众号以及其他帮我分享文章的平台,Thanks~