C#利用Refit实现JWT自动续期

这篇具有很好参考价值的文章主要介绍了C#利用Refit实现JWT自动续期。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

笔者之前开发过一套C/S架构的桌面应用,采用了JWT作为用户的登录认证和授权。遇到的唯一问题就是JWT过期了该怎么办?设想当一个用户正在进行业务操作,突然因为Token过期失效,莫名其妙地跳转到登录界面,是不是一件很无语的事。当然笔者也曾想过:为何不把JWT的有效期尽量设长些(假设24小时),用户每天总要下班退出系统吧,呵呵!这显然有点投机取巧,也违背了JWT的安全设计,看来等另想他法。

设计思路

后来笔者的做法是:当客户端每次发起Http请求时,先判断本地Token是否存在: 1. 如果不存在,则先向服务端发起登录验证请求,从而获取Token。2. 如果已存在,则检测Token是否即将过期。如果是的话,就重新发起登录验证更新Token,否则继续使用当前Token。其中判断Token是否即将过期没有一个标准设定,个人认为在1~5分钟之间比较合适。 以上就是实现Token自动续期的整个过程。

知识准备

什么是JWT

JWT(JSON Web Token) 是一个开发标准 (RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。JWT是由头部 (Header)、载荷 (Payload) 和签名 (Signature) 三部分组成,它们之间用圆点(.)连接。JWT最常见的应用场景是授权(Authorization)和信息交换(Information Exchange)。

什么是Refit

Refit 是一个受到Square的Retrofit库(Java)启发的自动类型安全REST库。我们的应用程序通过Refit请求网络,实际上是使用Refit接口层封装请求参数、Header、Url等信息,之后由HttpClient完成后续的请求操作,在服务端返回数据之后,HttpClient将原始的结果交给Refit,后者根据用户的需求对结果进行解析的过程。

技术实现

我们需要先创建一个客户端和一个服务端。为了演示方便,客户端仍用WinForm,服务器使用ASP.NET Core Web API。如图所示:

C#利用Refit实现JWT自动续期

 JwtToken.Shared 公共类库:定义了一些POCO对象,供客户端/服务端共享使用。其中 TokenResult 定义如下:

 1     public record TokenResult
 2     {
 3         /// <summary>
 4         /// 访问令牌
 5         /// </summary>
 6         public string AccessToken { get; init; }
 7 
 8         /// <summary>
 9         /// 过期时间
10         /// </summary>
11         public DateTime ExpiredTime { get; init; }
12     }

服务端实现

JwtToken.Server 提供两个后台服务:一个是登录验证服务,为客户端颁发用户凭证(JWT),另一个是获取系统时间服务。

Program 启动类,我们需要添加和使用指定服务,从而开启JWT认证和授权。 代码如下:

 1     public class Program
 2     {
 3         public static void Main(string[] args)
 4         {
 5             var builder = WebApplication.CreateBuilder(args);
 6             builder.Services.AddControllers();
 7             builder.Services.AddAuthentication(options =>
 8             {
 9                 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
10                 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
11             })
12             .AddJwtBearer(o =>
13             {
14                 o.TokenValidationParameters = new TokenValidationParameters
15                 {
16                     NameClaimType = "Name",
17                     RoleClaimType = "Role",
18                     ValidateAudience = false,
19                     ValidateIssuer = false,
20                     ValidateLifetime = true,
21                     ClockSkew = TimeSpan.FromSeconds(30),
22                     IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtConsts.SigningKey))
23                 };
24             });
25             builder.Services.AddAuthorization();
26 
27             var app = builder.Build();
28             app.UseAuthentication();
29             app.UseAuthorization();
30             app.MapControllers();
31             app.Run();
32         }
33     }

DemoController 控制器:提供 LoginAsync() GetCurrentTimeAsync() 两个方法,代码如下:

 1     [ApiController]
 2     [Route("[controller]")]
 3     public class DemoController : ControllerBase
 4     {
 5         /// <summary>
 6         /// 登录
 7         /// </summary>
 8         /// <param name="dto"></param>
 9         /// <returns></returns>
10         [HttpPost("Login")]
11         public async ValueTask<TokenResult> LoginAsync(LoginDto dto)
12         {
13             var user = GetUserInfo(dto.UserName);
14             if (user.Password == dto.Password) // 登录密码验证
15             {
16                 TokenResult tokenResult = await JwtHelper.GenerateAsync(user.Id, user.UserName, user.Name, user.PhoneNumber);
17                 return tokenResult;
18             }
19             return null;
20         }
21 
22         /// <summary>
23         /// 获取当前时间
24         /// </summary>
25         /// <returns></returns>
26         [Authorize]
27         [HttpGet("CurrentTime")]
28         public ValueTask<DateTimeOffset> GetCurrentTimeAsync()
29         {
30             return ValueTask.FromResult(DateTimeOffset.Now);
31         }
32     }

