ASP.NET Core JWT 認證
JWT (JSON Web Token)是一種開放標準,它以 JSON 物件的方式在各方之間安全地傳輸資訊。通俗的說,就是通過數字簽名演算法生產一個字串,然後在網路請求的中被攜帶到服務端進行身份認證,功能上來說和 SessionId 認證方式很像。
JWT 與 SessionId 認證對比
SessionId 認證方式一般做法是使用者登入成功後,服務端生成一個 SessionId,然後將 SessionId 和 使用者的關係進行儲存(記憶體、Redis、資料庫等),之後將 SessionId 寫入 Cookie(一般是主域名下,方便單點登入) 或返回給呼叫方,後續的所有請求都攜帶這個 SessionId 到服務端進行身份認證。
而 JWT 最大區別是登入狀態不在服務端進行儲存,而是通過金鑰生成一個具有有效時間的 Token 返回給前端,Token 中包含類似使用者 Id 等資訊,且是不允許被篡改的,之後的請求將 Token 攜帶到服務端進行認證,認證通過後可解析 Token 拿到使用者標識進行後續操作。
JWT 構成
Header
header 典型的由兩部分組成:token的型別(JWT)和演算法名稱(HMAC、SHA256、RSA等)
如:
{ "alg": "HS256", "typ": "JWT" }
通過 Base64 對這個 JSON 編碼就得到 JWT 的第一部分。
Payload
它包含關於實體(通常是使用者)和其他資料的宣告,分別是 Registered、Public 和 Private 三種類型。
- Registered claims : 預定義的宣告,它們不是強制的,但是推薦。如:iss (issuer)、exp (expiration time)、sub (subject)、aud (audience) 等
- Public claims : 可隨意定義
- Private claims : 用於在同意使用它們的各方之間共享資訊
如:
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
通過 Base64 對這個 JSON 編碼就得到 JWT 的第二部分。
Signature
Signature用來驗證傳送請求者身份,由前兩部分加密形成。
如:
var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey)); var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload))));
通過 Header+Payload+Signature 就得到如下結果:
注意:Payload 最終是可以被解析成明文的,所以在設定 Payload 時一定不能將非加密的敏感資訊儲存在內
JWT 使用方式
- 客戶端到認證服務進行認證
- 認證成功返回 Token
- 客戶端在請求頭中加入 Authorization: Bearer {Token} 訪問 API 資源
.NET Core 整合 JWT
搭建 JWTServer
-
建立 JWTServer(.NET Core Web API)專案 (用來進行身份認證及生成 Token) ;
-
Nuget 安裝 Microsoft.AspNetCore.Authentication.JwtBearer
-
配置檔案中加入 JWT 相關引數
"JwtSetting": { "SecurityKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 金鑰 "Issuer": "jwtIssuertest", // 頒發者 "Audience": "jwtAudiencetest", // 接收者 "ExpireSeconds": 20 // 過期時間(20s) }
-
新增使用者登入介面,模擬身份認證
private readonly static User User = new User { Id = 1, Name = "beck", Password = "123456" }; public async Task<User> LoginAsync(string name, string password) { await Task.CompletedTask; if (User.Name == name && User.Password == password) { return User; } return null; }
-
使用者認證成功後獲得 User 詳細資訊,然後生成 Token
public string GetToken(User user) { //建立使用者身份標識,可按需要新增更多資訊 var claims = new Claim[] { new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim("id", user.Id.ToString(), ClaimValueTypes.Integer32), // 使用者id new Claim("name", user.Name), // 使用者名稱 new Claim("admin", user.IsAdmin.ToString(),ClaimValueTypes.Boolean) // 是否是管理員 }; //建立令牌 var token = new JwtSecurityToken( issuer: _jwtSetting.Issuer, audience: _jwtSetting.Audience, signingCredentials: _jwtSetting.Credentials, claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddSeconds(_jwtSetting.ExpireSeconds) ); string jwtToken = new JwtSecurityTokenHandler().WriteToken(token); return jwtToken; }
-
返回 Token 資訊
{ "Status": true, "Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3Njg1MDUwMS1kZTk5LTRmYmYtYmVlNy01ODAxMjY5ZjNiMTgiLCJpZCI6MSwibmFtZSI6ImJlY2siLCJhZG1pbiI6dHJ1ZSwibmJmIjoxNTUzMzU2MTc1LCJleHAiOjE1NTMzNzYxNzUsImlzcyI6Imp3dElzc3VlcnRlc3QiLCJhdWQiOiJqd3RBdWRpZW5jZXRlc3QifQ.O15rMLMHADGkmhsCNAhcrCMO6c5iQzkXHfbU0jj5HaM", "Type": "Bearer" }
-
將 Token 在 https://jwt.io/ 進行解析檢視效果:
搭建 TestApi
-
建立 TestApi(.NET Core Web API)專案 (模擬需要身份認證的 API 介面)
-
Nuget 安裝 Microsoft.AspNetCore.Authentication.JwtBearer
-
配置檔案中加入 JWT 相關引數
"JwtSetting": { "SecurityKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 金鑰 "Issuer": "jwtIssuertest", // 頒發者 "Audience": "jwtAudiencetest" // 接收者 }
-
Startup 的 ConfigureServices 方法加入如下程式碼:
var jwtSetting = new JwtSetting(); Configuration.Bind("JwtSetting", jwtSetting); services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = jwtSetting.Issuer, ValidAudience = jwtSetting.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey)), // 預設允許 300s的時間偏移量,設定為0 ClockSkew = TimeSpan.Zero }; });
-
Startup 的 Configure 方法加入如下程式碼:
app.UseAuthentication();
-
在需要認證的介面上加 [Authorize] 特性
[HttpGet] [Authorize] public async Task<string> Get() { await Task.CompletedTask; return $"{_identityService.GetUserId()}:{_identityService.GetUserName()}"; }
public class IdentityService : IIdentityService { private readonly IHttpContextAccessor _context; public IdentityService(IHttpContextAccessor context) { _context = context; } public int GetUserId() { var nameId = _context.HttpContext.User.FindFirst("id"); return nameId != null ? Convert.ToInt32(nameId.Value) : 0; } public string GetUserName() { return _context.HttpContext.User.FindFirst("name")?.Value; } }
-
使用 Postman 進行測試
-
請求頭不新增 Authorization ,返回 401 狀態碼:
-
請求頭新增 Authorization,確保 Token 沒過期 :
-