欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#利用Refit實(shí)現(xiàn)JWT自動(dòng)續(xù)期詳解

 更新時(shí)間:2023年08月18日 11:34:25   作者:天行健君子以自強(qiáng)  
Refit?是一個(gè)受到Square的Retrofit庫(Java)啟發(fā)的自動(dòng)類型安全REST庫,這篇文章主要為大家介紹了C#如何利用Refit實(shí)現(xiàn)JWT自動(dòng)續(xù)期,感興趣的可以了解下

前言

筆者之前開發(fā)過一套C/S架構(gòu)的桌面應(yīng)用,采用了JWT作為用戶的登錄認(rèn)證和授權(quán)。遇到的唯一問題就是JWT過期了該怎么辦?設(shè)想當(dāng)一個(gè)用戶正在進(jìn)行業(yè)務(wù)操作,突然因?yàn)門oken過期失效,莫名其妙地跳轉(zhuǎn)到登錄界面,是不是一件很無語的事。當(dāng)然筆者也曾想過:為何不把JWT的有效期盡量設(shè)長些(假設(shè)24小時(shí)),用戶每天總要下班退出系統(tǒng)吧,呵呵!這顯然有點(diǎn)投機(jī)取巧,也違背了JWT的安全設(shè)計(jì),看來等另想它法。

設(shè)計(jì)思路

后來筆者的做法是:當(dāng)客戶端每次發(fā)起Http請求時(shí),先判斷本地Token是否存在: 1. 如果不存在,則先向服務(wù)端發(fā)起登錄驗(yàn)證請求,從而獲取Token。2. 如果已存在,則檢測Token是否即將過期。如果是的話,就重新發(fā)起登錄驗(yàn)證更新Token,否則繼續(xù)使用當(dāng)前Token。其中判斷Token是否即將過期沒有一個(gè)標(biāo)準(zhǔn)設(shè)定,個(gè)人認(rèn)為在1~5分鐘之間比較合適。 以上就是實(shí)現(xiàn)Token自動(dòng)續(xù)期的整個(gè)過程。

知識(shí)準(zhǔn)備

什么是JWT

JWT(JSON Web Token) 是一個(gè)開發(fā)標(biāo)準(zhǔn) (RFC 7519),它定義了一種緊湊的、自包含的方式,用于作為JSON對象在各方之間安全地傳輸信息。該信息可以被驗(yàn)證和信任,因?yàn)樗菙?shù)字簽名的。JWT是由頭部 (Header)、載荷 (Payload) 和簽名 (Signature) 三部分組成,它們之間用圓點(diǎn)(.)連接。JWT最常見的應(yīng)用場景是授權(quán)(Authorization)和信息交換(Information Exchange)。

什么是Refit

Refit 是一個(gè)受到Square的Retrofit庫(Java)啟發(fā)的自動(dòng)類型安全REST庫。我們的應(yīng)用程序通過Refit請求網(wǎng)絡(luò),實(shí)際上是使用Refit接口層封裝請求參數(shù)、Header、Url等信息,之后由HttpClient完成后續(xù)的請求操作,在服務(wù)端返回?cái)?shù)據(jù)之后,HttpClient將原始的結(jié)果交給Refit,后者根據(jù)用戶的需求對結(jié)果進(jìn)行解析的過程。

技術(shù)實(shí)現(xiàn)

我們需要先創(chuàng)建客戶端和服務(wù)端。為了演示方便,客戶端仍用WinForm,服務(wù)器使用ASP.NET Core Web API。如圖所示:

JwtToken.Shared 公共類庫:定義了一些POCO對象,供客戶端/服務(wù)端共享使用。其中 TokenResult 定義如下:

public record TokenResult
    {
        /// <summary>
        /// 訪問令牌
        /// </summary>
        public string AccessToken { get; init; }
        /// <summary>
        /// 過期時(shí)間
        /// </summary>
        public DateTime ExpiredTime { get; init; }
    }

服務(wù)端實(shí)現(xiàn)

JwtToken.Server 提供兩個(gè)后臺(tái)服務(wù):一個(gè)是登錄驗(yàn)證服務(wù),為客戶端頒發(fā)用戶憑證(JWT),另一個(gè)是獲取系統(tǒng)時(shí)間服務(wù)。

在 Program 啟動(dòng)類,我們需要添加和使用指定服務(wù),從而開啟JWT認(rèn)證和授權(quán)。 代碼如下:

public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            builder.Services.AddControllers();
            builder.Services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(o =>
            {
                o.TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = "Name",
                    RoleClaimType = "Role",
                    ValidateAudience = false,
                    ValidateIssuer = false,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.FromSeconds(30),
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtConsts.SigningKey))
                };
            });
            builder.Services.AddAuthorization();
            var app = builder.Build();
            app.UseAuthentication();
            app.UseAuthorization();
            app.MapControllers();
            app.Run();
        }
    }

DemoController 控制器:提供 LoginAsync() 和 GetCurrentTimeAsync() 兩個(gè)方法,代碼如下:

