1. 程式人生 > 實用技巧 >《ASP.NET Core 與 RESTful API 開發實戰》-- (第8章)-- 讀書筆記(上)

《ASP.NET Core 與 RESTful API 開發實戰》-- (第8章)-- 讀書筆記(上)

第 8 章 認證和安全

8.1 認證

認證(Authentication)是指驗證使用者身份的過程,授權(Authorization)是驗證一個已經通過認證的使用者是否有許可權做某些事的過程

常見的 HTTP 認證方式包括:

  • Basic 認證:使用者名稱密碼
  • Digest 認證:摘要認證
  • Bearer 認證:token 認證

常見的 Token 有兩種型別,一種是引用型別,另一種是自包含型別。前者是伺服器根據自己定義的規則隨機生成的字串,後者是對使用者資訊以及 Token 元資料等資訊進行編碼、加密之後得到的結果。JWT 是最為常見的自包含型別的 Token

JWT 全名為 JSON Web Token,是一個開放標準,一種 Token 格式,它由3部分組成,分別是頭部、負載和簽名,各部分之間以.隔開,例如:header.payload.signature

頭部由兩部分組成,即 Token 型別和使用的演算法名稱

{
    "alg": "HS256",
    "type": "JWT"
}

負載部分包括要傳輸的資訊,通常由多個 Claim 構成,Claim 是與實體相關的資訊以及其他元資料

Claim 有3種類型:已註冊、公有、私有

公共型別的 Claim 主要是常見且通用的 Claim,如 name、email 和 gender 等

一個典型的 JWT 負載如下:

{
    "sub": "1234567890",
    "name": "John Doe",
    "Admin": true
}

簽名部分通過使用頭部指定的演算法以及一個金鑰,對 Base64 編碼後的頭部和負載加密而成

HMAC-SHA256(
encodeBase64Url(header) + '.' +
encodeBase64Url(payload),
secret)

簽名主要用於驗證訊息不會被篡改

最終,上述3個部分的內容均使用 Base64 編碼,並使用 "." 將各部分隔開,即為一個標準的 JWT

使用 JWT 能夠以緊湊的方式傳遞使用者資訊,並通過簽名保護其中的資訊不會被修改。需要注意到是,它很容易被解碼,因此不應該在 Token 中包含敏感資訊,如使用者密碼等

接下來,通過 JwtBearer 認證中介軟體實現基於 Token 的認證

新增nuget

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer

在 Startup 類中註冊服務

// defaultScheme用於指定當未指定具體認證方案時將會使用的預設方案
services.AddAuthentication(defaultScheme: JwtBearerDefaults.AuthenticationScheme);

在 Startup 類中使用服務

app.UseAuthentication();

對於不同的認證方式(如 Cookie 或 JwtBearer),ASP.NET Core 均在其實現的包中包含相應的擴充套件方法,以便新增相應型別的認證方式,例如

services.AddAuthentication(defaultScheme: JwtBearerDefaults.AuthenticationScheme)
.AddCookie()
.AddJwtBearer();

可以新增多個相同型別的認證方式,但指定的名稱必須不同

services.AddAuthentication()
.AddCookie("cookie1")
.AddCookie("cookie2");

當新增 JwtBearer 認證方式時,JwtBearerOptions 物件能夠配置該認證的選項,它的 TokenValidationParameters 屬性用於指定驗證 Token 時的規則

var tokenSection = Configuration.GetSection("Security:Token");

services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuer = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = tokenSection["Issuer"],// 合法的簽發者
            ValidAudience = tokenSection["Audience"],// 合法的接受方
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenSection["Key"])),// 簽名驗證的安全金鑰
            ClockSkew = TimeSpan.Zero// 驗證時間的時間偏移值
        };
    });

上述程式碼會從配置檔案中讀取關於 Token 的資訊,因此還需要在 appsettings.json 中新增如下內容

"Security": {
"Token": {
  "Issuer": "demo_issuer",
  "Audience": "demo_audience",
  "Key": "<your_secret_key>" 
} 
},

接下來,為了使用 ASP.NET Core 的認證功能來保護資源,應為 Controller 或 Action 新增 [Authorize] 特性

[Authorize]
public class AuthorController : ControllerBase
{}

如果使用了多個認證方式,可以使用 [Authorize] 特性的 AuthenticationSchemes 屬性指明當前使用哪一種認證方式

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class BookController : ControllerBase
{}

JwtBearer 中介軟體提供了對 JWT 的驗證功能,然而並未能提供生成 Token 的功能。要生成 Tokne,可以使用 JwtSecurityTokenHandler 類

接下來,我們建立一個 Controller,它將會根據使用者的認證資訊生成 JWT,並返回給客戶端

namespace Library.API.Controllers
{
    [Route("auth")]
    [ApiController]
    public class AuthenticateController : ControllerBase
    {
        public IConfiguration Configuration { get; set; }

        public AuthenticateController(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        [HttpPost("token", Name = nameof(GenerateToken))]
        public IActionResult GenerateToken(LoginUser loginUser)
        {
            if (loginUser.UserName != "demouser" || loginUser.Password != "demopassword")
            {
                return Unauthorized();
            }

            var claims = new List<Claim>
            {
                new Claim(JwtRegisteredClaimNames.Sub,loginUser.UserName)
            };

            var tokenConfigSection = Configuration.GetSection("Security:Token");
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenConfigSection["Key"]));
            var signCredential = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var jwtToken = new JwtSecurityToken(
                issuer: tokenConfigSection["Issuer"],
                audience: tokenConfigSection["Audience"],
                claims: claims,
                expires: DateTime.Now.AddMinutes(3),// 由於 JWT 不支援銷燬以及撤回功能,因此在設定它的有效時間時,應該設定一個較短的時間
                signingCredentials: signCredential);

            return Ok(new
            {
                token = new JwtSecurityTokenHandler().WriteToken(jwtToken),
                expiration = TimeZoneInfo.ConvertTimeFromUtc(jwtToken.ValidTo, TimeZoneInfo.Local)
            });
        }
    }

    public class LoginUser
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }
}

對於受保護的資源,應在每一次請求時均攜帶 Authorization 訊息頭

如果不希望新增認證功能,則應為其新增 [AllowAnonymous] 特性

當伺服器驗證 Token 通過時,JwtBearer 認證處理器會通過 JwtSecurityTokenHandler 將 Token 轉換為 ClaimPrincipal 物件,並將他賦值給 HttpContext 物件的 User 屬性

ClaimPrincipal 類代表一個使用者,它包含一些重要的屬性(如 Identity 和 Identities),它們分別返回該物件中主要的 ClaimsIdentity 物件和所有的 ClaimsIdentity 物件集合

ClaimsIdentity 類代表使用者的一個身份,一個使用者可以有一個或多個身份;ClaimsIdentity 類則又由一個或多個 Claim 組成

Claim 類代表與使用者相關的具體資訊(如使用者名稱和出生日期等)

本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。

歡迎轉載、使用、重新發布,但務必保留文章署名 鄭子銘 (包含連結: http://www.cnblogs.com/MingsonZheng/ ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。

如有任何疑問,請與我聯絡 ([email protected]) 。