Dotnet core使用JWT認證受權最佳實踐(二)

最近,團隊的小夥伴們在作項目時,須要用到JWT認證。遂根據本身的經驗,整理成了這篇文章,用來幫助理清JWT認證的原理和代碼編寫操做。html

第一部分:Dotnet core使用JWT認證受權最佳實踐(一)前端

(接上文)git

  1. 測試運行
% 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#

4、Token認證

拿到Token後,咱們就能夠進行認證操做了。瀏覽器

既然是認證,那應該在每一個API上進行。因此,認證的過程不會放到控制器裏,而應該以MiddleWare的方式,放到主流程中。安全

這個MiddleWare,Microsoft.AspNetCore.Authentication.JwtBearer庫已經幫咱們作好了。咱們只須要配置就好。bash

  1. 在Startup.cs中,ConfigureServices方法裏,添加如下內容
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從上下文中直接取用戶信息。

  1. 在Startup.cs裏,Configure方法中,打開認證
app.UseAuthentication();
app.UseAuthorization();

這兩步完成,咱們就完成的認證的開發工具。

用別人的輪子仍是很爽的,雖然輪子的挑選工做很複雜很費力。

  1. 設置API認證。

在這個Demo裏,咱們選代碼生成時給的WeatherForecastController下的Get方法來測試。

在方法前邊,咱們加上Authorize:

[HttpGet]
[Authorize]
public IEnumerable<WeatherForecast> Get()
...
  1. 測試運行。

啓動程序,跟上一章的方式同樣。

程序運行後,打開: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「,這時候,咱們就能拿到正確的返回數據。

5、擴展:用戶角色認證

在上一章中,咱們實現了用戶的認證。但這個認證有個不漂亮的地方:用戶只簡單的被認證系統分紅了經過認證的和不經過認證的。

在實際項目中,咱們有時候會有這樣的需求:對於某個API,咱們但願只容許具備某種角色權限的用戶去訪問。

下面,咱們對這個項目進行小量的修改,以完成這個需求。

  1. 在給用戶簽發Token的過程當中,加入用戶的角色數據。

在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。

  1. 給API增長認證的角色要求
[HttpGet]
[Authorize(Roles="testUser")]
public IEnumerable<WeatherForecast> Get()
...

在這裏,這個Roles="testUser"裏的testUser,就是這個方法受權所對應的角色名稱。

  1. 測試運行

按正常的步驟,取Token,拼串,保存認證信息,而後去運行WeatherForecast,API能正常返回。

咱們能夠把代碼中的testUser改爲別的字符串進行測試,會返回403 - Error: Forbidden錯誤。

增長角色認證成功。

6、刷新Token

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。

  1. 在DTOModels下建一個RefreshTokenDTO,用做API的輸入參數
using System;

namespace demo.DTOModels
{
    public class RefreshTokenDTO
    {

        public string Token { get; set; } 
        public string refreshToken { get; set; }
    }
  1. 在AuthenticationController裏,建立一個RefreshToken的API,並補齊驗證代碼
[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運行測試,能正常認證經過。

  1. 單獨說一下refreshToken

refreshToken,名義上是爲了刷新Token,實際上用處主要是給用戶從新登陸作計時。refreshToken過時了,用戶就必須從新登陸。就是這麼個做用。要否則,Token本身刷新豈不更好?

refreshToken能夠採用跟Token同樣的生成方式。可是,咱們也看到,Token生成出來的串就很長,若是refreshToken也那樣生成,那就也會是一個很長的串。這樣會加大前端到API的傳輸量。所以,這不算是一個好主意。

通常來講,refreshToken會換一種生成方式。惟一序列、Hash,都是能夠選擇的,能夠減小不少傳輸。

至於持久化和過時,依託數據庫就行了。

7、彩蛋

最後,送你們一個彩蛋。

在生成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

掃描二維碼,關注我的公衆號,能夠第一時間獲得最新的我的文章和內容推送

本文版權歸做者全部,轉載請保留此聲明和原文連接

相關文章
相關標籤/搜索