Dotnet core使用JWT認證授權最佳實踐(一)

Tiger.Wang發表於2020-05-15

最近,團隊的小夥伴們在做專案時,需要用到JWT認證。遂根據自己的經驗,整理成了這篇文章,用來幫助理清JWT認證的原理和程式碼編寫操作。

一、JWT

JSON Web Token (JWT)是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用於作為JSON物件在各方之間安全地傳輸資訊。該資訊可以被驗證和信任,因為它是數字簽名的。

JWT是什麼,看上面這段網上抄來的話。

關於JWT以及優缺點,網上有很多詳細的說法,我這兒就不重複了。

我們只需要知道以下的事實:

在一般的系統中,我們有時候會做個使用者登入。使用者登入完成進到系統後,需要根據使用者的許可權,來控制一些功能可用,而另一些功能不可用。

在SOA/AOP架構中,做為最重要的API端,其實也需要有類似登入或認證的內容,用來區分哪些使用者可以使用某個API,哪些使用者不行。

同時,我們希望這個登入或類似登入的過程,只發生在一個固定位置。這樣,在我們寫程式碼時,建立好這樣一個過程後,在我們後邊寫程式碼時,簡單引用即可,而不需要每個API程式都開發一次認證。這個需求,其實就是OAuth的由來。

最重要的是,這樣的程式碼寫出來,顯得高大上

下面進入正題。

認證這個操作,就像我們最近的日子。

首先,我們要有一個出入證,或者綠碼。這個證,我們稱作令牌(Token)。我們去領這個證,這個操作稱為發行(Issue)。

我們拿著這個證,去到一個地方。有專人會檢查這個證,這稱為使用者身份驗證(Authentication)。驗證通過放行,稱為授權(Authorization),驗證不通過,叫作未授權錯誤(Unauthorized)。

如果這個證過期了,你就需要去重新辦一個證。這個過程叫做重新整理(RefreshToken)。

簡言之,這就是認證的全部流程。

下面,我用一個Demo專案,來逐步完成這個過程。

二、開發環境&基礎專案

這個Demo的開發環境是:Mac + VS Code + Dotnet Core 3.1.2。

$ dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   3.1.201
 Commit:    b1768b4ae7

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  10.15
 OS Platform: Darwin
 RID:         osx.10.15-x64
 Base Path:   /usr/local/share/dotnet/sdk/3.1.201/

Host (useful for support):
  Version: 3.1.3
  Commit:  4a9f85e9f8

