来源:
   https://www.c-sharpcorner.com/article/handle-refresh-token-using-asp-net-core-2-0-and-json-web-token/

In this article , you will learn how to deal with the refresh token when you use jwt (JSON Web Token) as your access_token.

Backgroud

Many people choose jwt as their access_token when the client sends a request to the Resource Server.

However, before the client sends a request to the Resource Server, the client needs to get the access_token from the Authorization Server. After receiving and storing the access_token, the client uses access_token to send a request to the Resource Server.

But as all we know, the expired time for a jwt is too short. And we do not require the users to pass their name and password once more! At this time, the refresh_token provides a vary convenient way that we can use to exchange a new access_token.

The normal way may be as per the following.

I will use ASP.NET Core 2.0 to show how to do this work.

Requirement first

You need to install the SDK of .NET Core 2.0 preview and the VS 2017 preview.

Now, let's begin!

First of all, building a Resource Server

Creating an ASP.NET Core Web API project.

Edit the Program class to specify the url when we visit the API.

 public class Program  

 {  

 public static void Main(string[] args)  

     {  

         BuildWebHost(args).Run();  

     }  

 public static IWebHost BuildWebHost(string[] args) =>  

         WebHost.CreateDefaultBuilder(args)  

             .UseStartup<Startup>()  

             .UseUrls("http://localhost:5002")  

             .Build();  

 }  

Add a private method in Startup class which configures the jwt authorization. There are some differences when we use the lower version of .NET Core SDK.

 public void ConfigureJwtAuthService(IServiceCollection services)  

 {  

 var audienceConfig = Configuration.GetSection("Audience");  

 var symmetricKeyAsBase64 = audienceConfig["Secret"];  

 var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);  

 var signingKey = new SymmetricSecurityKey(keyByteArray);  

 var tokenValidationParameters = new TokenValidationParameters  

     {  

 // The signing key must match!  

         ValidateIssuerSigningKey = true,  

         IssuerSigningKey = signingKey,  

 // Validate the JWT Issuer (iss) claim  

         ValidateIssuer = true,  

         ValidIssuer = audienceConfig["Iss"],  

 // Validate the JWT Audience (aud) claim  

         ValidateAudience = true,  

         ValidAudience = audienceConfig["Aud"],  

 // Validate the token expiry  

         ValidateLifetime = true,  

         ClockSkew = TimeSpan.Zero  

     };  

     services.AddAuthentication(options =>  

     {  

         options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;  

         options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;  

     })  

     .AddJwtBearerAuthentication(o =>  

     {  

         o.TokenValidationParameters = tokenValidationParameters;  

     });  

 }  

And, we need to use this method in the ConfigureServices method.

 public void ConfigureServices(IServiceCollection services)  

 {  

 //configure the jwt   

     ConfigureJwtAuthService(services);  

     services.AddMvc();
}

Do not forget touse the authentication in the Configure method.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  

{  

    loggerFactory.AddConsole(Configuration.GetSection("Logging"));  

    loggerFactory.AddDebug();  

//use the authentication  

    app.UseAuthentication();  

    app.UseMvc();  

}  

The last step of our Resource Server is to edit the ValueController so that we can use the authentication when we visit this API.

[Route("api/[controller]")]  

public class ValuesController : Controller  

{  

// GET api/values/5  

    [HttpGet("{id}")]  

    [Authorize]  

public string Get(int id)  

    {  

return "visit by jwt auth";  

    }          

}  

  

Turn to the Authentication Server

How to design the authentication?

Here is my point of view,

When the client uses the parameters to get an access_token , the client needs to pass the parameters in the querystring are as follow:

Parameter Value
grant_type the value must be password
client_id the client_id is assigned by manager
client_secret the client_secret is assigned by manager
username the name of the user
password the password of the user

When the client use the parameters to refresh a expired access_token , the client need to pass the parameters in the querystring are as follow,

Parameter Value
grant_type the value must be refresh_token
client_id the client_id is assigned by manager
client_secret the client_secret is assigned by manager
refresh_token after authentication the server will return a refresh_token

Here is the implementation!

