天天看點

jwt token 過期重新整理_.NET Core 2.2 應用JWT進行使用者認證及Token的重新整理一、什麼是JWT?二、JWT的組成:三、認證流程四、應用執行個體五、Token的重新整理

jwt token 過期重新整理_.NET Core 2.2 應用JWT進行使用者認證及Token的重新整理一、什麼是JWT?二、JWT的組成:三、認證流程四、應用執行個體五、Token的重新整理

本文将通過實際的例子來示範如何在ASP.NET Core中應用JWT進行使用者認證以及Token的重新整理方案

一、什麼是JWT?

JWT(json web token)基于開放标準(RFC 7519),是一種無狀态的分布式的身份驗證方式,主要用于在網絡應用環境間安全地傳遞聲明。它是基于JSON的,是以它也像json一樣可以在.Net、JAVA、JavaScript,、PHP等多種語言使用。為什麼要使用JWT?

傳統的Web應用一般采用Cookies+Session來進行認證。但對于目前越來越多的App、小程式等應用來說,它們對應的服務端一般都是RestFul 類型的無狀态的API,再采用這樣的的認證方式就不是很友善了。而JWT這種無狀态的分布式的身份驗證方式恰好符合這樣的需求。

二、JWT的組成:

JWT是什麼樣子的呢?它就是下面這樣的一段字元串:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjAwMiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiLmnY7lm5siLCJuYmYiOjE1NjU5MjMxMjIsImV4cCI6MTU2NTkyMzI0MiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1NDIxNCIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTQyMTUifQ.Mrta7nftmfXeo_igBVd4rl2keMmm0rg0WkqRXoVAeik
           

它是由三段“亂碼”字元串通過兩個“.”連接配接在一起組成。官網https://jwt.io/提供了它的驗證方式它的三個字元串分别對應了上圖右側的Header、Payload和Signature三部分。

Header:

Header:{"alg": "HS256", "typ": "JWT"}
           

辨別加密方式為HS256,Token類型為JWT, 這段JSON通過Base64Url編碼形成上例的第一個字元串

Payload

Payload是JWT用于資訊存儲部分,其中包含了許多種的聲明(claims)。可以自定義多個聲明添加到Payload中,系統也提供了一些預設的類型iss (issuer):簽發人exp (expiration time):過期時間sub (subject):主題aud (audience):閱聽人nbf (Not Before):生效時間iat (Issued At):簽發時間jti (JWT ID):編号

這部分通過Base64Url編碼生成第二個字元串。

Signature

Signature是用于Token的驗證。它的值類似這樣的表達式:Signature = HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret),也就是說,它是通過将前兩個字元串加密後生成的一個新字元串。

是以隻有擁有同樣加密密鑰的人,才能通過前兩個字元串獲得同樣的字元串,通過這種方式保證了Token的真實性。

三、認證流程

大概的流程是這樣的:

jwt token 過期重新整理_.NET Core 2.2 應用JWT進行使用者認證及Token的重新整理一、什麼是JWT?二、JWT的組成:三、認證流程四、應用執行個體五、Token的重新整理
  1. 認證伺服器:用于使用者的登入驗證和Token的發放。
  2. 應用伺服器:業務資料接口。被保護的API。
  3. 用戶端:一般為APP、小程式等。

認證流程:

  1.  使用者首先通過登入,到認證伺服器擷取一個Token。
  2. 在通路應用伺服器的API的時候,将擷取到的Token放置在請求的Header中。
  3. 應用伺服器驗證該Token,通過後傳回對應的結果。

說明:這隻是示例方案,實際項目中可能有所不同。

  1. 對于小型項目,可能認證服務和應用服務在一起。本例通過分開的方式來實作,使我們能更好的了解二者之間的認證流程。
  2. 對于複雜一些的項目,可能存在多個應用服務,使用者擷取到的Token可以在多個分布式服務中被認證,這也是JWT的優勢之一。

關于JWT的文章很多,這裡就不做過多介紹了。下面通過實際的例子來看一下 它是如何在ASP.NET Core 中應用的。

四、應用執行個體

上一節的圖:“JWT的認證流程”中涉及到用戶端、認證伺服器、應用伺服器三部分,下面通過示例來對這三部分進行模拟:

  1. 認證伺服器:建立一個WebApi的解決方案,名為FlyLolo.JWT.Server。
  2. 應用伺服器:建立一個WebApi的解決方案,名為FlyLolo.JWT.API。
  3. 用戶端:這裡用Fiddler發送請求做測試。