.NET Core SDKs installed:
  3.1.201 [/usr/local/share/dotnet/sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

首先,在這個環境下建立工程:

  1. 建立Solution
% dotnet new sln -o demo
The template "Solution File" was created successfully.
  1. 用Webapi模板建立專案
cd demo
% dotnet new webapi -o demo
The template "ASP.NET Core Web API" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on demo/demo.csproj...
  Restore completed in 179.13 ms for demo/demo.csproj.

Restore succeeded.
  1. 把Demo專案加到Solution中
% dotnet sln add demo/demo.csproj
Project `demo/demo.csproj` added to the solution.
  1. 安裝Swagger(這步非必須,我習慣用Swagger,不習慣用Postman)
% dotnet add package Swashbuckle.AspNetCore
log  : Restore completed in 2.75 sec for demo/demo.csproj.
  1. 安裝JWT認證支援庫(必須引入)
% dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
log  : Restore completed in 3.09 sec for demo/demo.csproj.

五步做完,基礎專案就建完了。

看一下整個的目錄結構:

% tree .
.
├── demo
│   ├── Controllers
│   │   └── WeatherForecastController.cs
│   ├── Program.cs
│   ├── Properties
│   │   └── launchSettings.json
│   ├── Startup.cs
│   ├── WeatherForecast.cs
│   ├── appsettings.Development.json
│   ├── appsettings.json
│   ├── demo.csproj
│   └── obj
│       ├── demo.csproj.nuget.dgspec.json
│       ├── demo.csproj.nuget.g.props
│       ├── demo.csproj.nuget.g.targets
│       ├── project.assets.json
│       └── project.nuget.cache
└── demo.sln

  1. 在Startup.cs中補充程式碼,以啟用Swagger

在ConfigureServices方法中加入以下程式碼:

services.AddSwaggerGen(c =>
{
        c.SwaggerDoc("v1"new OpenApiInfo { Title = "Demo", Version = "V1" });

        c.AddSecurityDefinition("Bearer"new OpenApiSecurityScheme
        {
                Name = "Authorization",
                Type = SecuritySchemeType.ApiKey,
                Scheme = "Bearer",
                BearerFormat = "JWT",
                In = ParameterLocation.Header,
                Description = "",
        });

        c.AddSecurityRequirement(new OpenApiSecurityRequirement
        {
                {
                        new OpenApiSecurityScheme
                        {
                                Reference = new OpenApiReference
                                {
                                        Type = ReferenceType.SecurityScheme,
                                        Id = "Bearer"
                                }
                        },
                        new string[] {}
                }
        });
});

在Configure方法中加入以下程式碼

app.UseSwagger();
app.UseSwaggerUI(c =>
{
        c.SwaggerEndpoint("/swagger/v1/swagger.json""Demo V1");
});

關於Swagger的詳細配置,這裡不做說明,留著以後寫。

三、簽發Token

簽發Token是認證的第一步。

使用者進到系統,在驗證使用者帳號密碼後,需要根據使用者的資料,把Token返回給使用者。

這個過程其實跟認證沒什麼關係,只是一個普通的API功能。

  1. 工程下加一個目錄DTOModels,建立一個LoginRequestDTO的類,用於定義API的輸入引數。
using System;

namespace demo.DTOModels
{
    public class LoginRequestDTO
    {

        public string username { get; set; }
        public string password { get; set; }
    }
}
  1. 建立一個控制器AuthenticationController,並在控制器裡建立一個API方法RequestToken。
using Microsoft.AspNetCore.Mvc;
using demo.DTOModels;

namespace demo.Controllers
{
    public class AuthenticationController : ControllerBase
    {
        [HttpPost, Route("requesttoken")]
        public ActionResult RequestToken([FromBody] LoginRequestDTO request)
        
{
              //這兒待完善
            return Ok();
        }
    }
}
  1. 生成JWT Token需要預設一些引數。我們在appsetting.json裡先設定好。
{
  "Logging": {
    "LogLevel": {
      "Default""Information",
      "Microsoft""Warning",
      "Microsoft.Hosting.Lifetime""Information"
    }
  },
  "AllowedHosts""*",
  "tokenParameter": {
    "secret""123456123456123456",
    "issuer""WangPlus",
    "accessExpiration"120,
    "refreshExpiration"1440
  }
}

這裡,tokenParameter節是我們設定的引數。一般來說,是這幾個:

secret: JWT加密的金鑰。現在主流用SHA256加密,需要256位以上的金鑰,unicode是16個字元以上,儘量複雜一些。金鑰洩露,Token就會被破解,所以,你懂的。

issuer: 簽發人的名稱,如果沒人注意,你可以把大名寫在上面。

accessExpiration: Token的有效分鐘數。過了這個時間,這個Token會過期。

refreshExpiration: refreshToken的有效分鐘數。過了這個時間,使用者需要重新登入。

Token過期後,可以讓使用者重新登入認證拿Token。但這個方式會比較Low。高大上的方式是簽發Token的時候,同時也簽發一個refreshToken給使用者。使用者Token過期後,可以拿refreshToken去申請新的Token,同時重新整理refreshToken。如果使用者長時間未使用系統,refreshToken也過期了,才讓使用者重新登入認證。

refreshToken可以用JWT生成,也可以自己生成,不影響認證。

  1. 建一個Models目錄,建立一個對映tokenParameter的類。這個類不是必須,只是為了寫著方便。不想這樣寫,也可以直接讀配置,再轉成資料。
using System;

namespace demo.Models
{
    public class tokenParameter
    {

        public string Secret { get; set; }
        public string Issuer { get; set; }
        public int AccessExpiration { get; set; }
        public int RefreshExpiration { get; set; }
    }
}
  1. 在前邊建好的API - RequestToken中,完成Token和refreshToken的生成和返回。
using Microsoft.AspNetCore.Mvc;
using demo.DTOModels;
using Microsoft.Extensions.Configuration;
using System;
using System.Text;
using demo.Models;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;

namespace demo.Controllers
{
    public class AuthenticationController : ControllerBase
    {
        private tokenParameter _tokenParameter = new tokenParameter();
        public AuthenticationController()
        
{
            var config = new ConfigurationBuilder()
                .SetBasePath(AppContext.BaseDirectory)
                .AddJsonFile("appsettings.json")
                .Build();

            _tokenParameter = config.GetSection("tokenParameter").Get<tokenParameter>();
        }

        [HttpPost, Route("requestToken")]
        public ActionResult RequestToken([FromBody] LoginRequestDTO request)
        
{
            //這兒在做使用者的帳號密碼校驗。我這兒略過了。
            if (request.username == null && request.password == null)
                return BadRequest("Invalid Request");

              //生成Token和RefreshToken
            var token = GenUserToken(request.username, "testUser");
            var refreshToken = "123456";

            return Ok(new[] { token, refreshToken });
        }


          //這兒是真正的生成Token程式碼
          private string GenUserToken(string username, string role)
        
{
            var claims = new[]
            {
                new Claim(ClaimTypes.Name, username),
                new Claim(ClaimTypes.Role, role),
            };

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenParameter.Secret));
            var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var jwtToken = new JwtSecurityToken(_tokenParameter.Issuer, null, claims, expires: DateTime.UtcNow.AddMinutes(_tokenParameter.AccessExpiration), signingCredentials: credentials);

            var token = new JwtSecurityTokenHandler().WriteToken(jwtToken);

            return token;
        }
    }
}

這個類裡,驗證帳號密碼的程式碼我略過了。還有,refreshToken給了一個固定串。真實專案這兒就按需要做就好。

(未完待續)

 


 

老王Plus

微信公眾賬號:老王Plus

如果你想及時得到個人文章以及內容的訊息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號)。

本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。

 

相關文章