Create a new ASP.NET Core project and a new controller named TokenController.

 [Route("api/token")]  

 public class TokenController : Controller  

 {  

 //some config in the appsettings.json  

 private IOptions<Audience> _settings;  

 //repository to handler the sqlite database  

 private IRTokenRepository _repo;  

 public TokenController(IOptions<Audience> settings, IRTokenRepository repo)  

     {  

 this._settings = settings;  

 this._repo = repo;  

     }  

     [HttpGet("auth")]  

 public IActionResult Auth([FromQuery]Parameters parameters)  

     {  

 if (parameters == null)  

         {  

 return Json(new ResponseData  

             {  

                 Code = "",  

                 Message = "null of parameters",  

                 Data = null  

             });  

         }  

 if (parameters.grant_type == "password")  

         {  

 return Json(DoPassword(parameters));  

         }  

 else if (parameters.grant_type == "refresh_token")  

         {  

 return Json(DoRefreshToken(parameters));  

         }  

 else  

         {  

 return Json(new ResponseData  

             {  

                 Code = "",  

                 Message = "bad request",  

                 Data = null  

             });  

         }  

     }  

 //scenario 1 : get the access-token by username and password  

 private ResponseData DoPassword(Parameters parameters)  

     {  

 //validate the client_id/client_secret/username/passwo  

 var isValidated = UserInfo.GetAllUsers().Any(x => x.ClientId == parameters.client_id  

                                 && x.ClientSecret == parameters.client_secret  

                                 && x.UserName == parameters.username  

                                 && x.Password == parameters.password);  

 if (!isValidated)  

         {  

 return new ResponseData  

             {  

                 Code = "",  

                 Message = "invalid user infomation",  

                 Data = null  

             };  

         }  

 var refresh_token = Guid.NewGuid().ToString().Replace("-", "");  

 var rToken = new RToken  

         {  

             ClientId = parameters.client_id,  

             RefreshToken = refresh_token,  

             Id = Guid.NewGuid().ToString(),  

             IsStop =   

         };  

 //store the refresh_token   

 if (_repo.AddToken(rToken))  

         {  

 return new ResponseData  

             {  

                 Code = "",  

                 Message = "OK",  

                 Data = GetJwt(parameters.client_id, refresh_token)  

             };  

         }  

 else  

         {  

 return new ResponseData  

             {  

                 Code = "",  

                 Message = "can not add token to database",  

                 Data = null  

             };  

         }  

     }  

 //scenario 2 : get the access_token by refresh_token  

 private ResponseData DoRefreshToken(Parameters parameters)  

     {  

 var token = _repo.GetToken(parameters.refresh_token, parameters.client_id);  

 if (token == null)  

         {  

 return new ResponseData  

             {  

                 Code = "",  

                 Message = "can not refresh token",  

                 Data = null  

             };  

         }  

 if (token.IsStop == )  

         {  

 return new ResponseData  

             {  

                 Code = "",  

                 Message = "refresh token has expired",  

                 Data = null  

             };  

         }  

 var refresh_token = Guid.NewGuid().ToString().Replace("-", "");  

         token.IsStop = ;  

 //expire the old refresh_token and add a new refresh_token  

 var updateFlag = _repo.ExpireToken(token);  

 var addFlag = _repo.AddToken(new RToken  

         {  

             ClientId = parameters.client_id,  

             RefreshToken = refresh_token,  

             Id = Guid.NewGuid().ToString(),  

             IsStop =   

         });  

 if (updateFlag && addFlag)  

         {  

 return new ResponseData  

             {  

                 Code = "",  

                 Message = "OK",  

                 Data = GetJwt(parameters.client_id, refresh_token)  

             };  

         }  

 else  

         {  

 return new ResponseData  

             {  

                 Code = "",  

                 Message = "can not expire token or a new token",  

                 Data = null  

             };  

         }  

     }  

 //get the jwt token   

 private string GetJwt(string client_id, string refresh_token)  

     {  

 var now = DateTime.UtcNow;  

 var claims = new Claim[]  

         {  

 new Claim(JwtRegisteredClaimNames.Sub, client_id),  

 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),  

 new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)  

         };  

 var symmetricKeyAsBase64 = _settings.Value.Secret;  

 var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);  

 var signingKey = new SymmetricSecurityKey(keyByteArray);  

 var jwt = new JwtSecurityToken(  

             issuer: _settings.Value.Iss,  

             audience: _settings.Value.Aud,  

             claims: claims,  

             notBefore: now,  

             expires: now.Add(TimeSpan.FromMinutes()),  

             signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256));  

 var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);  

 var response = new  

         {  

             access_token = encodedJwt,  

             expires_in = (int)TimeSpan.FromMinutes().TotalSeconds,  

             refresh_token = refresh_token,  

         };  

 return JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented });  

     }  

 }  

Both above two scenarios only use one action , because the parameters are similar.

When the grant_type is password ,we will create a refresh_token and store this refresh_token to the sqlite database. And return the jwt toekn to the client.

When the grant_type is refresh_token ,we will expire or delete the old refresh_token which belongs to this client_id and store a new refresh_toekn to the sqlite database. And return the new jwt toekn to the client.