第26行代码:给 GetCurrentTimeAsync() 加上 [Authorize] 特性后, 当前服务必须授权后才能访问。

第16行代码:根据用户的Id、用户名、姓名等信息来生成 TokenResult ,它包含JWT令牌和过期时间。下面是JWT的生成代码:

 1     public static class JwtHelper
 2     {
 3         /// <summary>
 4         /// 生成Token
 5         /// </summary>
 6         /// <returns></returns>
 7         public static ValueTask<TokenResult> GenerateAsync(int id, string username, string name, string phoneNumber)
 8         {
 9             var claims = new List<Claim>()
10             {
11                 new Claim("UserId", id.ToString()), // 用户Id
12                 new Claim("UserName", username),  // 用户名
13                 new Claim("Name", name) , // 姓名
14                 new Claim("PhoneNumber", phoneNumber) // 手机号码
15             };
16 
17             var tokenHandler = new JwtSecurityTokenHandler();
18             var expiresAt = DateTime.Now.AddMinutes(20); // 过期时间
19             var tokenDescriptor = new SecurityTokenDescriptor
20             {
21                 Subject = new ClaimsIdentity(claims),
22                 Expires = expiresAt,
23                 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtConsts.SigningKey)),
24                    SecurityAlgorithms.HmacSha256Signature)
25             };
26 
27             var token = tokenHandler.CreateToken(tokenDescriptor);
28             var tokenString = tokenHandler.WriteToken(token);
29 
30             return ValueTask.FromResult(new TokenResult
31             {
32                 AccessToken = tokenString,
33                 ExpiredTime = expiresAt
34             });
35         }
36     }

第18行代码:设置Token的过期时间,这里我们把有效期设为20分钟。

客户端实现

 JwtToken.Client 定义后台服务调用接口和实现Token自动续期。IDemoApi 接口定义如下:

 1     [Headers(new[] { "Authorization:Bearer" })]
 2     public interface IDemoApi
 3     {
 4         /// <summary>
 5         /// 获取当前时间
 6         /// </summary>
 7         /// <returns></returns>
 8         [Get("/Demo/CurrentTime")]
 9         Task<DateTimeOffset> GetCurrentTimeAsync();
10     }

第1行代码:给 IDemApi 接口加上 [Headers(...)] 特性,这样每次调用 GetCurrentTimeAsync() 方法,Http请求头部都会加上此信息。JWT的标准授权头部格式为:Authorization: Bearer <token>

接下来,就是实现Token自动续期功能。笔者封装了一个 RestHelper 类,核心代码如下:

 1     /// <summary>
 2     /// Rest请求服务
 3     /// </summary>
 4     /// <typeparam name="T"></typeparam>
 5     /// <returns></returns>
 6     public static T For<T>()
 7     {
 8         var settings = new RefitSettings()
 9         {
10             AuthorizationHeaderValueGetter = () => GetTokenAsync(),
11         };
12 
13         return RestService.For<T>(BaseUrl, settings);
14     }
15 
16     /// <summary>
17     /// 获取Token
18     /// </summary>
19     /// <returns></returns>
20     private static async Task<string> GetTokenAsync()
21     {
22         if (TokenResult is null || DateTimeOffset.Now.AddMinutes(1) >= TokenResult?.ExpiredTime)
23         {
24             var uri = new Uri($"{BaseUrl}/demo/login", UriKind.Absolute);
25 
26             var dto = new LoginDto { UserName = "fjq", Password = "123456" };
27 
28             using var httpResMsg = await new HttpClient().PostAsync(uri, JsonContent.Create(dto));
29 
30             if (httpResMsg.IsSuccessStatusCode)
31             {
32                 var jsonStr = await httpResMsg.Content.ReadAsStringAsync();
33 
34                 TokenResult = JsonHelper.FromJson<TokenResult>(jsonStr);
35             }
36         }
37 
38         return TokenResult?.AccessToken;
39     }

第10行代码:AuthorizationHeaderValueGetter 是 RefitSettings 对象的一个委托属性,用来提供授权头部信息,即JWT字符串。

第22至35行代码:即按照笔者前面的思路转换成代码实现,这里就不再详细说明了。

最后,我们用一行代码来获取后台系统时间:

1   var dt = await RestHelper.For<IDemoApi>().GetCurrentTimeAsync();  

界面运行效果如下(~亲测有效~):

C#利用Refit实现JWT自动续期

参考资料

认识JWT - 废物大师兄 - 博客园 (cnblogs.com)

Refit | The automatic type-safe REST library for Xamarin and .NET (reactiveui.github.io)文章来源地址https://www.toymoban.com/news/detail-655024.html