認證服務

首先建立一個ASP.NET Core 的解決方案WebApi的解決方案 

jwt token 過期重新整理_.NET Core 2.2 應用JWT進行使用者認證及Token的重新整理一、什麼是JWT?二、JWT的組成:三、認證流程四、應用執行個體五、Token的重新整理

将其命名為FlyLolo.JWT.Server。

首先建立一個TokenController用于登入和Token的發放:

[Route("api/[controller]")]public class TokenController : Controller{    private ITokenHelper tokenHelper = null;    public TokenController(ITokenHelper _tokenHelper)    {        tokenHelper = _tokenHelper;    }    [HttpGet]    public IActionResult Get(string code, string pwd)    {        User user = TemporaryData.GetUser(code);        if (null != user && user.Password.Equals(pwd))        {            return Ok(tokenHelper.CreateToken(user));        }        return BadRequest();    }}
           

它有個名為Get的Action用于接收送出的使用者名和密碼,并進行驗證,驗證通過後,調用TokenHelper的CreateToken方法生成Token傳回。

這裡涉及到了User和TokenHelper兩個類。

User相關:

public class User{    public string Code { get; set; }    public string Name { get; set; }    public string Password { get; set; }}
           

由于隻是Demo,User類隻含有以上三個字段。在TemporaryData類中做了User的模拟資料

/// /// 虛拟資料,模拟從資料庫或緩存中讀取使用者/// public static class TemporaryData{    private static List Users = new List() { new User { Code = "001", Name = "張三", Password = "111111" }, new User { Code = "002", Name = "李四", Password = "222222" } };    public static User GetUser(string code)    {        return Users.FirstOrDefault(m => m.Code.Equals(code));    }}
           

這隻是模拟資料,實際項目中應該從資料庫或者緩存等讀取。

TokenHelper:

public class TokenHelper : ITokenHelper{    private IOptions _options;    public TokenHelper(IOptions options)    {        _options = options;    }    public Token CreateToken(User user)    {        Claim[] claims = { new Claim(ClaimTypes.NameIdentifier,user.Code),new Claim(ClaimTypes.Name,user.Name) };        return CreateToken(claims);    }    private Token CreateToken(Claim[] claims)    {        var now = DateTime.Now;var expires = now.Add(TimeSpan.FromMinutes(_options.Value.AccessTokenExpiresMinutes));        var token = new JwtSecurityToken(            issuer: _options.Value.Issuer,            audience: _options.Value.Audience,            claims: claims,            notBefore: now,            expires: expires,            signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Value.IssuerSigningKey)), SecurityAlgorithms.HmacSha256));        return new Token { TokenContent = new JwtSecurityTokenHandler().WriteToken(token), Expires = expires };    }}
           

通過CreateToken方法建立Token,這裡有幾個關鍵參數:

  1. issuer            Token釋出者
  2. Audience      Token接受者
  3. expires          過期時間
  4. IssuerSigningKey  簽名秘鑰

對應的Token代碼如下:

public class Token{    public string TokenContent { get; set; }    public DateTime Expires { get; set; }}
           

這樣通過TokenHelper的CreateToken方法生成了一個Token傳回給了用戶端。到現在來看,貌似所有的工作已經完成了。并非如此,我們還需要在Startup檔案中做一些設定。