Note

I use a GUID as my refresh_token , because GUID is more easier to generate and manager , you can use a more complex value as the refresh token.

At last , Create a console app to test the refresh token.

class Program  

{  

static void Main(string[] args)  

    {  

        HttpClient _client = new HttpClient();  

        _client.DefaultRequestHeaders.Clear();  

        Refresh(_client);  

        Console.Read();  

    }  

private static void Refresh(HttpClient _client)  

    {  

var client_id = "";  

var client_secret = "";  

var username = "Member";  

var password = "";  

var asUrl = $"http://localhost:5001/api/token/auth?grant_type=password&client_id={client_id}&client_secret={client_secret}&username={username}&password={password}";  

        Console.WriteLine("begin authorizing:");  

        HttpResponseMessage asMsg = _client.GetAsync(asUrl).Result;  

        string result = asMsg.Content.ReadAsStringAsync().Result;  

var responseData = JsonConvert.DeserializeObject<ResponseData>(result);  

if (responseData.Code != "")  

        {  

            Console.WriteLine("authorizing fail");  

return;  

        }  

var token = JsonConvert.DeserializeObject<Token>(responseData.Data);  

        Console.WriteLine("authorizing successfully");              

        Console.WriteLine($"the response of authorizing {result}");              

        Console.WriteLine("sleep 2min to make the token expire!!!");  

        System.Threading.Thread.Sleep(TimeSpan.FromMinutes());  

        Console.WriteLine("begin to request the resouce server");  

var rsUrl = "http://localhost:5002/api/values/1";  

        _client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token.access_token);  

        HttpResponseMessage rsMsg = _client.GetAsync(rsUrl).Result;  

        Console.WriteLine("result of requesting the resouce server");  

        Console.WriteLine(rsMsg.StatusCode);  

        Console.WriteLine(rsMsg.Content.ReadAsStringAsync().Result);  

//refresh the token  

if (rsMsg.StatusCode == HttpStatusCode.Unauthorized)  

        {  

            Console.WriteLine("begin to refresh token");  

var refresh_token = token.refresh_token;  

            asUrl = $"http://localhost:5001/api/token/auth?grant_type=refresh_token&client_id={client_id}&client_secret={client_secret}&refresh_token={refresh_token}";  

            HttpResponseMessage asMsgNew = _client.GetAsync(asUrl).Result;  

            string resultNew = asMsgNew.Content.ReadAsStringAsync().Result;  

var responseDataNew = JsonConvert.DeserializeObject<ResponseData>(resultNew);  

if (responseDataNew.Code != "")  

            {  

                Console.WriteLine("refresh token fail");  

return;  

            }  

            Token tokenNew = JsonConvert.DeserializeObject<Token>(responseDataNew.Data);  

            Console.WriteLine("refresh token successful");  

            Console.WriteLine(asMsg.StatusCode);  

            Console.WriteLine($"the response of refresh token {resultNew}");  

            Console.WriteLine("requset resource server again");  

            _client.DefaultRequestHeaders.Clear();  

            _client.DefaultRequestHeaders.Add("Authorization", "Bearer " + tokenNew.access_token);  

            HttpResponseMessage rsMsgNew = _client.GetAsync("http://localhost:5002/api/values/1").Result;  

            Console.WriteLine("the response of resource server");  

            Console.WriteLine(rsMsgNew.StatusCode);  

            Console.WriteLine(rsMsgNew.Content.ReadAsStringAsync().Result);  

        }  

    }  

}  

We should pay attention to the request of the Resource Server!

We must add a HTTP header when we send a HTTP request : `Authorization:Bearer token`

Now , using the dotnet CLI command to run our three projects.

Here is the screenshot of the runninng result.

Note

  • In the console app, I do not store the access_token and the refresh_token, I just used them once . You should store them in your project ,such as the web app, you can store them in localstorage.
  • When the access_token is expired , the client should remove the expired access_toekn and because the short time will cause the token expired , we do not need to worry about the leakage of the token !

Summary

This article introduced an easy way to handle the refresh_token when you use jwt. Hope this will help you to understand how to deal with the tokens.

