JWT(Json Web Token)
什么是JWT,它是一種對API的保護方案,為什么要進行保護呢
- 防泄漏:你肯定不希望你的數據能被別人隨意調用,比如公司的機密信息,不可能每個人都可以訪問到
- 防攻擊:防止被人偽裝惡意調用接口,利用網關就把請求攔截在外面,防止對服務器造成資源壓力
- 防止被人篡改,導致請求不到信息,防重放攻擊(案例:在公共網絡環境中,請求被截獲,稍后被重放或多次重放)
設計原則
- 輕量級
- 易於開發、測試和部署
- 適合於異構系統(跨操作系統、多語言簡易實現)
- 所有寫操作接口(增、刪、改 操作)
- 非公開的讀接口(如:涉密/敏感/隱私 等)
JWT核心知識
claim:claim就是聲明,就像身份證上的地址,個人信息
Httpcontext是如何通過claim鑒權的:我們去辦業務的時候,需要出示自己的身份證,這個身份證就相當於自己的令牌進行訪問
JWT由三部分組成
第一部分是頭部,頭部分為兩部分(
Header)
- 算法、聲明
- 類型type:JWT類型
第二部分是載荷相當於HTML中的body(
Payload)
第三部分就是簽名也是就是密鑰(
Signature)
基於.NetCore WebApi創建一個簡單的token令牌
public ActionResult<IEnumerable<string>>Get() { //創建聲明Token數組 var claim =newClaim[] { newClaim(ClaimTypes.Name,"ylc"), newClaim(JwtRegisteredClaimNames.Email,"1234567@11.com"), newClaim(JwtRegisteredClaimNames.Sub,"Sub") }; //實例化一個token對象 var token =newJwtSecurityToken(claims:claim); //生成token var jwtToken =newJwtSecurityTokenHandler().WriteToken(token); returnnewstring[]{ jwtToken }; }
啟動項目之后我們會得到一個token令牌

粘貼到jwt官網中可以解密,下面介紹了另一種解密方法

token令牌分為兩部分,紅色部分解析出來代表頭部,粉色代表載荷,我們探入的信息
雖然得到令牌,但是頭部的加密算法顯示none,也沒有數字簽名,所以下面顯示無效簽名

到底這個加密算法怎么配置呢,接下來進行擴展
通過查看實例化token的函數發現它有五個重寫的構造函數
publicJwtSecurityToken(string issuer =null, string audience =null, IEnumerable<Claim> claims =null, DateTime? notBefore =default(DateTime?), DateTime? expires =default(DateTime?), SigningCredentials signingCredentials =null) { if(expires.HasValue && notBefore.HasValue && notBefore >= expires) { throw LogHelper.LogExceptionMessage(newArgumentException(LogHelper.FormatInvariant("IDX12401: Expires: '{0}' must be after NotBefore: '{1}'.", expires.Value, notBefore.Value))); } Payload =newJwtPayload(issuer, audience, claims, notBefore, expires); Header =newJwtHeader(signingCredentials); RawSignature = string.Empty; }
我們就聲明五個參數
[HttpGet] public ActionResult<IEnumerable<string>>Get() { //創建聲明Token數組 var claim =newClaim[] { newClaim(ClaimTypes.Name,"ylc"), newClaim(JwtRegisteredClaimNames.Email,"1234567@11.com"), newClaim(JwtRegisteredClaimNames.Sub,"Sub") }; var key =newSymmetricSecurityKey(Encoding.UTF8.GetBytes("yanglingcong@qq.com"));//密鑰大小要超過128bt,最少要16位 //實例化一個token對象 var token =newJwtSecurityToken( issuer:"http://localhost:5000",//發起人:當前項目 audience:"http://localhost:5000",//訂閱:我們需要誰去使用這個Token claims: claim,//聲明的數組 expires:DateTime.Now.AddDays(1),//當前時間加一小時,一小時后過期 signingCredentials:newSigningCredentials(key,SecurityAlgorithms.HmacSha256)//數字簽名 第一部分是密鑰,第二部分是加密方式 ); var jwtToken =newJwtSecurityTokenHandler().WriteToken(token); returnnewstring[]{ jwtToken }; }
可能會出現一下報錯:IDX10603: Decryption failed. Keys tried: '[PII is hidden]'. Exceptions caught: '[PII is hidden]'. token: '[PII is hidden]' Parameter name: KeySize

安裝最新版
Microsoft.IdentityModel.Logging
nuget包

最后成功運行,加密算法已經顯示出
HS256

但是最下方還是顯示數字簽名無效,還要怎么做呢

我們對令牌解密不一定要看官網通過瀏覽器控制台打印出來也可以
將令牌的一部分放入atob中就可以進行解密

相信做完這些對JWT的三大部分有了一定的了解
聲明Claim的兩種方式
開始在聲明Claim的時候用到了ClaimTypes和JwtRegisteredClaimNames這兩種
var claim =newClaim[] { newClaim(ClaimTypes.Name,"ylc"), newClaim(JwtRegisteredClaimNames.Email,"1234567@11.com"), newClaim(JwtRegisteredClaimNames.Sub,"Sub") };
F12查看ClaimTypes源碼里面寫的都是.net Core中提供的常用的靜態變量

查看JwtRegisteredClaimNames源碼都是JWT自己提供的
但是可能有人想獲取令牌中的Email的值,獲取不到

接下來就說到獲取令牌的三種方式,在這之前要使用http進行上下文注入
[HttpGet("{str}")] public ActionResult<IEnumerable<string>>Get(string str) { ///獲取Token的三種方式 //第一種直接用JwtSecurityTokenHandler提供的read方法 var jwtHander =newJwtSecurityTokenHandler(); JwtSecurityToken jwtSecurityToken = jwtHander.ReadJwtToken(str); //第二種 通過User對象獲取 var sub = User.FindFirst(d => d.Type =="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")?.Value; //第三種 通過Httpcontext上下文中直接獲取 var name = _accessor.HttpContext.User.Identity.Name; var Claims = _accessor.HttpContext.User.Claims; var claimstype =(from item in Claims where item.Type == JwtRegisteredClaimNames.Email select item.Value).ToList(); returnnewstring[]{ JsonConvert.SerializeObject(jwtSecurityToken), sub, name, JsonConvert.SerializeObject(claimstype)}; }
然后在postman進行測試,路徑后面傳入Token令牌,傳入的Token的string參數實際是為了方法一,第二三種是沒有調用str的,所以它們為空跟Token沒有任何關系

除了第一種JwtSecurityTokenHandler獲取的值不為空,其他都為空這是為什么呢
剛剛只是生成了令牌並沒有出現內存中,也就是在http上下文中,下面就是要說到HttpContext是如何鑒權的
首先我們必須在服務里注冊一個Bealer認證,也就是說我們必須注冊一個認證才可以獲取內容
如何進行 Jwt Bealer認證呢,需要在配置服務的做相應的配置
之前在聲明Token的時候指明了發起人訂閱人,這里也要對應上,就相當於解密的過程
publicvoidConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddSingleton<IHttpContextAccessor,HttpContextAccessor>(); var keyBase64 ="yanglingcong@qq.com"; var keyByeArray = Encoding.ASCII.GetBytes(keyBase64); var signkey =newSymmetricSecurityKey(keyByeArray); var tokenValidationParameters =newTokenValidationParameters//生成了一個Token驗證的參數 { ValidateIssuerSigningKey =true, IssuerSigningKey = signkey,//數字簽名 ValidateIssuer =true, ValidIssuer ="http://localhost:5000",//發行人 ValidateAudience =true, ValidAudience ="http://localhost:5000",//訂閱人 ValidateLifetime =true, ClockSkew = TimeSpan.FromSeconds(30), RequireExpirationTime =true, }; services.AddAuthentication("Bearer")//注入服務 1.開啟Bearer認證 2.注冊Bearer服務 .AddJwtBearer(o => { o.TokenValidationParameters = tokenValidationParameters; }); }
完成Bearer以后,我們再進行postman測試,第二種第三種還是為空,這是什么原因呢
我們通過三步創建令牌之后,通過注入完成我們的認證,還需要開啟中間件管道,如果不開啟的話,Token令牌還是不能傳送到HttpContext上下文,中間件實際就是操作Http上下文。
app.UseAuthentication();//開啟中間件
還差最后一步就是登錄,只有登錄認證了Bearer才能將Token轉化成相應的服務
服務對象在經過中間件的時候添加到HttpContext中,然后賦值給User

通過登錄生成Token令牌,返回給客戶端,客戶端拿着Token,放在header里面,傳輸給服務器進行校驗,校驗成功返回數據

返回了三個參數,最后一個還是空

通過項目調試,發現我們之前的Email參數被改名了,原本是JwtRegisteredClaimNames變成了ClaimTypes的聲明方法,這就需要解除,.NetCore自己的映射匹配,使用JWT,在ConfigureServices加上
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();//把 JwtSecurityToken清除
顯示成功

我們在當前GET方法中進行測試加上 [Authorize],把
Authorization取消勾選
,運行

得到401無狀態,我們再把鈎點上,成功授權