public class Startup{    // 。。。。。。此處省略部分代碼    public void ConfigureServices(IServiceCollection services)    {        //讀取配置資訊        services.AddSingleton();        services.Configure(Configuration.GetSection("JWT"));        //啟用JWT        services.AddAuthentication(Options =>        {            Options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;            Options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;        }).        AddJwtBearer();        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);    }    public void Configure(IApplicationBuilder app, IHostingEnvironment env)    {        if (env.IsDevelopment())        {            app.UseDeveloperExceptionPage();        }        //啟用認證中間件        app.UseAuthentication();        app.UseMvc();    }}
           

這裡用到了配置資訊,在appsettings.json中對認證資訊做配置如下:

"JWT": {    "Issuer": "FlyLolo",    "Audience": "TestAudience",    "IssuerSigningKey": "FlyLolo1234567890",    "AccessTokenExpiresMinutes": "30"  }
           

運作這個項目,并通過Fidder以Get方式通路api/token?code=002&pwd=222222,傳回結果如下:

{"tokenContent":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjAwMiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiLmnY7lm5siLCJuYmYiOjE1NjY3OTg0NzUsImV4cCI6MTU2NjgwMDI3NSwiaXNzIjoiRmx5TG9sbyIsImF1ZCI6IlRlc3RBdWRpZW5jZSJ9.BVf3gOuW1E9RToqKy8XXp8uIvZKL-lBA-q9fB9QTEZ4","expires":"2019-08-26T21:17:55.1183172+08:00"}
           

用戶端登入成功并成功傳回了一個Token,認證服務建立完成

應用服務

建立一個WebApi的解決方案,名為FlyLolo.JWT.API。

添加BookController用作業務API。

[Route("api/[controller]")][Authorize]public class BookController : Controller{    // GET: api/    [HttpGet]    [AllowAnonymous]    public IEnumerable<string> Get()    {        return new string[] { "ASP", "C#" };    }    // POST api/    [HttpPost]    public JsonResult Post()    {        return new JsonResult("Create  Book ...");    }}
           

對此Controller添加了[Authorize]辨別,表示此Controller的Action被通路時需要進行認證,而它的名為Get的Action被辨別了[AllowAnonymous],表示此Action的通路可以跳過認證。

在Startup檔案中配置認證:

public class Startup{// 省略部分代碼    public void ConfigureServices(IServiceCollection services)    {        #region 讀取配置        JWTConfig config = new JWTConfig();        Configuration.GetSection("JWT").Bind(config);        #endregion        #region 啟用JWT認證        services.AddAuthentication(options =>        {            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;        }).        AddJwtBearer(options =>        {            options.TokenValidationParameters = new TokenValidationParameters            {                ValidIssuer = config.Issuer,                ValidAudience = config.Audience,                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.IssuerSigningKey)),                ClockSkew = TimeSpan.FromMinutes(1)            };        });        #endregion        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);    }    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.    public void Configure(IApplicationBuilder app, IHostingEnvironment env)    {        if (env.IsDevelopment())        {            app.UseDeveloperExceptionPage();        }        app.UseAuthentication();        app.UseMvc();    }}
           

 這裡同樣用到了配置:

publicclass JWTConfig{    public string Issuer { get; set; }    public string Audience { get; set; }    public string IssuerSigningKey { get; set; }    public int AccessTokenExpiresMinutes { get; set; }}
           

 appsettings.json:

"JWT": {"Issuer": "FlyLolo","Audience": "TestAudience","IssuerSigningKey": "FlyLolo1234567890","AccessTokenExpiresMinutes": "30"}
           

關于JWT認證,這裡通過options.TokenValidationParameters對認證資訊做了設定,ValidIssuer、ValidAudience、IssuerSigningKey這三個參數用于驗證Token生成的時候填寫的Issuer、Audience、IssuerSigningKey,是以值要和生成Token時的設定一緻。

ClockSkew預設值為5分鐘,它是一個緩沖期,例如Token設定有效期為30分鐘,到了30分鐘的時候是不會過期的,會有這麼個緩沖時間,也就是35分鐘才會過期。為了友善測試(不想等太長時間),這裡我設定了1分鐘。

TokenValidationParameters還有一些其他參數,在它的構造方法中已經做了預設設定,代碼如下:

public TokenValidationParameters(){    RequireExpirationTime = true;    RequireSignedTokens = true;    SaveSigninToken = false;    ValidateActor = false;    ValidateAudience = true;  //是否驗證接受者    ValidateIssuer = true;   //是否驗證釋出者    ValidateIssuerSigningKey = false;  //是否驗證秘鑰    ValidateLifetime = true; //是否驗證過期時間    ValidateTokenReplay = false; }
           

 通路api/book,正常傳回了結果

["ASP","C#"]
           

通過POST方式通路,傳回401錯誤。

這就需要使用擷取到的Toke了,如下圖方式再次通路

jwt token 過期重新整理_.NET Core 2.2 應用JWT進行使用者認證及Token的重新整理一、什麼是JWT?二、JWT的組成:三、認證流程四、應用執行個體五、Token的重新整理

添加了“Authorization: bearer Token内容”這樣的Header,可以正常通路了。

至此,簡單的JWT認證示例就完成了,代碼位址https://github.com/FlyLolo/JWT.Demo/releases/tag/1.0。

這裡可能會有個疑問,例如:

1.Token被盜了怎麼辦?

答: 在啟用Https的情況下,Token被放在Header中還是比較安全的。另外Token的有效期不要設定過長。例如可以設定為1小時(微信公衆号的網頁開發的Token有效期為2小時)。

2. Token到期了如何處理?

答:理論上Token過期應該是跳到登入界面,但這樣太不友好了。可以在背景根據Token的過期時間定期去請求新的Token。下一節來示範一下Token的重新整理方案。

五、Token的重新整理

為了使用戶端能夠擷取到新的Token,對上文的例子進行改造,大概思路如下:

  1. 使用者登入成功的時候,一次性給他兩個Token,分别為AccessToken和RefreshToken,AccessToken用于正常請求,也就是上例中原有的Token,RefreshToken作為重新整理AccessToken的憑證。
  2. AccessToken的有效期較短,例如一小時,短一點安全一些。RefreshToken有效期可以設定長一些,例如一天、一周等。
  3. 當AccessToken即将過期的時候,例如提前5分鐘,用戶端利用RefreshToken請求指定的API擷取新的AccessToken并更新本地存儲中的AccessToken。

是以隻需要修改FlyLolo.JWT.Server即可。

首先修改Token的傳回方案,新增一個Model

public class ComplexToken{    public Token AccessToken { get; set; }    public Token RefreshToken { get; set; }}
           

包含AccessToken和RefreshToken,用于使用者登入成功後的Token結果傳回。

修改 appsettings.json,添加兩個配置項:

"RefreshTokenAudience": "RefreshTokenAudience",    "RefreshTokenExpiresMinutes": "10080" //60*24*7
           

RefreshTokenExpiresMinutes用于設定RefreshToken的過期時間,這裡設定了7天。RefreshTokenAudience用于設定RefreshToken的接受者,與原Audience值不一緻,作用是使RefreshToken不能用于通路應用服務的業務API,而AccessToken不能用于重新整理Token。

修改TokenHelper:

public enum TokenType{    AccessToken = 1,    RefreshToken = 2}public class TokenHelper : ITokenHelper{    private IOptions _options;    public TokenHelper(IOptions options)    {        _options = options;    }    public Token CreateAccessToken(User user)    {        Claim[] claims = new Claim[] { new Claim(ClaimTypes.NameIdentifier, user.Code), new Claim(ClaimTypes.Name, user.Name) };        return CreateToken(claims, TokenType.AccessToken);    }    public ComplexToken CreateToken(User user)    {        Claim[] claims = new Claim[] { new Claim(ClaimTypes.NameIdentifier, user.Code), new Claim(ClaimTypes.Name, user.Name)            //下面兩個Claim用于測試在Token中存儲使用者的角色資訊,對應測試在FlyLolo.JWT.API的兩個測試Controller的Put方法,若用不到可删除            , new Claim(ClaimTypes.Role, "TestPutBookRole"), new Claim(ClaimTypes.Role, "TestPutStudentRole")        };        return CreateToken(claims);    }    public ComplexToken CreateToken(Claim[] claims)    {        return new ComplexToken { AccessToken = CreateToken(claims, TokenType.AccessToken), RefreshToken = CreateToken(claims, TokenType.RefreshToken) };    }    ///     /// 用于建立AccessToken和RefreshToken。    /// 這裡AccessToken和RefreshToken隻是過期時間不同,【實際項目】中二者的claims内容可能會不同。    /// 因為RefreshToken隻是用于重新整理AccessToken,其内容可以簡單一些。    /// 而AccessToken可能會附加一些其他的Claim。    ///     ///     ///     ///     private Token CreateToken(Claim[] claims, TokenType tokenType)    {        var now = DateTime.Now;        var expires = now.Add(TimeSpan.FromMinutes(tokenType.Equals(TokenType.AccessToken) ? _options.Value.AccessTokenExpiresMinutes : _options.Value.RefreshTokenExpiresMinutes));//設定不同的過期時間        var token = new JwtSecurityToken(            issuer: _options.Value.Issuer,            audience: tokenType.Equals(TokenType.AccessToken) ? _options.Value.Audience : _options.Value.RefreshTokenAudience,//設定不同的接受者            claims: claims,            notBefore: now,            expires: expires,            signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Value.IssuerSigningKey)), SecurityAlgorithms.HmacSha256));        return new Token { TokenContent = new JwtSecurityTokenHandler().WriteToken(token), Expires = expires };    }    public Token RefreshToken(ClaimsPrincipal claimsPrincipal)    {        var code = claimsPrincipal.Claims.FirstOrDefault(m => m.Type.Equals(ClaimTypes.NameIdentifier));        if (null != code )        {            return CreateAccessToken(TemporaryData.GetUser(code.Value.ToString()));        }        else        {            return null;        }    }}
           

在登入後,生成兩個Token傳回給用戶端。在TokenHelper添加了一個RefreshToken方法,用于生成新的AccessToken。對應在TokenController中添加一個名為Post的Action,用于調用這個RefreshToken方法重新整理Token

[HttpPost][Authorize]public IActionResult Post(){    return Ok(tokenHelper.RefreshToken(Request.HttpContext.User));}
           

這個方法添加了[Authorize]辨別,說明調用它需要RefreshToken認證通過。既然啟用了認證,那麼在Startup檔案中需要像上例的業務API一樣做JWT的認證配置。

publicvoid ConfigureServices(IServiceCollection services){    #region 讀取配置資訊    services.AddSingleton();    services.Configure(Configuration.GetSection("JWT"));    JWTConfig config = new JWTConfig();    Configuration.GetSection("JWT").Bind(config);    #endregion    #region 啟用JWT    services.AddAuthentication(Options =>    {        Options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;        Options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;    }).     AddJwtBearer(options =>     {         options.TokenValidationParameters = new TokenValidationParameters         {             ValidIssuer = config.Issuer,             ValidAudience = config.RefreshTokenAudience,             IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.IssuerSigningKey))         };     });    #endregion    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);}
           

注意這裡的ValidAudience被指派為config.RefreshTokenAudience,和FlyLolo.JWT.API中的不一緻,用于防止AccessToken和RefreshToken的混用。

再次通路/api/token?code=002&pwd=222222,會傳回兩個Token:

{"accessToken":{"tokenContent":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjAwMiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiLmnY7lm5siLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiVGVzdFB1dEJvb2tSb2xlIiwiVGVzdFB1dFN0dWRlbnRSb2xlIl0sIm5iZiI6MTU2NjgwNjQ3OSwiZXhwIjoxNTY2ODA4Mjc5LCJpc3MiOiJGbHlMb2xvIiwiYXVkIjoiVGVzdEF1ZGllbmNlIn0.wlMorS1V0xP0Fb2MDX7jI7zsgZbb2Do3u78BAkIIwGg","expires":"2019-08-26T22:31:19.5312172+08:00"},"refreshToken":{"tokenContent":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjAwMiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiLmnY7lm5siLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiVGVzdFB1dEJvb2tSb2xlIiwiVGVzdFB1dFN0dWRlbnRSb2xlIl0sIm5iZiI6MTU2NjgwNjQ3OSwiZXhwIjoxNTY3NDExMjc5LCJpc3MiOiJGbHlMb2xvIiwiYXVkIjoiUmVmcmVzaFRva2VuQXVkaWVuY2UifQ.3EDi6cQBqa39-ywq2EjFGiM8W2KY5l9QAOWaIDi8FnI","expires":"2019-09-02T22:01:19.6143038+08:00"}}
           

可以使用RefreshToken去請求新的AccessToken

jwt token 過期重新整理_.NET Core 2.2 應用JWT進行使用者認證及Token的重新整理一、什麼是JWT?二、JWT的組成:三、認證流程四、應用執行個體五、Token的重新整理

測試用AccessToken可以正常通路FlyLolo.JWT.API,用RefreshToken則不可以。

至此,Token的重新整理功能改造完成。代碼位址:https://github.com/FlyLolo/JWT.Demo/releases/tag/1.1

疑問:RefreshToken有效期那麼長,被盜了怎麼辦,和直接将AccessToken的有效期延長有什麼差別?

個人認為:1. RefreshToken不像AccessToken那樣在大多數請求中都被使用。2. 應用類的API較多,對應的服務(器)也可能較多,是以洩露的機率更大一些。

jwt token 過期重新整理_.NET Core 2.2 應用JWT進行使用者認證及Token的重新整理一、什麼是JWT?二、JWT的組成:三、認證流程四、應用執行個體五、Token的重新整理
jwt token 過期重新整理_.NET Core 2.2 應用JWT進行使用者認證及Token的重新整理一、什麼是JWT?二、JWT的組成:三、認證流程四、應用執行個體五、Token的重新整理

點選 【在看】與好朋友一起分享

寫留言與我一起探讨

繼續閱讀