Handle Refresh Token Using ASP.NET Core 2.0 And JSON Web Token的更多相关文章

  1. .Net Core 3.0 Api json web token 中间件签权验证和 Cors 中间件处理跨域请求

    第一步:在Nuget上安装"Microsoft.AspNet.WebApi.Cors"包,并对api controller使用[EnableCors]特性以及Microsoft.A ...

  2. asp.net core 3.0 MVC JSON 全局配置

    asp.net core 3.0 MVC JSON 全局配置 System.Text.Json(default) startup配置代码如下: using System.Text.Encodings. ...

  3. 升级 ASP.NET Core 3.0 设置 JSON 返回 PascalCase 格式与 SignalR 问题

    由于一些 JS 组件要求 JSON 格式是 PascalCase 格式,新版本 ASP.NET Core 3.0 中默认移除了 Newtonsoft.Json ,使用了微软自己实现的 System.T ...

  4. ASP.NET Core 1.0 开发记录

    官方资料: https://github.com/dotnet/core https://docs.microsoft.com/en-us/aspnet/core https://docs.micro ...

  5. 推荐:【视频教程】ASP.NET Core 3.0 入门

    墙裂推荐了,免费,通俗易懂,唯一可惜的就是不是我录的,更可惜的是人家录制完了快半年了我还没看完... 版权归原作者所有,建议新手还是边看边实践吧,要不然过完一遍发现自己啥也没学会,不要眼高手低 [视频 ...

  6. ASP.NET Core 5.0中的Host.CreateDefaultBuilder执行过程

      通过Rider调试的方式看了下ASP.NET Core 5.0的Web API默认项目,重点关注Host.CreateDefaultBuilder(args)中的执行过程,主要包括主机配置.应用程 ...

  7. JWT(Json web token)认证详解

    JWT(Json web token)认证详解 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该to ...

  8. 【Jwt】JSON Web Token

    一.什么是JSON Web Token: 首先要明确的是JSON Web Token:是一个开放标准,这个标准定义了一种用于简洁,自包含的用于通信双方之间以JSON对象的形式安全传递信息的方法 而我们 ...

  9. Ubuntu & Docker & Consul & Fabio & ASP.NET Core 2.0 微服务跨平台实践

    相关博文: Ubuntu 简单安装 Docker Mac OS.Ubuntu 安装及使用 Consul Consul 服务注册与服务发现 Fabio 安装和简单使用 阅读目录: Docker 运行 C ...

随机推荐

  1. 微信小程序细节部分

    微信小程序和HTML区别: 1.开发工具不同,H5的开发工具+浏览器Device Mode预览,小程序的开发基于自己的开发者工具 2.开发语言不同,小程序自己开发了一套WXML标签语言和WXSS样式语 ...

  2. vue多层传递$attrs

    今天在使用$attrs的时候遇到一个问题: 父组件: <PanelContainer name="正向舆情"> <PositiveOpinion opinion= ...

  3. 洛谷 P3496 BZOJ 2079 [POI2010]GIL-Guilds

    题目描述 King Byteasar faces a serious matter. Two competing trade organisations, The Tailors Guild and ...

  4. 转载--C++的反思

    转载自http://blog.csdn.net/yapian8/article/details/46983319 最近两年 C++又有很多人出来追捧,并且追捧者充满了各种优越感,似乎不写 C++你就一 ...

  5. Spring Cloud教程(十一)环境变化和刷新范围

    应用程序将收听EnvironmentChangeEvent,并以几种标准方式进行更改(用户可以以常规方式添加ApplicationListeners附加ApplicationListeners).当观 ...

  6. Java 中如何使用clone()方法克隆对象?

    java为什么要 对象克隆: 在程序开发时,有时可能会遇到以下情况:已经存在一个对象A,现在需要一个与A对象完全相同的B 对象,并对B 对象的属性值进行修改,但是A 对象原有的属性值不能改变.这时,如 ...

  7. 北风设计模式课程---开放封闭原则(Open Closed Principle)

    北风设计模式课程---开放封闭原则(Open Closed Principle) 一.总结 一句话总结: 抽象是开放封闭原则的关键. 1."所有的成员变量都应该设置为私有(Private)& ...

  8. Retrotranslator使用简介(JDK1.5->1.4)

      Retrotranslator是一个可以把JDK1.5(6)下编译的类(或包)转译成JDK1.4下可以识别的类(包)的工具. 为现在还用JDK1.4呢?我想无非是现在的大部分Java Web应用是 ...

  9. linux中也有闹钟alarm, timer, stopwatch, world clock 等等

    stopwatch和timer的区别? timer叫计时器, 是先给出一个时间, 然后从现在开始, 倒数, 减少, 直到时间为0 stopwatch 叫跑錶, 则是从现在开始, 往后 增加时间, 事先 ...

  10. CSS - 初始值、指定值、计算值、应用值、实际值

    初始值:未提供指定值且未从父元素指定值继承的 CSS 属性的值. 指定值:通过直接声明或 CSS 属性的值. 计算值:通过需要计算得到的值,如,继承和相对的尺寸.(注意:有些计算要等到布局确定才能进行 ...