JWT 介紹
JWT(JSON Web Token)是一種開放標(biāo)準(zhǔn),它以 JSON 對象的方式在各方之間安全地傳輸信息训枢。通俗的說忘巧,就是通過數(shù)字簽名算法生產(chǎn)一個字符串,然后在網(wǎng)絡(luò)請求的中被攜帶到服務(wù)端進(jìn)行身份認(rèn)證十酣,功能上來說和 SessionId 認(rèn)證方式很像际长。
JWT 與 SessionId 認(rèn)證對比
SessionId 認(rèn)證方式一般做法是用戶登錄成功后工育,服務(wù)端生成一個 SessionId,然后將 SessionId 和 用戶的關(guān)系進(jìn)行存儲(內(nèi)存如绸、Redis怔接、數(shù)據(jù)庫等),之后將 SessionId 寫入 Cookie(一般是主域名下森书,方便單點登錄) 或返回給調(diào)用方谎势,后續(xù)的所有請求都攜帶這個 SessionId 到服務(wù)端進(jìn)行身份認(rèn)證。
而 JWT 最大區(qū)別是登錄狀態(tài)不在服務(wù)端進(jìn)行存儲猖毫,而是通過密鑰生成一個具有有效時間的 Token 返回給前端须喂,Token 中包含類似用戶 Id 等信息趁蕊,且是不允許被篡改的掷伙,之后的請求將 Token 攜帶到服務(wù)端進(jìn)行認(rèn)證又兵,認(rèn)證通過后可解析 Token 拿到用戶標(biāo)識進(jìn)行后續(xù)操作。
JWT 構(gòu)成
Header
header 典型的由兩部分組成:token的類型(JWT)和算法名稱(HMAC宙地、SHA256逆皮、RSA等)
如:
{
"alg": "HS256",
"typ": "JWT"
}
通過 Base64 對這個 JSON 編碼就得到 JWT 的第一部分电谣。
Payload
它包含關(guān)于實體(通常是用戶)和其他數(shù)據(jù)的聲明,分別是 Registered辰企、Public 和 Private 三種類型牢贸。
- Registered claims : 預(yù)定義的聲明,它們不是強(qiáng)制的潜索,但是推薦竹习。如: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用來驗證發(fā)送請求者身份宾毒,由前兩部分加密形成殿遂。
如:
var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey));
var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload))));
通過 Header+Payload+Signature 就得到如下結(jié)果:
注意:Payload 最終是可以被解析成明文的,所以在設(shè)置 Payload 時一定不能將非加密的敏感信息存儲在內(nèi)
JWT 使用方式
- 客戶端到認(rèn)證服務(wù)進(jìn)行認(rèn)證
- 認(rèn)證成功返回 Token
- 客戶端在請求頭中加入 Authorization: Bearer {Token} 訪問 API 資源
.NET Core 集成 JWT
搭建 JWTServer
創(chuàng)建 JWTServer(.NET Core Web API)項目 (用來進(jìn)行身份認(rèn)證及生成 Token)耳峦;
Nuget 安裝 Microsoft.AspNetCore.Authentication.JwtBearer
-
配置文件中加入 JWT 相關(guān)參數(shù)
"JwtSetting": { "SecurityKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 密鑰 "Issuer": "jwtIssuertest", // 頒發(fā)者 "Audience": "jwtAudiencetest", // 接收者 "ExpireSeconds": 20 // 過期時間(20s) }
-
添加用戶登錄接口,模擬身份認(rèn)證
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; }
-
用戶認(rèn)證成功后獲得 User 詳細(xì)信息咬荷,然后生成 Token
public string GetToken(User user) { //創(chuàng)建用戶身份標(biāo)識轻掩,可按需要添加更多信息 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) // 是否是管理員 }; //創(chuàng)建令牌 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/ 進(jìn)行解析查看效果:
搭建 TestApi
創(chuàng)建 TestApi(.NET Core Web API)項目 (模擬需要身份認(rèn)證的 API 接口)
Nuget 安裝 Microsoft.AspNetCore.Authentication.JwtBearer
-
配置文件中加入 JWT 相關(guān)參數(shù)
"JwtSetting": { "SecurityKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 密鑰 "Issuer": "jwtIssuertest", // 頒發(fā)者 "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)), // 默認(rèn)允許 300s 的時間偏移量罕扎,設(shè)置為0 ClockSkew = TimeSpan.Zero }; });
-
Startup 的 Configure 方法加入如下代碼:
app.UseAuthentication();
-
在需要認(rèn)證的接口上加 [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 進(jìn)行測試
-
請求頭不添加 Authorization 腔召,返回 401 狀態(tài)碼:
-
-
請求頭添加 Authorization扮惦,確保 Token 沒過期 :