到了这里,关于C#利用Refit实现JWT自动续期的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • acme自动化---免费SSL证书申请并自动续期

    acme.sh 仓库地址:acme.sh acme.sh 中文说明:官方中文说明 各个 dnsapi 说明:dnsapi 安装 acme,后面my@example.com换成自己的邮箱 如果上面官方下载地址失败 或者 太慢,可以选用国内的备用地址 然后在 root 目录下 ls -a 就可以看到有一个 .acme.sh 的文件夹,进入后里面有个 account.conf

    2023年04月13日
    浏览(33)
  • 使用 Certbot 并设置自动续期 SSL 证书

    步骤: 安装 Certbot:使用命令安装 Certbot: 获取 SSL 证书:运行 Certbot 命令来获取并安装 SSL 证书。 示例命令,替换其中的域名和路径信息: 这将使用 Certbot 的 webroot 插件来进行验证,并为你的域名生成 SSL 证书。确保将 /path/to/your/website 替换为你网站的根目录路径, your-do

    2024年02月15日
    浏览(41)
  • certbot 申请免费SSL证书、自动续期

    Let’s Encrypt是证书颁发机构,Certbot是Let’s Encrypt的客户端,它们之间使用ACME协议通信,除了Certbot还有其他客户端,但官方推荐CertBot,Certbot是一个免费、开源的软件,它能自动从Let’s Encrypt下载证书、证书自动续期,支持nginx/tomcat,以往我们都从阿里云、腾讯云买证书,有

    2024年02月10日
    浏览(35)
  • 【redis】redis分布式锁(三)自动续期

    【redis】redis分布式锁(一)手写分布式锁1.0~6.0 【redis】redis分布式锁(二)可重入锁+设计模式 在主节点set的值,但是在复制给从机过程中宕机了,这时候从机上位是不能获取这个值的 CP: 故障:牺牲了高可用,即在一台master宕机后,选举新master时,zk服务不可用 AP: 在锁的

    2024年02月02日
    浏览(26)
  • C# &OpenCV 从零开发(0):前言

    由于我想换个机器视觉+运动控制的工作,我就开始了自学机器视觉方向的技术。但是Halcon毕竟是商业化的库,国内用盗版还是怕被告。所以期望使用OpenCV。 OpenCV目前已知的方法的有两个版本 Python:用起来挺简单的,就是Python的语言不适合管理,感觉以后必定会出现问题,不适

    2024年01月18日
    浏览(51)
  • 基于token的身份认证及自动续期的解决方案

        B/S架构大家应该都不陌生,web1.0时代,用户通过个人电脑浏览网站,单项获取信息,比如我们浏览新闻,查阅资料等。web2.0很快就到来,和1.0相比,用户可以随时随地分享自己的信息。这个时候就涉及到了我们的身份认证。当我们使用http无状态请求访问资源服务的时候,

    2023年04月16日
    浏览(47)
  • 用acme.sh给网站域名,申请免费SSL永久证书(自动续期)

    申请ssl证书,即https有很多,有免费的,也有收费的。如第三方域名管理cloudflare也可以自动添加使用https,而且永久。 但是由于有些服务,需要在服务器使用自签证书,所以需要自己申请。免费的可以使用certbot,也可以是使用zeroSSL。 Cerbot可以参考我以前的文章:Certbot申请免

    2024年01月23日
    浏览(43)
  • C# 利用 UI 自动化框架与应用程序的用户界面进行交互来模拟点击按钮

    ①需要引入命名空间: using System.Windows.Automation; ②添加两个引用: UIAutomationClient、UIAutomationTypes 当程序已经启动时, AutoClickLoginButton 方法会寻找名为\\\"FR\\\"的应用程序进程。然后,它使用 AutomationElement.FromHandle 从该进程的主窗口句柄获取根元素。 接着, FindLoginButton 方法被调用

    2024年01月25日
    浏览(67)
  • SSL 证书免费,自动续期的web服务器Caddy,Caddy2 实战

    Caddy官网 Caddy 是由go语言开发的web 服务器 ,和nginx 功能作用相同。但是区别在于caddy 没有很多的依赖,或者说是插件。并且 caddy 实现了 ssl 证书每三个月自动续期,ssl 证书免费 。这意味着 使用 caddy 作为web 服务器 不再有nginx 的 ssl 证书 到期且付费的困扰。 以上优点是我研

    2024年02月10日
    浏览(31)
  • acme.sh自动配置免费SSL泛域名证书并续期(Aliyun + Debian + nginx)

    以前使用Certbot自动配置SSL证书,需要安装snap管理器再安装Certbot,期间还要去找AliDNS脚本,比较麻烦。如果不想如此,推荐使用acme.sh自动化脚本,更方便快捷。 1. 安装acme.sh 脚本会安装home目录下:~/.acme.sh/。同时会创建一个cronjob,每天检测证书,快过期自动更新。 2. 更换证

    2024年02月04日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包