gpt4 book ai didi

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

转载 作者:我是一只小鸟 更新时间:2023-08-19 21:56:57 25 4
gpt4 key购买 nike

前言

笔者之前开发过一套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。如图所示:

  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();  
                          

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

参考资料

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

Refit | The automatic type-safe REST library for Xamarin and .NET (reactiveui.github.io) 。

最后此篇关于C#利用Refit实现JWT自动续期的文章就讲到这里了,如果你想了解更多关于C#利用Refit实现JWT自动续期的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com