最近沉寂了一段,主要是上半年至關於休息和調整了一段時間,接下來我將開始陸續學習一些新的技術,好比Docker、Jenkins等,都會以生活實例從零開始講解起,到時一併和你們分享和交流。接下來幾節課的內容將會講解JWT,關於JWT的原理解析等等園子裏大有文章,就再也不敘述,這裏咱們講解使用和一些注意的地方。html
在.NET Core以前對於Web應用程序跟蹤用戶登陸狀態最普通的方式則是使用Cookie,當用戶點擊登陸後將對其信息進行加密並響應寫入到用戶瀏覽器的Cookie裏,當用戶進行請求時,服務端將對Cookie進行解密,而後建立用戶身份,整個過程都是那麼順其天然,可是這是客戶端是基於瀏覽器的狀況,若是是客戶端是移動app或者桌面應用程序呢?關於JWT原理能夠參考系列文章https://www.cnblogs.com/RainingNight/p/jwtbearer-authentication-in-asp-net-core.html,固然這只是其中一種限制還有其餘。若是咱們使用Json Web Token簡稱爲JWT而不是使用Cookie,此時Token將表明用戶,同時咱們再也不依賴瀏覽器的內置機制來處理Cookie,咱們僅僅只須要請求一個Token就好。這個時候就涉及到Token認證,那麼什麼是Token認證呢?一言以蔽之:將令牌(咱們有時稱爲AccessToken或者是Bearer Token)附加到HTTP請求中並對其進行身份認證的過程。Token認證被普遍應用於移動端或SPA。ajax
JWT由三部分構成,Base64編碼的Header,Base64編碼的Payload,簽名,三部分經過點隔開。第一部分以Base64編碼的Header主要包括Token的類型和所使用的算法,例如:算法
{ "alg": "HS265", "typ": "JWT" }
第二部分以Base64編碼的Payload主要包含的是聲明(Claims),例如,以下:json
{ "sub": "765032130654732", "name": "jeffcky" }
第三部分則是將Key經過對應的加密算法生成簽名,最終三部分以點隔開,好比以下形式:api
1 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. 2 eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiSmVmZmNreSIsImVtYWlsIjoiMjc1MjE1NDg0NEBxcS5jb20iLCJleHAiOjE1NjU2MTUzOTgsIm5iZiI6MTU2MzE5NjE5OCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAxIn0. 3 OJjlGJOnCCbpok05gOIgu5bwY8QYKfE2pOArtaZJbyI
到這裏此時咱們應該知道:JWT包含的信息並無加密,好比爲了獲取Payload,咱們大可經過好比谷歌控制檯中的APi(atob)對其進行解碼,以下:瀏覽器
那如我所說既然JWT包含的信息並無加密,只是進行了Base64編碼,豈不是很是不安全呢?固然不是這樣,還沒說完,第三部分就是簽名,雖然咱們對Payload(姑且翻譯爲有效負載),未進行加密,可是如有蓄意更換Payload,此時簽名將能充分保證Token無效,除非將簽名的Key不當心暴露在光天化日之下,不然必須是安全的。好了,到了這裏,咱們稍稍講解了下JWT構成,接下來咱們進入如何在.NET Core中使用JWT。安全
在.NET Core中如何使用JWT,那麼咱們必須得知曉如何建立JWT,接下來咱們首先建立一個端口號爲5000的APi,建立JWT,而後咱們須要安裝 System.IdentityModel.Tokens.Jwt 包,以下:app
咱們直接給出代碼來建立Token,而後一一對其進行詳細解釋,代碼以下:ide
var claims = new Claim[] { new Claim(ClaimTypes.Name, "Jeffcky"), new Claim(JwtRegisteredClaimNames.Email, "2752154844@qq.com"), new Claim(JwtRegisteredClaimNames.Sub, "D21D099B-B49B-4604-A247-71B0518A0B1C"), }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456")); var token = new JwtSecurityToken( issuer: "http://localhost:5000", audience: "http://localhost:5001", claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddHours(1), signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) ); var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
如上咱們在聲明集合中初始化聲明時,咱們使用了兩種方式,一個是使用 ClaimTypes ,一個是 JwtRegisteredClaimNames ,那麼這兩者有什麼區別?以及咱們到底應該使用哪一種方式更好?或者說兩種方式都使用是否有問題呢?針對ClaimTypes則來自命名空間 System.Security.Claims ,而JwtRegisteredClaimNames則來自命名空間 System.IdentityModel.Tokens.Jwt ,兩者在獲取聲明方式上是不一樣的,ClaimTypes是沿襲微軟提供獲取聲明的方式,好比咱們要在控制器Action方法上獲取上述ClaimTypes.Name的值,此時咱們須要F12查看Name的常量定義值是多少,以下:函數
接下來則是獲取聲明Name的值,以下:
var sub = User.FindFirst(d => d.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")?.Value;
那麼若是咱們想要獲取聲明JwtRegisterClaimNames.Sub的值,咱們是否是應該如上一樣去獲取呢?咱們來試試。
var sub = User.FindFirst(d => d.Type == JwtRegisteredClaimNames.Sub)?.Value;
此時咱們發現爲空沒有獲取到,這是爲什麼呢?這是由於獲取聲明的方式默認是走微軟定義的一套映射方式,若是咱們想要走JWT映射聲明,那麼咱們須要將默認映射方式給移除掉,在對應客戶端Startup構造函數中,添加以下代碼:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
若是用過並熟悉IdentityServer4的童鞋關於這點早已明瞭,由於在IdentityServer4中映射聲明好比用戶Id即(sub)是使用的JWT,也就是說使用的JwtRegisteredClaimNames,此時咱們再來獲取Sub看看。
因此以上對於初始化聲明兩種方式的探討並無用哪一個更好,由於對於使用ClaimTypes是沿襲以往聲明映射的方式,若是要出於兼容性考慮,能夠結合兩種聲明映射方式來使用。接下來咱們來看生成簽名代碼,生成簽名是以下代碼:
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456"));
如上咱們給出簽名的Key是1234567890123456,是否是給定Key的任意長度皆可呢,顯然不是,關於Key的長度至少是16,不然會拋出以下錯誤
接下來咱們再來看實例化Token的參數,即以下代碼:
var token = new JwtSecurityToken( issuer: "http://localhost:5000", audience: "http://localhost:5001", claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddHours(1), signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) );
issuer表明頒發Token的Web應用程序,audience是Token的受理者,若是是依賴第三方來建立Token,這兩個參數確定必需要指定,由於第三方本就不受信任,如此設置這兩個參數後,咱們可驗證這兩個參數。要是咱們徹底不關心這兩個參數,可直接使用JwtSecurityToken的構造函數來建立Token,以下:
var claims = new Claim[] { new Claim(ClaimTypes.Name, "Jeffcky"), new Claim(JwtRegisteredClaimNames.Email, "2752154844@qq.com"), new Claim(JwtRegisteredClaimNames.Sub, "D21D099B-B49B-4604-A247-71B0518A0B1C"), new Claim(JwtRegisteredClaimNames.Exp, $"{new DateTimeOffset(DateTime.Now.AddMilliseconds(1)).ToUnixTimeSeconds()}"), new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456")); var jwtToken = new JwtSecurityToken(new JwtHeader(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims));
這裏須要注意的是Exp和Nbf是基於Unix時間的字符串,因此上述經過實例化DateTimeOffset來建立基於Unix的時間。到了這裏,咱們已經清楚的知道如何建立Token,接下來咱們來使用Token獲取數據。咱們新建一個端口號爲5001的Web應用程序,同時安裝包【 Microsoft.AspNetCore.Authentication.JwtBearer 】接下來在Startup中ConfigureServices添加以下代碼:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456")), ValidateIssuer = true, ValidIssuer = "http://localhost:5000", ValidateAudience = true, ValidAudience = "http://localhost:5001", ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(5) }; });
如上述若Token依賴於第三方而建立,此時必然會配置issuer和audience,同時在我方也如上必須驗證issuer和audience,上述咱們也驗證了簽名,咱們經過設置 ValidateLifetime 爲true,說明驗證過時時間而並不是Token中的值,最後設置 ClockSkew 有效期爲5分鐘。對於設置 ClockSkew 除了如上方式外,還可以下設置默認也是5分鐘。
ClockSkew = TimeSpan.Zero
如上對於認證方案咱們使用的是 JwtBearerDefaults.AuthenticationScheme 即Bearer,除此以外咱們也能夠自定義認證方案名稱,以下:
最後別忘記添加認證中間件在Configure方法中,認證中間件必須放在使用MVC中間件以前,以下:
app.UseAuthentication(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });
到了這裏,咱們經過端口爲5000的Web Api建立了Token,並配置了端口號爲5001的Web應用程序使用JWT認證,接下來最後一步則是調用端口號爲5000的APi獲取Token,並將Token設置到請求頭中Authorization鍵的值,格式以下(注意Bearer後面有一個空格):
('Authorization', 'Bearer ' + token);
咱們在頁面上放置一個按鈕點擊獲取端口號爲5000的Token後,接下來請求端口號爲5001的應用程序,以下:
$(function () { $('#btn').click(function () { $.get("http://localhost:5000/api/token").done(function (token) { $.ajax({ type: 'get', contentType: 'application/json', url: 'http://localhost:5001/api/home', beforeSend: function (xhr) { if (token !== null) { xhr.setRequestHeader('Authorization', 'Bearer ' + token); } }, success: function (data) { alert(data); }, error: function (xhr) { alert(xhr.status); } }); }); }); });
本節咱們講解了在.NET Core中使用JWT進行認證以及一點點注意事項,比較基礎性的東西,下一節講解完在JWT中使用刷新Token,開始正式進入Docker系列,感謝閱讀,下節見。