ASP.NET Core Web Api之JWT(一)

前言

最近沉寂了一段,主要是上半年至關於休息和調整了一段時間,接下來我將開始陸續學習一些新的技術,好比Docker、Jenkins等,都會以生活實例從零開始講解起,到時一併和你們分享和交流。接下來幾節課的內容將會講解JWT,關於JWT的原理解析等等園子裏大有文章,就再也不敘述,這裏咱們講解使用和一些注意的地方。html

爲何要使用JWT

在.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

Json Web Token基礎

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

在.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系列,感謝閱讀,下節見。

相關文章
相關標籤/搜索