[ApiController]
    [Route("[controller]")]
    public class DemoController : ControllerBase
    {
        /// <summary>
        /// 登錄
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost("Login")]
        public async ValueTask<TokenResult> LoginAsync(LoginDto dto)
        {
            var user = GetUserInfo(dto.UserName);
            if (user.Password == dto.Password) // 登錄密碼驗(yàn)證
            {
                TokenResult tokenResult = await JwtHelper.GenerateAsync(user.Id, user.UserName, user.Name, user.PhoneNumber);
                return tokenResult;
            }
            return null;
        }
        /// <summary>
        /// 獲取當(dāng)前時(shí)間
        /// </summary>
        /// <returns></returns>
        [Authorize]
        [HttpGet("CurrentTime")]
        public ValueTask<DateTimeOffset> GetCurrentTimeAsync()
        {
            return ValueTask.FromResult(DateTimeOffset.Now);
        }
    }

第26行代碼:給 GetCurrentTimeAsync() 加上 [Authorize] 特性后, 當(dāng)前服務(wù)必須授權(quán)后才能訪問。

第16行代碼:根據(jù)用戶的Id、用戶名、姓名等信息來生成 TokenResult ,它包含JWT令牌和過期時(shí)間。下面是JWT的生成代碼:

public static class JwtHelper
    {
        /// <summary>
        /// 生成Token
        /// </summary>
        /// <returns></returns>
        public static ValueTask<TokenResult> GenerateAsync(int id, string username, string name, string phoneNumber)
        {
            var claims = new List<Claim>()
            {
                new Claim("UserId", id.ToString()), // 用戶Id
                new Claim("UserName", username),  // 用戶名
                new Claim("Name", name) , // 姓名
                new Claim("PhoneNumber", phoneNumber) // 手機(jī)號碼
            };
            var tokenHandler = new JwtSecurityTokenHandler();
            var expiresAt = DateTime.Now.AddMinutes(20); // 過期時(shí)間
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),
                Expires = expiresAt,
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtConsts.SigningKey)),
                   SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            var tokenString = tokenHandler.WriteToken(token);
            return ValueTask.FromResult(new TokenResult
            {
                AccessToken = tokenString,
                ExpiredTime = expiresAt
            });
        }
    }

第18行代碼:設(shè)置Token的過期時(shí)間,這里我們把有效期設(shè)為20分鐘。

客戶端實(shí)現(xiàn)

 JwtToken.Client 定義后臺(tái)服務(wù)調(diào)用接口和實(shí)現(xiàn)Token自動(dòng)續(xù)期。IDemoApi 接口定義如下:

[Headers(new[] { "Authorization:Bearer" })]
    public interface IDemoApi
    {
        /// <summary>
        /// 獲取當(dāng)前時(shí)間
        /// </summary>
        /// <returns></returns>
        [Get("/Demo/CurrentTime")]
        Task<DateTimeOffset> GetCurrentTimeAsync();
    }

第1行代碼:給 IDemApi 接口加上 [Headers(...)] 特性,這樣每次調(diào)用 GetCurrentTimeAsync() 方法,Http請求頭部都會(huì)攜帶此信息。JWT的標(biāo)準(zhǔn)頭部格式為:Authorization: Bearer <token>。

接下來,就是實(shí)現(xiàn)Token自動(dòng)續(xù)期功能。筆者封裝了一個(gè) RestHelper 類,核心代碼如下:

/// <summary>
    /// Rest請求服務(wù)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public static T For<T>()
    {
        var settings = new RefitSettings()
        {
            AuthorizationHeaderValueGetter = () => GetTokenAsync(),
        };
        return RestService.For<T>(BaseUrl, settings);
    }
    /// <summary>
    /// 獲取Token
    /// </summary>
    /// <returns></returns>
    private static async Task<string> GetTokenAsync()
    {
        if (TokenResult is null || DateTimeOffset.Now.AddMinutes(1) >= TokenResult?.ExpiredTime)
        {
            var uri = new Uri($"{BaseUrl}/demo/login", UriKind.Absolute);
            var dto = new LoginDto { UserName = "fjq", Password = "123456" };
            using var httpResMsg = await new HttpClient().PostAsync(uri, JsonContent.Create(dto));
            if (httpResMsg.IsSuccessStatusCode)
            {
                var jsonStr = await httpResMsg.Content.ReadAsStringAsync();
                TokenResult = JsonHelper.FromJson<TokenResult>(jsonStr);
            }
        }
        return TokenResult?.AccessToken;
    }

第10行代碼:AuthorizationHeaderValueGetter 是 RefitSettings 對象的一個(gè)委托屬性,用來提供授權(quán)頭部信息,即JWT字符串。

第22至35行代碼:即按照筆者前面的思路轉(zhuǎn)換成代碼,這里就不多介紹了。

最后,我們通過下面一行代碼來獲取后臺(tái)系統(tǒng)時(shí)間:

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

界面運(yùn)行效果如下(親測有效):

到此這篇關(guān)于C#利用Refit實(shí)現(xiàn)JWT自動(dòng)續(xù)期詳解的文章就介紹到這了,更多相關(guān)C# Refit實(shí)現(xiàn)JWT自動(dòng)續(xù)期內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論