ASP.NET?Core應(yīng)用JWT進(jìn)行用戶認(rèn)證及Token的刷新方案
本文將通過(guò)實(shí)際的例子來(lái)演示如何在ASP.NET Core中應(yīng)用JWT進(jìn)行用戶認(rèn)證以及Token的刷新方案
一、什么是JWT?
JWT(json web token)基于開放標(biāo)準(zhǔn)(RFC 7519),是一種無(wú)狀態(tài)的分布式的身份驗(yàn)證方式,主要用于在網(wǎng)絡(luò)應(yīng)用環(huán)境間安全地傳遞聲明。它是基于JSON的,所以它也像json一樣可以在.Net、JAVA、JavaScript,、PHP等多種語(yǔ)言使用。
為什么要使用JWT?
傳統(tǒng)的Web應(yīng)用一般采用Cookies+Session來(lái)進(jìn)行認(rèn)證。但對(duì)于目前越來(lái)越多的App、小程序等應(yīng)用來(lái)說(shuō),它們對(duì)應(yīng)的服務(wù)端一般都是RestFul 類型的無(wú)狀態(tài)的API,再采用這樣的的認(rèn)證方式就不是很方便了。而JWT這種無(wú)狀態(tài)的分布式的身份驗(yàn)證方式恰好符合這樣的需求。
二、JWT的組成:
JWT是什么樣子的呢?它就是下面這樣的一段字符串:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjAwMiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiLmnY7lm5siLCJuYmYiOjE1NjU5MjMxMjIsImV4cCI6MTU2NTkyMzI0MiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1NDIxNCIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTQyMTUifQ.Mrta7nftmfXeo_igBVd4rl2keMmm0rg0WkqRXoVAeik
它是由三段“亂碼”字符串通過(guò)兩個(gè)“.”連接在一起組成。官網(wǎng)https://jwt.io/提供了它的驗(yàn)證方式
它的三個(gè)字符串分別對(duì)應(yīng)了上圖右側(cè)的Header、Payload和Signature三部分。
Header
Header: { "alg": "HS256", "typ": "JWT" }
標(biāo)識(shí)加密方式為HS256,Token類型為JWT, 這段JSON通過(guò)Base64Url編碼形成上例的第一個(gè)字符串
Payload
Payload是JWT用于信息存儲(chǔ)部分,其中包含了許多種的聲明(claims)。
可以自定義多個(gè)聲明添加到Payload中,系統(tǒng)也提供了一些默認(rèn)的類型
- iss (issuer):簽發(fā)人
- exp (expiration time):過(guò)期時(shí)間
- sub (subject):主題
- aud (audience):受眾
- nbf (Not Before):生效時(shí)間
- iat (Issued At):簽發(fā)時(shí)間
- jti (JWT ID):編號(hào)
這部分通過(guò)Base64Url編碼生成第二個(gè)字符串。
Signature
Signature是用于Token的驗(yàn)證。它的值類似這樣的表達(dá)式:Signature = HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret),也就是說(shuō),它是通過(guò)將前兩個(gè)字符串加密后生成的一個(gè)新字符串。
所以只有擁有同樣加密密鑰的人,才能通過(guò)前兩個(gè)字符串獲得同樣的字符串,通過(guò)這種方式保證了Token的真實(shí)性。
三、認(rèn)證流程
大概的流程是這樣的:
- 認(rèn)證服務(wù)器:用于用戶的登錄驗(yàn)證和Token的發(fā)放。
- 應(yīng)用服務(wù)器:業(yè)務(wù)數(shù)據(jù)接口。被保護(hù)的API。
- 客戶端:一般為APP、小程序等。
認(rèn)證流程:
- 用戶首先通過(guò)登錄,到認(rèn)證服務(wù)器獲取一個(gè)Token。
- 在訪問(wèn)應(yīng)用服務(wù)器的API的時(shí)候,將獲取到的Token放置在請(qǐng)求的Header中。
- 應(yīng)用服務(wù)器驗(yàn)證該Token,通過(guò)后返回對(duì)應(yīng)的結(jié)果。
說(shuō)明:這只是示例方案,實(shí)際項(xiàng)目中可能有所不同。
- 對(duì)于小型項(xiàng)目,可能認(rèn)證服務(wù)和應(yīng)用服務(wù)在一起。本例通過(guò)分開的方式來(lái)實(shí)現(xiàn),使我們能更好的了解二者之間的認(rèn)證流程。
- 對(duì)于復(fù)雜一些的項(xiàng)目,可能存在多個(gè)應(yīng)用服務(wù),用戶獲取到的Token可以在多個(gè)分布式服務(wù)中被認(rèn)證,這也是JWT的優(yōu)勢(shì)之一。
關(guān)于JWT的文章很多,這里就不做過(guò)多介紹了。下面通過(guò)實(shí)際的例子來(lái)看一下 它是如何在ASP.NET Core 中應(yīng)用的。
四、應(yīng)用實(shí)例
上一節(jié)的圖:“JWT的認(rèn)證流程”中涉及到客戶端、認(rèn)證服務(wù)器、應(yīng)用服務(wù)器三部分,下面通過(guò)示例來(lái)對(duì)這三部分進(jìn)行模擬:
- 認(rèn)證服務(wù)器:新建一個(gè)WebApi的解決方案,名為FlyLolo.JWT.Server。
- 應(yīng)用服務(wù)器:新建一個(gè)WebApi的解決方案,名為FlyLolo.JWT.API。
- 客戶端:這里用Fiddler發(fā)送請(qǐng)求做測(cè)試。
認(rèn)證服務(wù)
首先新建一個(gè)ASP.NET Core 的解決方案WebApi的解決方案
將其命名為FlyLolo.JWT.Server。
首先新建一個(gè)TokenController用于登錄和Token的發(fā)放:
[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(); } }
它有個(gè)名為Get的Action用于接收提交的用戶名和密碼,并進(jìn)行驗(yàn)證,驗(yàn)證通過(guò)后,調(diào)用TokenHelper的CreateToken方法生成Token返回。
這里涉及到了User和TokenHelper兩個(gè)類。
User相關(guān):
public class User { public string Code { get; set; } public string Name { get; set; } public string Password { get; set; } }
由于只是Demo,User類只含有以上三個(gè)字段。在TemporaryData類中做了User的模擬數(shù)據(jù)
/// <summary> /// 虛擬數(shù)據(jù),模擬從數(shù)據(jù)庫(kù)或緩存中讀取用戶 /// </summary> public static class TemporaryData { private static List<User> Users = new List<User>() { 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)); } }
這只是模擬數(shù)據(jù),實(shí)際項(xiàng)目中應(yīng)該從數(shù)據(jù)庫(kù)或者緩存等讀取。
TokenHelper:
public class TokenHelper : ITokenHelper { private IOptions<JWTConfig> _options; public TokenHelper(IOptions<JWTConfig> 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 }; } }
通過(guò)CreateToken方法創(chuàng)建Token,這里有幾個(gè)關(guān)鍵參數(shù):
- issuer? ? ? ? ? ? Token發(fā)布者
- Audience? ? ? Token接受者
- expires? ? ? ? ? 過(guò)期時(shí)間
- IssuerSigningKey? 簽名秘鑰
對(duì)應(yīng)的Token代碼如下:
public class Token { public string TokenContent { get; set; } public DateTime Expires { get; set; } }
這樣通過(guò)TokenHelper的CreateToken方法生成了一個(gè)Token返回給了客戶端。到現(xiàn)在來(lái)看,貌似所有的工作已經(jīng)完成了。并非如此,我們還需要在Startup文件中做一些設(shè)置。
public class Startup { // 。。。。。。此處省略部分代碼 public void ConfigureServices(IServiceCollection services) { //讀取配置信息 services.AddSingleton<ITokenHelper, TokenHelper>(); services.Configure<JWTConfig>(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(); } //啟用認(rèn)證中間件 app.UseAuthentication(); app.UseMvc(); } }
這里用到了配置信息,在appsettings.json中對(duì)認(rèn)證信息做配置如下:
"JWT": { "Issuer": "FlyLolo", "Audience": "TestAudience", "IssuerSigningKey": "FlyLolo1234567890", "AccessTokenExpiresMinutes": "30" }
運(yùn)行這個(gè)項(xiàng)目,并通過(guò)Fidder以Get方式訪問(wèn)api/token?code=002&pwd=222222,返回結(jié)果如下:
{"tokenContent":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjAwMiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiLmnY7lm5siLCJuYmYiOjE1NjY3OTg0NzUsImV4cCI6MTU2NjgwMDI3NSwiaXNzIjoiRmx5TG9sbyIsImF1ZCI6IlRlc3RBdWRpZW5jZSJ9.BVf3gOuW1E9RToqKy8XXp8uIvZKL-lBA-q9fB9QTEZ4","expires":"2019-08-26T21:17:55.1183172+08:00"}
客戶端登錄成功并成功返回了一個(gè)Token,認(rèn)證服務(wù)創(chuàng)建完成
應(yīng)用服務(wù)
新建一個(gè)WebApi的解決方案,名為FlyLolo.JWT.API。
添加BookController用作業(yè)務(wù)API。
[Route("api/[controller]")] [Authorize] public class BookController : Controller { // GET: api/<controller> [HttpGet] [AllowAnonymous] public IEnumerable<string> Get() { return new string[] { "ASP", "C#" }; } // POST api/<controller> [HttpPost] public JsonResult Post() { return new JsonResult("Create Book ..."); } }
對(duì)此Controller添加了[Authorize]標(biāo)識(shí),表示此Controller的Action被訪問(wèn)時(shí)需要進(jìn)行認(rèn)證,而它的名為Get的Action被標(biāo)識(shí)了[AllowAnonymous],表示此Action的訪問(wèn)可以跳過(guò)認(rèn)證。
在Startup文件中配置認(rèn)證:
public class Startup { // 省略部分代碼 public void ConfigureServices(IServiceCollection services) { #region 讀取配置 JWTConfig config = new JWTConfig(); Configuration.GetSection("JWT").Bind(config); #endregion #region 啟用JWT認(rèn)證 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(); } }
這里同樣用到了配置:
public class 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" }
關(guān)于JWT認(rèn)證,這里通過(guò)options.TokenValidationParameters對(duì)認(rèn)證信息做了設(shè)置,ValidIssuer、ValidAudience、IssuerSigningKey這三個(gè)參數(shù)用于驗(yàn)證Token生成的時(shí)候填寫的Issuer、Audience、IssuerSigningKey,所以值要和生成Token時(shí)的設(shè)置一致。
ClockSkew默認(rèn)值為5分鐘,它是一個(gè)緩沖期,例如Token設(shè)置有效期為30分鐘,到了30分鐘的時(shí)候是不會(huì)過(guò)期的,會(huì)有這么個(gè)緩沖時(shí)間,也就是35分鐘才會(huì)過(guò)期。為了方便測(cè)試(不想等太長(zhǎng)時(shí)間),這里我設(shè)置了1分鐘。
TokenValidationParameters還有一些其他參數(shù),在它的構(gòu)造方法中已經(jīng)做了默認(rèn)設(shè)置,代碼如下:
public TokenValidationParameters() { RequireExpirationTime = true; RequireSignedTokens = true; SaveSigninToken = false; ValidateActor = false; ValidateAudience = true; //是否驗(yàn)證接受者 ValidateIssuer = true; //是否驗(yàn)證發(fā)布者 ValidateIssuerSigningKey = false; //是否驗(yàn)證秘鑰 ValidateLifetime = true; //是否驗(yàn)證過(guò)期時(shí)間 ValidateTokenReplay = false; }
訪問(wèn)api/book,正常返回了結(jié)果
["ASP","C#"]
通過(guò)POST方式訪問(wèn),返回401錯(cuò)誤。
這就需要使用獲取到的Toke了,如下圖方式再次訪問(wèn)
添加了“Authorization: bearer Token內(nèi)容”這樣的Header,可以正常訪問(wèn)了。
至此,簡(jiǎn)單的JWT認(rèn)證示例就完成了,代碼地址https://github.com/FlyLolo/JWT.Demo/releases/tag/1.0。
這里可能會(huì)有個(gè)疑問(wèn),例如:
- 1.Token被盜了怎么辦?
答: 在啟用Https的情況下,Token被放在Header中還是比較安全的。另外Token的有效期不要設(shè)置過(guò)長(zhǎng)。例如可以設(shè)置為1小時(shí)(微信公眾號(hào)的網(wǎng)頁(yè)開發(fā)的Token有效期為2小時(shí))。 - 2. Token到期了如何處理?
答:理論上Token過(guò)期應(yīng)該是跳到登錄界面,但這樣太不友好了。可以在后臺(tái)根據(jù)Token的過(guò)期時(shí)間定期去請(qǐng)求新的Token。下一節(jié)來(lái)演示一下Token的刷新方案。
五、Token的刷新
為了使客戶端能夠獲取到新的Token,對(duì)上文的例子進(jìn)行改造,大概思路如下:
- 用戶登錄成功的時(shí)候,一次性給他兩個(gè)Token,分別為AccessToken和RefreshToken,AccessToken用于正常請(qǐng)求,也就是上例中原有的Token,RefreshToken作為刷新AccessToken的憑證。
- AccessToken的有效期較短,例如一小時(shí),短一點(diǎn)安全一些。RefreshToken有效期可以設(shè)置長(zhǎng)一些,例如一天、一周等。
- 當(dāng)AccessToken即將過(guò)期的時(shí)候,例如提前5分鐘,客戶端利用RefreshToken請(qǐng)求指定的API獲取新的AccessToken并更新本地存儲(chǔ)中的AccessToken。
所以只需要修改FlyLolo.JWT.Server即可。
首先修改Token的返回方案,新增一個(gè)Model
public class ComplexToken { public Token AccessToken { get; set; } public Token RefreshToken { get; set; } }
包含AccessToken和RefreshToken,用于用戶登錄成功后的Token結(jié)果返回。
修改appsettings.json,添加兩個(gè)配置項(xiàng):
"RefreshTokenAudience": "RefreshTokenAudience", "RefreshTokenExpiresMinutes": "10080" //60*24*7
RefreshTokenExpiresMinutes用于設(shè)置RefreshToken的過(guò)期時(shí)間,這里設(shè)置了7天。RefreshTokenAudience用于設(shè)置RefreshToken的接受者,與原Audience值不一致,作用是使RefreshToken不能用于訪問(wèn)應(yīng)用服務(wù)的業(yè)務(wù)API,而AccessToken不能用于刷新Token。
修改TokenHelper:
public enum TokenType { AccessToken = 1, RefreshToken = 2 } public class TokenHelper : ITokenHelper { private IOptions<JWTConfig> _options; public TokenHelper(IOptions<JWTConfig> 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) //下面兩個(gè)Claim用于測(cè)試在Token中存儲(chǔ)用戶的角色信息,對(duì)應(yīng)測(cè)試在FlyLolo.JWT.API的兩個(gè)測(cè)試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) }; } /// <summary> /// 用于創(chuàng)建AccessToken和RefreshToken。 /// 這里AccessToken和RefreshToken只是過(guò)期時(shí)間不同,【實(shí)際項(xiàng)目】中二者的claims內(nèi)容可能會(huì)不同。 /// 因?yàn)镽efreshToken只是用于刷新AccessToken,其內(nèi)容可以簡(jiǎn)單一些。 /// 而AccessToken可能會(huì)附加一些其他的Claim。 /// </summary> /// <param name="claims"></param> /// <param name="tokenType"></param> /// <returns></returns> 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));//設(shè)置不同的過(guò)期時(shí)間 var token = new JwtSecurityToken( issuer: _options.Value.Issuer, audience: tokenType.Equals(TokenType.AccessToken) ? _options.Value.Audience : _options.Value.RefreshTokenAudience,//設(shè)置不同的接受者 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; } } }
在登錄后,生成兩個(gè)Token返回給客戶端。在TokenHelper添加了一個(gè)RefreshToken方法,用于生成新的AccessToken。對(duì)應(yīng)在TokenController中添加一個(gè)名為Post的Action,用于調(diào)用這個(gè)RefreshToken方法刷新Token
[HttpPost] [Authorize] public IActionResult Post() { return Ok(tokenHelper.RefreshToken(Request.HttpContext.User)); }
這個(gè)方法添加了[Authorize]標(biāo)識(shí),說(shuō)明調(diào)用它需要RefreshToken認(rèn)證通過(guò)。既然啟用了認(rèn)證,那么在Startup文件中需要像上例的業(yè)務(wù)API一樣做JWT的認(rèn)證配置。
public void ConfigureServices(IServiceCollection services) { #region 讀取配置信息 services.AddSingleton<ITokenHelper, TokenHelper>(); services.Configure<JWTConfig>(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的混用。
再次訪問(wèn)/api/token?code=002&pwd=222222,會(huì)返回兩個(gè)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去請(qǐng)求新的AccessToken
測(cè)試用AccessToken可以正常訪問(wèn)FlyLolo.JWT.API,用RefreshToken則不可以。
至此,Token的刷新功能改造完成。代碼地址:https://github.com/FlyLolo/JWT.Demo/releases/tag/1.1
疑問(wèn):RefreshToken有效期那么長(zhǎng),被盜了怎么辦,和直接將AccessToken的有效期延長(zhǎng)有什么區(qū)別?
個(gè)人認(rèn)為:
- 1. RefreshToken不像AccessToken那樣在大多數(shù)請(qǐng)求中都被使用。
- 2. 應(yīng)用類的API較多,對(duì)應(yīng)的服務(wù)(器)也可能較多,所以泄露的概率更大一些。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
asp.net使用jquery實(shí)現(xiàn)搜索框默認(rèn)提示功能
這篇文章主要介紹了asp.net使用jquery實(shí)現(xiàn)搜索框默認(rèn)提示功能,大家參考使用吧2014-01-01.NET6打包部署到Windows?Service的全過(guò)程
net用了這么久,雖然多數(shù)都是部署在centos系統(tǒng),但也有部署在windows上的情況,下面這篇文章主要給大家介紹了關(guān)于.NET6打包部署到Windows?Service的相關(guān)資料,需要的朋友可以參考下2022-10-10詳解將ASP.NET Core應(yīng)用程序部署至生產(chǎn)環(huán)境中(CentOS7)
這篇文章主要介紹了詳解將ASP.NET Core應(yīng)用程序部署至生產(chǎn)環(huán)境中(CentOS7),具有一定的參考價(jià)值,有需要的可以了解一下。2016-12-12Visual Studio 2017開發(fā)環(huán)境的安裝圖文教程
Visual Studio 2017是微軟于2017年3月8日正式推出的新版本,是迄今為止 最具生產(chǎn)力 的 Visual Studio 版本。這篇文章主要介紹了Visual Studio 2017開發(fā)環(huán)境的安裝,需要的朋友可以參考下2017-11-11ASP.NET中上傳并讀取Excel文件數(shù)據(jù)示例
如何打開Excel數(shù)據(jù)庫(kù)文件,想必有很多朋友都不清楚吧,下面通過(guò)一個(gè)簡(jiǎn)單的例子,實(shí)現(xiàn)讀取Excel數(shù)據(jù)文件2014-05-05.net/c# memcached緩存獲取所有緩存鍵的方法步驟
這篇文章主要介紹了.net/c# memcached緩存獲取所有緩存鍵的方法步驟,大家參考使用吧2013-12-12在 asp.net core 的中間件中返回具體的頁(yè)面的實(shí)現(xiàn)方法
這篇文章主要介紹了在 asp.net core 的中間件中返回具體的頁(yè)面的實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08asp. net下使用foreach簡(jiǎn)化文本文件的訪問(wèn)。
asp. net下使用foreach簡(jiǎn)化文本文件的訪問(wèn)。...2007-04-04詳解ASP.NET Core 之 Identity 入門(三)
本篇文章主要介紹了ASP.NET Core 之 Identity 入門,主要負(fù)責(zé)對(duì)用戶的身份進(jìn)行認(rèn)證,有興趣的可以了解一下。2016-12-12