最近,團隊的小夥伴們在作項目時,須要用到JWT認證。遂根據本身的經驗,整理成了這篇文章,用來幫助理清JWT認證的原理和代碼編寫操做。html
第一部分:Dotnet core使用JWT認證受權最佳實踐(一)前端
(接上文)git
% dotnet run
等程序運行起來後,在瀏覽器輸入:http://localhost:5000/swagger/,會進到Swagger的API界面。選擇requestToken,點擊按鈕」Try it out「->」Execute「,能夠看到運行結果:github
["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNTg5MzgxMzQ4LCJpc3MiOiJXYW5nUGx1cyJ9.ojGuWUk9i2Vp5qu3s2UZSLC64Sm95Cao2eGF3GDVvec","123456"]
好吧,不要在乎這個返回的格式。返回的兩個串中,第一個就是Token,第二個是refreshToken。web
到這兒,咱們成功拿到了用戶的Token。數據庫
爲了防止不提供原網址的轉載,特在這裏加上原文連接:http://www.javashuo.com/article/p-sqxzwszx-dk.htmlc#
拿到Token後,咱們就能夠進行認證操做了。瀏覽器
既然是認證,那應該在每一個API上進行。因此,認證的過程不會放到控制器裏,而應該以MiddleWare的方式,放到主流程中。安全
這個MiddleWare,Microsoft.AspNetCore.Authentication.JwtBearer庫已經幫咱們作好了。咱們只須要配置就好。bash
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(option =>
{
option.RequireHttpsMetadata = false;
option.SaveToken = true;
var token = Configuration.GetSection("tokenParameter").Get<tokenParameter>();
option.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidateIssuer = true,
ValidateAudience = false,
};
});
這裏面,有幾個參數須要注意:
RequireHttpsMetadata: 限定認證操做是否必須經過https來作,這個要跟隨項目在生產環境中的運行狀況來定。若是WebServer是我前文15分鐘從零開始搭建支持10w+用戶的生產環境(三)中介紹的Jexus,採用對外https,對內http的方式,那這兒能夠設爲false。
SaveToken: 決定Token在認證完成後,是否須要保存到上下文裏並向後傳。這個設置也要看應用。咱們Token生成後,用戶的相關信息已經包含在裏面了。API裏若是有涉及用戶的操做,按理能夠不用往API裏傳相關用戶的參數。一方面不安全,另外一方面代碼也很差看。這時就能夠把這個參數設爲True,而後API從上下文中直接取用戶信息。
app.UseAuthentication();
app.UseAuthorization();
這兩步完成,咱們就完成的認證的開發工具。
用別人的輪子仍是很爽的,雖然輪子的挑選工做很複雜很費力。
在這個Demo裏,咱們選代碼生成時給的WeatherForecastController下的Get方法來測試。
在方法前邊,咱們加上Authorize:
[HttpGet]
[Authorize]
public IEnumerable<WeatherForecast> Get()
...
啓動程序,跟上一章的方式同樣。
程序運行後,打開:http://localhost:5000/swagger/,進入WeatherForecast,點」Try it out「->」Execute「,咱們會獲得一個401 - Error: Unauthorized的返回,由於咱們沒有作認證。
下面測試作認證後的訪問。
先去requestToken拿一個Token(refreshToken這章不用),在前邊加「Bearer 」,拼成一個串
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNTg5MzgxMzQ4LCJpc3MiOiJXYW5nUGx1cyJ9.ojGuWUk9i2Vp5qu3s2UZSLC64Sm95Cao2eGF3GDVvec
要注意,Bearer後邊要跟一個空格。這個串的格式是:Bearer + 空格 + Token。
在頁面的右上角,有一個「Authorize」,點進去,在Value輸入框中粘貼上面拼好的串,而後點按鈕「Authorize」,保存認證信息。
下面進入WeatherForecast,點」Try it out「->」Execute「,這時候,咱們就能拿到正確的返回數據。
在上一章中,咱們實現了用戶的認證。但這個認證有個不漂亮的地方:用戶只簡單的被認證系統分紅了經過認證的和不經過認證的。
在實際項目中,咱們有時候會有這樣的需求:對於某個API,咱們但願只容許具備某種角色權限的用戶去訪問。
下面,咱們對這個項目進行小量的修改,以完成這個需求。
在AuthenticationController的RequestToken中,咱們構建了一個用戶的Claims:
var claims = new[]
{
new Claim(ClaimTypes.Name,request.username),
};
就是這兒。咱們在這兒加入用戶的角色:
var claims = new[]
{
new Claim(ClaimTypes.Name,request.username),
new Claim(ClaimTypes.Role, "testUser"),
};
實際應用中,這個角色的名稱,能夠根據須要,從用戶系統中拿來。
在這個Demo裏,就直接寫成個字符串了。就是說,有一個角色,叫testUser。
[HttpGet]
[Authorize(Roles="testUser")]
public IEnumerable<WeatherForecast> Get()
...
在這裏,這個Roles="testUser"裏的testUser,就是這個方法受權所對應的角色名稱。
按正常的步驟,取Token,拼串,保存認證信息,而後去運行WeatherForecast,API能正常返回。
咱們能夠把代碼中的testUser改爲別的字符串進行測試,會返回403 - Error: Forbidden錯誤。
增長角色認證成功。
Token過時後,就須要刷新。
固然咱們能夠把Token設成永遠不過時,但這不是個安全的作法。還能夠在Token過時後從新請求一個新Token,但這樣作會顯得Low。
賞心悅目的作法是:用refreshToken來刷新Token。設置refreshToken的過時時間長於Token。Token過時後,讓用戶提交Token和refreshToken到服務器,服務器驗證Token是否合法,並從中提取用戶信息,根據用戶信息和refreshToken覈驗是否匹配。若是匹配,就從新生成Token給用戶。
至於refreshToken的過時時長,和是否須要在刷新Token時也刷新refreshToken,就看心情了,沒有固定的作法。我本身的項目中,Token是2小時過時,refreshToken是24小時過時。在Token刷新時,若是refreshToken的過時時間少於6小時,則刷新refreshToken。供參考。
下面,按這個方式,作一下刷新Token。
using System;
namespace demo.DTOModels
{
public class RefreshTokenDTO
{
public string Token { get; set; }
public string refreshToken { get; set; }
}
[HttpPost, Route("refreshToken")]
public ActionResult RefreshToken([FromBody]RefreshTokenDTO request)
{
if(request.Token == null && request.refreshToken == null)
return BadRequest("Invalid Request");
//這兒是驗證Token的代碼
var handler = new JwtSecurityTokenHandler();
try
{
ClaimsPrincipal claim = handler.ValidateToken(request.Token, new TokenValidationParameters{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_tokenParameter.Secret)),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
}, out SecurityToken securityToken);
var username = claim.Identity.Name;
//這兒是生成Token的代碼
var token = GenUserToken(username, "testUser");
var refreshToken = "654321";
return Ok(new[] { token, refreshToken });
}
catch(Exception)
{
return BadRequest("Invalid Request");
}
}
這樣,Token刷新就完成了。能夠用生成Token運行測試,能正常認證經過。
refreshToken,名義上是爲了刷新Token,實際上用處主要是給用戶從新登陸作計時。refreshToken過時了,用戶就必須從新登陸。就是這麼個做用。要否則,Token本身刷新豈不更好?
refreshToken能夠採用跟Token同樣的生成方式。可是,咱們也看到,Token生成出來的串就很長,若是refreshToken也那樣生成,那就也會是一個很長的串。這樣會加大前端到API的傳輸量。所以,這不算是一個好主意。
通常來講,refreshToken會換一種生成方式。惟一序列、Hash,都是能夠選擇的,能夠減小不少傳輸。
至於持久化和過時,依託數據庫就行了。
最後,送你們一個彩蛋。
在生成Token時,咱們把過時時間設置成少於五分鐘的時長,比方3分鐘。但這時,實測會發現,Token的過時失效了。
爲何呢?
TokenValidationParameters有一個屬性叫ClockSkew,這個參數有個默認值是TimeSpan.FromMinutes(5)。
這個參數的意義是:考慮到各個服務器之間的時間不必定徹底同步,系統給了個5分鐘的偏差時間。
這個偏差時間致使的結果是:少於五分鐘的過時時間,會在實際認證檢查時被忽略。
這個狀況,Microsoft上有N多人在討論,能夠本身去查。
因此,當Token的過時小於5分鐘時,想要讓認證對這個時間生效,能夠把這個值設爲TimeSpan.Zero。
option.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidateIssuer = true,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero, //就是這一行
};
我把上面的代碼,傳到了Github上,須要了能夠拉下來直接測試。
代碼地址:https://github.com/humornif/Demo-Code/tree/master/0007/demo
(全文完)
![]() |
微信公衆號:老王Plus 掃描二維碼,關注我的公衆號,能夠第一時間獲得最新的我的文章和內容推送 本文版權歸做者全部,轉載請保留此聲明和原文連接 |