Handle Refresh Token Using ASP.NET Core 2.0 And JSON Web Token

来源:
   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.

 1 public class Program
 2
 3 {
 4
 5 public static void Main(string[] args)
 6
 7     {
 8
 9         BuildWebHost(args).Run();
10
11     }
12
13 public static IWebHost BuildWebHost(string[] args) =>
14
15         WebHost.CreateDefaultBuilder(args)
16
17             .UseStartup<Startup>()
18
19             .UseUrls("http://localhost:5002")
20
21             .Build();
22
23 }  

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.

 1 public void ConfigureJwtAuthService(IServiceCollection services)
 2
 3 {
 4
 5 var audienceConfig = Configuration.GetSection("Audience");
 6
 7 var symmetricKeyAsBase64 = audienceConfig["Secret"];
 8
 9 var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
10
11 var signingKey = new SymmetricSecurityKey(keyByteArray);
12
13 var tokenValidationParameters = new TokenValidationParameters
14
15     {
16
17 // The signing key must match!
18
19         ValidateIssuerSigningKey = true,
20
21         IssuerSigningKey = signingKey,
22
23 // Validate the JWT Issuer (iss) claim
24
25         ValidateIssuer = true,
26
27         ValidIssuer = audienceConfig["Iss"],
28
29 // Validate the JWT Audience (aud) claim
30
31         ValidateAudience = true,
32
33         ValidAudience = audienceConfig["Aud"],
34
35 // Validate the token expiry
36
37         ValidateLifetime = true,
38
39         ClockSkew = TimeSpan.Zero
40
41     };
42
43     services.AddAuthentication(options =>
44
45     {
46
47         options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
48
49         options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
50
51     })
52
53     .AddJwtBearerAuthentication(o =>
54
55     {
56
57         o.TokenValidationParameters = tokenValidationParameters;
58
59     });
60
61 }  

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

 1 public void ConfigureServices(IServiceCollection services)
 2
 3 {
 4
 5 //configure the jwt
 6
 7     ConfigureJwtAuthService(services);
 8
 9     services.AddMvc();
10 }  

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.

  1 [Route("api/token")]
  2
  3 public class TokenController : Controller
  4
  5 {
  6
  7 //some config in the appsettings.json
  8
  9 private IOptions<Audience> _settings;
 10
 11 //repository to handler the sqlite database
 12
 13 private IRTokenRepository _repo;
 14
 15 public TokenController(IOptions<Audience> settings, IRTokenRepository repo)
 16
 17     {
 18
 19 this._settings = settings;
 20
 21 this._repo = repo;
 22
 23     }
 24
 25     [HttpGet("auth")]
 26
 27 public IActionResult Auth([FromQuery]Parameters parameters)
 28
 29     {
 30
 31 if (parameters == null)
 32
 33         {
 34
 35 return Json(new ResponseData
 36
 37             {
 38
 39                 Code = "901",
 40
 41                 Message = "null of parameters",
 42
 43                 Data = null
 44
 45             });
 46
 47         }
 48
 49 if (parameters.grant_type == "password")
 50
 51         {
 52
 53 return Json(DoPassword(parameters));
 54
 55         }
 56
 57 else if (parameters.grant_type == "refresh_token")
 58
 59         {
 60
 61 return Json(DoRefreshToken(parameters));
 62
 63         }
 64
 65 else
 66
 67         {
 68
 69 return Json(new ResponseData
 70
 71             {
 72
 73                 Code = "904",
 74
 75                 Message = "bad request",
 76
 77                 Data = null
 78
 79             });
 80
 81         }
 82
 83     }
 84
 85 //scenario 1 : get the access-token by username and password
 86
 87 private ResponseData DoPassword(Parameters parameters)
 88
 89     {
 90
 91 //validate the client_id/client_secret/username/passwo
 92
 93 var isValidated = UserInfo.GetAllUsers().Any(x => x.ClientId == parameters.client_id
 94
 95                                 && x.ClientSecret == parameters.client_secret
 96
 97                                 && x.UserName == parameters.username
 98
 99                                 && x.Password == parameters.password);
100
101 if (!isValidated)
102
103         {
104
105 return new ResponseData
106
107             {
108
109                 Code = "902",
110
111                 Message = "invalid user infomation",
112
113                 Data = null
114
115             };
116
117         }
118
119 var refresh_token = Guid.NewGuid().ToString().Replace("-", "");
120
121 var rToken = new RToken
122
123         {
124
125             ClientId = parameters.client_id,
126
127             RefreshToken = refresh_token,
128
129             Id = Guid.NewGuid().ToString(),
130
131             IsStop = 0
132
133         };
134
135 //store the refresh_token
136
137 if (_repo.AddToken(rToken))
138
139         {
140
141 return new ResponseData
142
143             {
144
145                 Code = "999",
146
147                 Message = "OK",
148
149                 Data = GetJwt(parameters.client_id, refresh_token)
150
151             };
152
153         }
154
155 else
156
157         {
158
159 return new ResponseData
160
161             {
162
163                 Code = "909",
164
165                 Message = "can not add token to database",
166
167                 Data = null
168
169             };
170
171         }
172
173     }
174
175 //scenario 2 : get the access_token by refresh_token
176
177 private ResponseData DoRefreshToken(Parameters parameters)
178
179     {
180
181 var token = _repo.GetToken(parameters.refresh_token, parameters.client_id);
182
183 if (token == null)
184
185         {
186
187 return new ResponseData
188
189             {
190
191                 Code = "905",
192
193                 Message = "can not refresh token",
194
195                 Data = null
196
197             };
198
199         }
200
201 if (token.IsStop == 1)
202
203         {
204
205 return new ResponseData
206
207             {
208
209                 Code = "906",
210
211                 Message = "refresh token has expired",
212
213                 Data = null
214
215             };
216
217         }
218
219 var refresh_token = Guid.NewGuid().ToString().Replace("-", "");
220
221         token.IsStop = 1;
222
223 //expire the old refresh_token and add a new refresh_token
224
225 var updateFlag = _repo.ExpireToken(token);
226
227 var addFlag = _repo.AddToken(new RToken
228
229         {
230
231             ClientId = parameters.client_id,
232
233             RefreshToken = refresh_token,
234
235             Id = Guid.NewGuid().ToString(),
236
237             IsStop = 0
238
239         });
240
241 if (updateFlag && addFlag)
242
243         {
244
245 return new ResponseData
246
247             {
248
249                 Code = "999",
250
251                 Message = "OK",
252
253                 Data = GetJwt(parameters.client_id, refresh_token)
254
255             };
256
257         }
258
259 else
260
261         {
262
263 return new ResponseData
264
265             {
266
267                 Code = "910",
268
269                 Message = "can not expire token or a new token",
270
271                 Data = null
272
273             };
274
275         }
276
277     }
278
279 //get the jwt token
280
281 private string GetJwt(string client_id, string refresh_token)
282
283     {
284
285 var now = DateTime.UtcNow;
286
287 var claims = new Claim[]
288
289         {
290
291 new Claim(JwtRegisteredClaimNames.Sub, client_id),
292
293 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
294
295 new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
296
297         };
298
299 var symmetricKeyAsBase64 = _settings.Value.Secret;
300
301 var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
302
303 var signingKey = new SymmetricSecurityKey(keyByteArray);
304
305 var jwt = new JwtSecurityToken(
306
307             issuer: _settings.Value.Iss,
308
309             audience: _settings.Value.Aud,
310
311             claims: claims,
312
313             notBefore: now,
314
315             expires: now.Add(TimeSpan.FromMinutes(2)),
316
317             signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256));
318
319 var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
320
321 var response = new
322
323         {
324
325             access_token = encodedJwt,
326
327             expires_in = (int)TimeSpan.FromMinutes(2).TotalSeconds,
328
329             refresh_token = refresh_token,
330
331         };
332
333 return JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented });
334
335     }
336
337 }  

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 = "100";  

var client_secret = "888";  

var username = "Member";  

var password = "123";  

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 != "999")  

        {  

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

        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 != "999")  

            {  

                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.

原文地址:https://www.cnblogs.com/frank0812/p/11840877.html

时间: 2024-08-28 06:18:08

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

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

第一步:在Nuget上安装"Microsoft.AspNet.WebApi.Cors"包,并对api controller使用[EnableCors]特性以及Microsoft.AspNetCore.Authentication.JwtBearer包 第二步:创建.netcore API项目 /控制器:AuthenticateController using System; using System.Collections.Generic; using System.Linq; usi

ASP.NET Core 1.0 开发记录

参考页面: http://www.yuanjiaocheng.net/ASPNET-CORE/first.html http://www.yuanjiaocheng.net/ASPNET-CORE/asp-net-core-overview.html http://www.yuanjiaocheng.net/ASPNET-CORE/asp.net-core-environment.html http://www.yuanjiaocheng.net/ASPNET-CORE/newproject.h

Asp.net core 2.0.1 Razor 的使用学习笔记(一)

环境:vs2017 版本:15.5.6 这里说明下, Razor页面模式跟mvc出现了严重的不同.正如微软官方说的一样“Razor 页面是 ASP.NET Core MVC 的一个新功能,它可以使基于页面的编码方式更简单高效.” 但就代码说没有什么不同几乎完全一样,但是存放的位置却有了根本的区别.个人研究分析的结果是:Razor页面模式其实是把mvc中的控制器化整为零了,即原来控制器中的操作代码被分布放到了各个页面的.cshtml.cs文件中了.这样一来由原来mvc中文件按类型分类变成了按功能分

asp.net core 2.0 web api基于JWT自定义策略授权

JWT(json web token)是一种基于json的身份验证机制,流程如下: 通过登录,来获取Token,再在之后每次请求的Header中追加Authorization为Token的凭据,服务端验证通过即可能获取想要访问的资源.关于JWT的技术,可参考网络上文章,这里不作详细说明, 这篇博文,主要说明在asp.net core 2.0中,基于jwt的web api的权限设置,即在asp.net core中怎么用JWT,再次就是不同用户或角色因为权限问题,即使援用Token,也不能访问不该访

JSON Web Token in ASP.NET Web API 2 using Owin

In the previous post Decouple OWIN Authorization Server from Resource Server we saw how we can separate the Authorization Server and the Resource Server by unifying the "decryptionKey" and "validationKey" key values in machineKey node

[转]Writing Custom Middleware in ASP.NET Core 1.0

本文转自:https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/ One of the new features from ASP.NET Core 1.0 is the idea of Middleware. Middleware are components of an application that examine the requests responses coming in t

ASP.NET Core 1.0中实现文件上传的两种方式(提交表单和采用AJAX)

Bipin Joshi (http://www.binaryintellect.net/articles/f1cee257-378a-42c1-9f2f-075a3aed1d98.aspx) Uploading files is a common requirement in web applications. In ASP.NET Core 1.0 uploading files and saving them on the server is quite easy. To that end

Securing ASP.NET Core 2.0 Applications with JWTs

A Quick Introduction to JWTs JSON Web Tokens, often shortened with JWTs, are gathering more and more popularity in the Web environment. It is an open standard that allows transmitting data between parties as a JSON object in a compact and secure way.

ASP.NET Core 2.0利用Jwt实现授权认证

背景 在微服务架构下,一般都会按不同的业务或功能将整个系统切分成不同的独立子系统,再通过REST API或RPC进行通讯并相互调用,形成各个子系统之间的串联结构.在这里,我们将采用REST API的通讯方式.比如: 1.有一个“用户中心”独立子系统名为“Lezhima.UserHub”,是一个基于ASP.NET Core mvc 2.0的项目. 2.有一个处理用户订单的独立子系统名为“Lezhima.UserOrder”,是一个基于ASP.NET Core webp api 2.0的项目. 3.