2018年4月16日 星期一

JWT JSON Web Token 使用 ASP.NET Core 2.0 Web API 的逐步練習教學與各種情境測試

至於要如何在使用端 Client 來進行使用者身分驗證,並且取得 JWT Token 權杖,接著使用該權杖來呼叫其他 Web API 的方法,可以參考 使用 HttpClient 進行 JWT 身分驗證與呼叫需要授權的 API 和重新更新 Token 權杖的程式設計範例 ;在這篇文章中,也包含了如何在用戶端進行更新 JWT Token 的作法。

在這篇文章中,我們將來體驗如何使用 ASP.NET Core 2.0 專案,搭配 JWT 機制,設計出具有存取權杖 Token 基礎的 Web API 服務。

了解更多關於 [ASP.NET Core 簡介
了解更多關於 [在 ASP.NET 4.x 和 ASP.NET Core 之間進行選擇]  



首先,您需要了解甚麼叫做 JSON Web Token (JWT),在 RFC 7519 文件中,有詳細與明確的定義,各位可以從這裡了解到甚麼叫做 JWT 與其相關資料結構與組成成員。另外,在這個 jwt.io 網頁中,您可以找到各種 JWT 的工具與程式庫和更多的解釋說明,例如,您可以快速瀏覽這篇 JWT 簡介 文章,快速了解到何謂 JWT與如何使用它。
由於我們使用的是 ASP.NET Core 2.0 Web API 方式來進行說明如何建置出一個 JWT 存取權杖 Access Token 的應用,因此,您需要對於 ASP.NET Core 2.0 有所認識,若您對於 ASP.NET Core 2.0 還沒接觸過,您可以參考 ASP.NET Core 2.0 使用者入門 這個網頁上的相關介紹內容與知識。另外,您的開發工具也需要準備好,您需要安裝 .NET Core 2.0.0 或更新版本,在您的 Visual Studio Installer 工具中,確認已經升級到最新的 Visual Studio2017 15.6.1 版本(這個版本是寫這篇文章時候的最新版本)或更新版本,加上下列工作負載需要勾選 [ASP.NET 與網頁程式開發] & [.NET Core 跨平台開發]。
Visual Studio 2017 About
本篇文章的專案範例原始碼,可以從 https://github.com/vulcanlee/CSharpNotes2018/tree/master/CoreJWT取得

建立一個 ASP.NET Core 2.0 Web API 專案

現在,讓我們開始進行建立一個 ASP.NET Core 2.0 Web API 專案
  • 點選 Visual Studio 2017 功能表 [檔案] > [新增] > [專案]
  • 當出現 [新增專案] 對話窗,點選 [已安裝] > [Visual C#] > [.NET Core] > [ASP.NET Core Web 應用程式],並且在最下方 [名稱] 欄位處,輸入 CoreJWT,作為此練習的開發專案名稱。
    Visual Studio 2017 新增專案對話窗
  • 當出現了 [新增 ASP.NET Core Web API 應用程式 - CoreJWT] 對話窗,請點選 [API] 項目,確認該對話窗的右方的 [驗證] 設定值為 [無驗證],如同下方螢幕截圖相同,就可以點選右下角的 [確定] 按鈕。
    新增 ASP.NET Core Web API 應用程式 - CoreJWT對話窗
  • 稍微等候一下,我們可以看到這個 ASP.NET Core 2.0 Web API 專案已經建立完成,並且從 Visual Studio 2017 方案總管中,您將會看到如同下面螢幕截圖,這裡就是這個 Web API 專案的所有檔案結構。
    Visual Studio 2017 ASP.NET Web API 方案專案結構
  • 最後,讓我們實際建置、執行這個專案,您就會在瀏覽器上看到 ["value1","value2"] 這樣的輸出結果。
    Web API 專案執行結果

修正 Startup 類別的運作行為

  • 我們先要定義 JWT Token 存取權杖會用到的定義字串,所以,請在專案中找到 appsettings.json 檔案,將它打開來,並且將這個檔案內容下改成為如下所示。
    在這裡,我們可以看到,我們新增了 Tokens 這個節點,裡面有三個子節點,這三個定義字串,我們將會等下會用到。
JSON
{
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  },
  "Tokens": {
    "ValidIssuer": "CoreJWT.vulcan.net",
    "ValidAudience": "Xamarin RESTful API",
    "IssuerSigningKey": "4{!-!Rjx2.W]fX~jN:<Ae$D'dLlnG%%xo`W2D5TatBx&MJjZ>(+ujy*G*Y<XH.X"
  }
}
  • 在專案中,找到 [Startup.cs] 檔案,將其打開,您將會看到這個 Startup 類別的定義,在這個 Startup 類別中,您也會看到 public void ConfigureServices(IServiceCollection services) 這個方法與 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 這個方法,我們等下就要來修正這兩個方法內的定義程式碼。
C Sharp / C#
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseMvc();
    }
}
  • 請在 Starpup.cs 檔案內,加入底下命名空間參考
C Sharp / C#
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System.Diagnostics;
  • 請使用底下程式碼,將 Starpup 類別定義程式碼,改成底下的類別定義程式碼
C Sharp / C#
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = Configuration["Tokens:ValidIssuer"],
                    ValidAudience = Configuration["Tokens:ValidAudience"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:IssuerSigningKey"])),
                    RequireExpirationTime = true,
                };
                options.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = context =>
                    {
                        context.NoResult();

                        context.Response.StatusCode = 401;
                        context.Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = context.Exception.Message;
                        Debug.WriteLine("OnAuthenticationFailed: " + context.Exception.Message);
                        return Task.CompletedTask;
                    },
                    OnTokenValidated = context =>
                    {
                        Console.WriteLine("OnTokenValidated: " +
                            context.SecurityToken);
                        return Task.CompletedTask;
                    }

                };
            });

        services.AddMvc();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseAuthentication();

        app.UseMvc();
    }
}
  • 想要讓這個 Web API 專案可以套用 JWT Bearer 驗證,我們需要先在 Configure 方法內,呼叫 app.UseAuthentication(); 方法。
  • 接著,我們需要在 ConfigureServices 方法內,叫用AddJwtBearer方法中的ConfigureServices方法。
    在 AddJwtBearer 方法內,需要設定 JwtBearerOptions.TokenValidationParameters 屬性,因此,我們建立一個 TokenValidationParameters 物件 Object / 執行個體 Instance ,並且設定這個 JWT Token 存取權杖會用到的相關屬性值。
  • 我們也訂閱了 JwtBearerOptions.Events 的 相關事件,在這裡,我們透過建立了 JwtBearerEvents 物件,並且設定相關事件的委派方法。我們綁定了 OnAuthenticationFailed 這個事件,也就是當所傳入的存取權杖不是正確的、合法的、有效的,此時,就會呼叫這個委派事件的方法,在這個方法哩,將會回傳 401 狀態碼 Status Code,說明此次身分驗證結果是失敗的。反之,OnTokenValidated 這個委派事件的方法,將會被呼叫。

建立控制器 Controller 與動作 Action

  • 現在,我們需要來建立控制器與相關方法,並且接下來的檢驗各種不同的使用情境下,我們的 Web API 會產生甚麼樣的結果。
  • 首先,滑鼠右擊專案的 Controllers 目錄,選擇 [加入] > [控制器]
  • 當出現 [新增 Scaffold] 對話窗,請選擇 [執行讀取/寫入動作的 API 控制器],並且點選 [新增] 按鈕
    Visual Studio 2017 新增 Scaffold 對話窗
  • 當出現了 [新增 執行讀取/寫入動作的 API 控制器] 對話窗,請在控制器名稱欄位內,輸入 JWTToken,最後點選 [新增]
    新增 執行讀取/寫入動作的 API 控制器 對話窗
  • 接著,滑鼠右擊專案的 Controllers 目錄,選擇 [加入] > [控制器]
  • 當出現 [新增 Scaffold] 對話窗,請選擇 [執行讀取/寫入動作的 API 控制器],並且點選 [新增] 按鈕
    Visual Studio 2017 新增 Scaffold 對話窗
  • 當出現了 [新增 執行讀取/寫入動作的 API 控制器] 對話窗,請在控制器名稱欄位內,輸入 Login ,最後點選 [新增]
    新增 執行讀取/寫入動作的 API 控制器 對話窗
  • 請使用底下程式碼,取代 LoginController.cs 的所有內容
    在 LoginController 控制器中,若使用 HTTP GET 方法,會取得一個 JWT 存取權杖 Access Token,這個存取權杖將會擁有 7 天的有效期限。若使用 HTTP POST 方法,會取得一個 JWT 存取權杖 Access Token,這個存取權杖將會擁有 1 分鐘的有效期限。
C Sharp / C#
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;

namespace CoreJWT.Controllers
{
    [Produces("application/json")]
    [Route("api/Login")]
    public class LoginController : Controller
    {
        public IConfiguration Configuration { get; }
        public LoginController(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        [HttpGet]
        public async Task<IActionResult> Get()
        {
            var claims = new[]
            {
                new Claim(JwtRegisteredClaimNames.NameId, "vulcan.lee@vulcan.net"),
                new Claim(ClaimTypes.Role, "Admini"),
            };

            var token = new JwtSecurityToken
            (
                issuer: Configuration["Tokens:ValidIssuer"],
                audience: Configuration["Tokens:ValidAudience"],
                claims: claims,
                expires: DateTime.UtcNow.AddDays(7),
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey
                (Encoding.UTF8.GetBytes(Configuration["Tokens:IssuerSigningKey"])),
                SecurityAlgorithms.HmacSha256)
            );

            return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
        }

        // POST: api/Login
        [HttpPost]
        public async Task<IActionResult> Post()
        {
            var claims = new[]
      {
                new Claim(JwtRegisteredClaimNames.NameId, "vulcan.lee@vulcan.net"),
                new Claim(ClaimTypes.Role, "Admini"),
            };

            var token = new JwtSecurityToken
            (
                issuer: Configuration["Tokens:ValidIssuer"],
                audience: Configuration["Tokens:ValidAudience"],
                claims: claims,
                expires: DateTime.UtcNow.AddMinutes(1),
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey
                            (Encoding.UTF8.GetBytes(Configuration["Tokens:IssuerSigningKey"])),
                        SecurityAlgorithms.HmacSha256)
            );

            return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
        }
    }
}
  • 請使用底下程式碼,取代 JWTTokenController.cs 的所有內容
    在這個 JWTTokenController 控制器中
    • HTTP GET 動作
      無須傳送任任何存取權杖,就可以直接呼叫
    • HTTP PUT 動作
      因為這個動作方法上,有使用 Authorize 標示,所以,當使用 PUT 動作呼叫這個 Web API 的時候,需要傳入一個存取權杖才能夠呼叫。
    • HTTP POST 動作
      因為這個動作方法上,有使用 Authorize(Roles = "Admin") 標示,所以,當使用 POST 動作呼叫這個 Web API 的時候,需要傳入一個存取權杖才能夠呼叫,而且這個存取權杖中,需要擁有 Admin 這個 Roles。我們可以從 Login 控制器的 GET 或者 POST 動作方法中得知,我們所取得的存取權杖,僅具有 new Claim(ClaimTypes.Role, "Admini") 這樣的 Role,因此,呼叫這個動作將會失敗。
    • HTTP DELETE 動作
      因為這個動作方法上,有使用 Authorize(Roles = "Admini") 標示,所以,當使用 DELETE 動作呼叫這個 Web API 的時候,需要傳入一個存取權杖才能夠呼叫,而且這個存取權杖中,需要擁有 Admini 這個 Roles。我們可以從 Login 控制器的 GET 或者 POST 動作方法中得知,我們所取得的存取權杖,僅具有 new Claim(ClaimTypes.Role, "Admini") 這樣的 Role,因此,呼叫這個動作將會成功。
C Sharp / C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace CoreJWT.Controllers
{
    [Produces("application/json")]
    [Route("api/JWTToken")]
    public class JWTTokenController : Controller
    {
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "JWTToken", "Get ( no Authorize )" };
        }

        [HttpPut]
        [Authorize]
        public IEnumerable<string> Put()
        {
            return new string[] { "JWTToken", "Put ( has Authorize)" };
        }

        [HttpPost]
        [Authorize(Roles = "Admin")]
        public IEnumerable<string> Post()
        {
            return new string[] { "JWTToken", "Post ( has Authorize and Roles=Admin )" };
        }

        [HttpDelete]
        [Authorize(Roles = "Admini")]
        public IEnumerable<string> Delete()
        {
            return new string[] { "JWTToken", "Post ( has Authorize and Roles=Admini )" };
        }
    }
}

進行 RESTfule Web API 的測試

最後,讓我們使用 PostMan 這個工具,開始進行我們剛剛寫好的 JWT 各種 Web API 來進行測試。現在,請開始執行這個專案。

取得具有 7 天有效期限的 Access Token 存取權杖

JSON
{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJ2dWxjYW4ubGVlQHZ1bGNhbi5uZXQiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmkiLCJleHAiOjE1MjEzODM2NDksImlzcyI6IkNvcmVKV1QudnVsY2FuLm5ldCIsImF1ZCI6IlhhbWFyaW4gUkVTVGZ1bCBBUEkifQ.2w6BoAq_KbVlrVJW_ldrO5-cLuMPMp7uQ77tGKfakTA"
}
使用 HTTP GET 取得存取權杖
  • 打開 https://jwt.io/ 網頁,請剛剛取得的存取權杖(token的值,也就是 eyJ...TA 這段字串)複製下來,貼到網頁中的 Encoded 文字輸入盒中,會看到這個存取權杖的詳細內容。
    透過 Jwt.io 網頁,驗證 使用 HTTP GET 取得存取權杖

取得具有 1 分鐘有效期限的 Access Token 存取權杖

JSON
{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJ2dWxjYW4ubGVlQHZ1bGNhbi5uZXQiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmkiLCJleHAiOjE1MjA3NzkxNTAsImlzcyI6IkNvcmVKV1QudnVsY2FuLm5ldCIsImF1ZCI6IlhhbWFyaW4gUkVTVGZ1bCBBUEkifQ.1iOWpcqtKyuFZ94ShA1XT1wctAbVzVN6daemUqScStU"
}
使用 HTTP POST 取得存取權杖
  • 打開 https://jwt.io/ 網頁,請剛剛取得的存取權杖(token的值,也就是 eyJ...StU 這段字串)複製下來,貼到網頁中的 Encoded 文字輸入盒中,會看到這個存取權杖的詳細內容。
    透過 Jwt.io 網頁,驗證 使用 HTTP GET 取得存取權杖

使用 GET 方法呼叫 JWTToken 控制器 Web API

使用 PUT 方法呼叫 JWTToken 控制器 Web API

  • 打開 PostMan
  • 設定使用 HTTP PUT 方法
  • 輸入網址為 http://localhost:50554/api/JWTToken
  • 點選 [SEND] 按鈕
  • 我們將會得到底下的輸出結果,在這裡,我們看到了,此次呼叫,得到了 Status Code 狀態碼 401,也就是說失敗了,不過,這個 Web API 已經受到了保護,也就是說,若沒有提供正確的存取權杖,是無法正常呼叫與使用這個 Web API 的
    沒有用 Access Token 呼叫有標示 Authorize 的 Web API

使用具有 7 天有效期限的 Access Token 存取權杖與 GET 方法呼叫 JWTToken Web API

  • 打開 PostMan
  • 設定使用 HTTP GET 方法
  • 輸入網址為 http://localhost:50554/api/JWTToken
  • 點選 [Header] 標籤頁次
  • 在 [Key] 欄位 輸入 Authorization
  • 在 [Value] 欄位 輸入 Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJ2dWxjYW4ubGVlQHZ1bGNhbi5uZXQiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmkiLCJleHAiOjE1MjA3NzkxNTAsImlzcyI6IkNvcmVKV1QudnVsY2FuLm5ldCIsImF1ZCI6IlhhbWFyaW4gUkVTVGZ1bCBBUEkifQ.1iOWpcqtKyuFZ94ShA1XT1wctAbVzVN6daemUqScStU
    其中,在 Bearer 之後的字串,是剛剛取得具有 7 天有效期限的 Access Token 存取權杖
  • 點選 [SEND] 按鈕
  • 我們將會得到底下的輸出結果,在這裡,因為這個 GET 動作方法沒有受到保護,不管我們是否有使用 Authorization 標頭 Header,我們都可以正常使用這個 Web API
    沒有用 Access Token 呼叫有標示 Authorize 的 Web API

使用具有 1 分鐘有效期限的 Access Token 存取權杖與 GET 方法呼叫 JWTToken Web API

  • 打開 PostMan
  • 設定使用 HTTP GET 方法
  • 輸入網址為 http://localhost:50554/api/JWTToken
  • 點選 [Header] 標籤頁次
  • 在 [Key] 欄位 輸入 Authorization
  • 在 [Value] 欄位 輸入 Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJ2dWxjYW4ubGVlQHZ1bGNhbi5uZXQiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmkiLCJleHAiOjE1MjEzODM2NDksImlzcyI6IkNvcmVKV1QudnVsY2FuLm5ldCIsImF1ZCI6IlhhbWFyaW4gUkVTVGZ1bCBBUEkifQ.2w6BoAq_KbVlrVJW_ldrO5-cLuMPMp7uQ77tGKfakTA
    其中,在 Bearer 之後的字串,是剛剛取得具有 1 分鐘有效期限的 Access Token 存取權杖;在 JSON Web Token (JWT),在 RFC 7519 文件中有提到,雖然我們設定這個 Access Token 存取權杖有效時間只有1分鐘,不過,在後端伺服器實作上,還是會有些容許時間差,因此,建議您可以在取得這個存取權杖之後的 5~10分鐘後,再來使用這個存取權杖。
  • 點選 [SEND] 按鈕
  • 我們將會得到底下的輸出結果,在這裡,因為這個 GET 動作方法沒有受到保護,不管我們是否有使用 Authorization 標頭 Header,我們都可以正常使用這個 Web API,但是,您會發現到,此時的回傳的 Status Code 狀態碼是 IDX10223: Lifetime validation failed. The token is expired. 。而且,它實際上還會有這樣的訊息,ValidTo: '03/11/2018 14:39:10' Current time: '03/11/2018 16:19:22'. 告知您,這個存取權杖已經真正逾期了。
    JWT Access Token PostMan Asp.net Core Web API

使用具有 7 天有效期限的 Access Token 存取權杖與 PUT 方法呼叫 JWTToken Web API

  • 打開 PostMan
  • 設定使用 HTTP PUT 方法
  • 輸入網址為 http://localhost:50554/api/JWTToken
  • 點選 [Header] 標籤頁次
  • 在 [Key] 欄位 輸入 Authorization
  • 在 [Value] 欄位 輸入 Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJ2dWxjYW4ubGVlQHZ1bGNhbi5uZXQiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmkiLCJleHAiOjE1MjA3NzkxNTAsImlzcyI6IkNvcmVKV1QudnVsY2FuLm5ldCIsImF1ZCI6IlhhbWFyaW4gUkVTVGZ1bCBBUEkifQ.1iOWpcqtKyuFZ94ShA1XT1wctAbVzVN6daemUqScStU
    其中,在 Bearer 之後的字串,是剛剛取得具有 7 天有效期限的 Access Token 存取權杖
  • 點選 [SEND] 按鈕
  • 我們將會得到底下的輸出結果,雖然 PUT 動作有受到 [Authorize] 屬性的保護,不過,我們有透過 HTTP 基本驗證協定,將 JWT Access Token 存取權杖傳送到伺服器端,而這個存取權杖是合法且有效的,因此,我們都可以正常使用這個 Web API
    JWT Access Token PostMan Asp.net Core Web API

使用具有 1 分鐘有效期限的 Access Token 存取權杖與 PUT 方法呼叫 JWTToken Web API

  • 打開 PostMan
  • 設定使用 HTTP PUT 方法
  • 輸入網址為 http://localhost:50554/api/JWTToken
  • 點選 [Header] 標籤頁次
  • 在 [Key] 欄位 輸入 Authorization
  • 在 [Value] 欄位 輸入 Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJ2dWxjYW4ubGVlQHZ1bGNhbi5uZXQiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmkiLCJleHAiOjE1MjEzODM2NDksImlzcyI6IkNvcmVKV1QudnVsY2FuLm5ldCIsImF1ZCI6IlhhbWFyaW4gUkVTVGZ1bCBBUEkifQ.2w6BoAq_KbVlrVJW_ldrO5-cLuMPMp7uQ77tGKfakTA
    其中,在 Bearer 之後的字串,是剛剛取得具有 1 分鐘有效期限的 Access Token 存取權杖
  • 點選 [SEND] 按鈕
  • 我們將會得到底下的輸出結果,在這裡,因為這個 PUT 動作方法有受到 [Authorize] 屬性的保護,因此,當我們使用了一個時間逾期的存取權杖,雖然它是合法、正確的權杖,但卻不是有效的,因此我們依然會得到回傳的 Status Code 狀態碼是 IDX10223: Lifetime validation failed. The token is expired. 。並且,我們看到回應結果的 Body 欄位,是沒有任何資料的,也就是這個 PUT 動作方法,是沒有成功呼叫的。
    JWT Access Token PostMan Asp.net Core Web API

使用具有 7 天有效期限的 Access Token 存取權杖與 POST 方法呼叫 JWTToken Web API

現在,我們使用具有 7 天有效期限的 Access Token 存取權杖,使用 POST 方法來呼叫 JWTToken Web API,可是,在這個 Web API 中,使用了 [Authorize(Roles = "Admin")] 屬性來保護這個 Web API,也就是說,這個存取權杖必須要具有 Admin 的 Role。
可是,在我們進行呼叫登入 Web API 的時候,每個存取權杖都會產生一個 Admini 這個 Claim,我們是使用這段程式碼 new Claim(ClaimTypes.Role, "Admini")。所以,原則上,我們的存取權杖雖然是正確的,但是因為沒有相同的 Role,所以,還是無法呼叫這個 Web API。
  • 打開 PostMan
  • 設定使用 HTTP POST 方法
  • 輸入網址為 http://localhost:50554/api/JWTToken
  • 點選 [Header] 標籤頁次
  • 在 [Key] 欄位 輸入 Authorization
  • 在 [Value] 欄位 輸入 Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJ2dWxjYW4ubGVlQHZ1bGNhbi5uZXQiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmkiLCJleHAiOjE1MjA3NzkxNTAsImlzcyI6IkNvcmVKV1QudnVsY2FuLm5ldCIsImF1ZCI6IlhhbWFyaW4gUkVTVGZ1bCBBUEkifQ.1iOWpcqtKyuFZ94ShA1XT1wctAbVzVN6daemUqScStU
    其中,在 Bearer 之後的字串,是剛剛取得具有 7 天有效期限的 Access Token 存取權杖
  • 點選 [SEND] 按鈕
  • 我們將會得到底下的輸出結果,根據在做這個測試的說明,我們得到了 403 Foribidden 這樣的錯誤狀態碼 Status Code,而且回應的 Body 也同樣的沒有回傳任何內容,也就是這個 POST 方法,是沒有被呼叫到。
    JWT Access Token PostMan Asp.net Core Web API

使用具有 7 天有效期限的 Access Token 存取權杖與 DELETE 方法呼叫 JWTToken Web API

現在,我們使用具有 7 天有效期限的 Access Token 存取權杖,使用 DELETE 方法來呼叫 JWTToken Web API,在這個 Web API 中,使用了 [Authorize(Roles = "Admini")] 屬性來保護這個 Web API,也就是說,這個存取權杖必須要具有 Admini 的 Role。
在我們進行呼叫登入 Web API 的時候,每個存取權杖都會產生一個 Admini 這個 Claim,我們是使用這段程式碼 new Claim(ClaimTypes.Role, "Admini")。所以,原則上,我們的存取權杖雖然是可以呼叫這個 Web API。
  • 打開 PostMan
  • 設定使用 HTTP DELETE 方法
  • 輸入網址為 http://localhost:50554/api/JWTToken
  • 點選 [Header] 標籤頁次
  • 在 [Key] 欄位 輸入 Authorization
  • 在 [Value] 欄位 輸入 Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJ2dWxjYW4ubGVlQHZ1bGNhbi5uZXQiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmkiLCJleHAiOjE1MjA3NzkxNTAsImlzcyI6IkNvcmVKV1QudnVsY2FuLm5ldCIsImF1ZCI6IlhhbWFyaW4gUkVTVGZ1bCBBUEkifQ.1iOWpcqtKyuFZ94ShA1XT1wctAbVzVN6daemUqScStU
    其中,在 Bearer 之後的字串,是剛剛取得具有 7 天有效期限的 Access Token 存取權杖
  • 點選 [SEND] 按鈕
  • 我們將會得到底下的輸出結果,根據在做這個測試的說明,我們真的可以正常呼叫這個 Delete Web API,而且,可以得到回應結果內容。
    JWT Access Token PostMan Asp.net Core Web API

其他相關文章

更多關於 Xamarin / Xamarin.Forms 教學、技術分享、用法文章,請參考 I ♥ Xamarin

2018年4月15日 星期日

在 ASP.NET Core Web API 內,使用 SQLite 資料庫開發教學

在這篇文章中,我們將要來實際建立一個 ASP.NET Core 的 Web API 專案,並且在這個專案內,我們將會要使用 SQLite 資料庫 database之教學練習。在這個練習中,我使用的 Visual Studio 2017 為 15.6 的版本。

本篇文章的專案範例原始碼,可以從 https://github.com/vulcanlee/CSharpNotes2018/tree/master/XamarinCore 取得

建立一個 ASP.NET Core Web API 專案

  • 打開任何一種版本的 Visual Studio 2017
  • 從功能表中點選 [檔案] > [新增] > [專案]
  • 從 [新增專案] 對話窗中,點選 [已安裝] > [Visual C#] > [.NET Core] > [ASP.NET Core Web 應用程式]
  • Visual Studio 2017 新增專案
  • 在該對話窗下方的 [名稱] 欄位,輸入這個專案的名稱,最後點選 [確定] 按鈕
  • 此時,您將會看到 [新增 ASP.NET Core Web 應用程式對話窗],請確認 [.NET Core] 選擇的版本是現在最新的 [ASP.NET Core 2.0]
  • 專案類型,請選擇 [API]
  • 在最左方的 [用於建立 ASP.NET Core 應用程式的專案範本,附有 RESTful HTTP 服務的控制器範例,此範本也可用於 ASP.NET Core MVC 的檢視及控制器] 文字下方,請確認選擇 [變更驗證] 的下方為 [無驗證]
  • 完成之後,請點選 [確定] 按鈕
    新增 ASP.NET Core Web 應用程式對話窗
  • 稍微等候一段時間,這個ASP.NET Core Web 應用程式專案就會建立完成,您可以從 Visual Studo 2017 的方案總管中,看到建立好的專案內容,如下圖所示:
    ASP.NET Core Web 應用程式 專案內容

安裝 NuGet 套件

  • 使用滑鼠右擊 [相依性] 節點,選擇 [管理 NuGet 套件]
  • 在 [管理 NuGet 套件] 視窗中,點選 [瀏覽] 標籤頁次,接著在搜尋文字輸入盒中,輸入 [Microsoft.EntityFrameworkCore]
  • 當搜尋到 [Microsoft.EntityFrameworkCore] 之後,請安裝這個套件
    管理 NuGet 套件
  • 在 [管理 NuGet 套件] 視窗中,點選 [瀏覽] 標籤頁次,接著在搜尋文字輸入盒中,輸入 [Microsoft.EntityFrameworkCore.SQlite]
  • 當搜尋到 [Microsoft.EntityFrameworkCore.SQlite] 之後,請安裝這個套件
    管理 NuGet 套件

建立 Context 類別

  • 滑鼠右擊專案節點,選擇 [加入] > [新增資料夾],接著輸入 [Models]
  • 滑鼠右擊 [Models] 資料夾,選擇 [加入] > [類別]
  • 在 [新增項目] 對話窗中名稱欄位,輸入 [DatabaseContext]
    新增項目對話窗
  • 使用底下程式碼替換剛剛建立好的檔案內容,在這裡,我們剛剛建立的 DatabaseContext 類別,將會繼承 DbContext 類別,並且我們需要覆寫 OnConfiguring 方法,並且在這個方法類,指定使用 SQLite 資料庫的名稱,也就是使用這行敘述 optionsBuilder.UseSqlite("Filename=MyDatabase.db")
C Sharp / C#
using Microsoft.EntityFrameworkCore;

namespace XamarinCore.Models
{
    public class DatabaseContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("Filename=MyDatabase.db");
        }
    }
}

修正 Startup 類別

  • 請在 Visual Studio 2017 方案總管中,打開 [Startup.cs] 檔案
  • 找到 [ConfigureServices] 方法,依照底下程式碼進行修正
C Sharp / C#
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddEntityFrameworkSqlite().AddDbContext<DatabaseContext>();
}
  • 接下來,我們需要建立 Startup 的建構式,依照底下程式碼進行修正。在這裡,我們將會使用建構式注入 (Constructor Dependency Injection) 方法,注入 IHostingEnvironment 這個介面實作物件,並且在這裡設定要產生 SQLite 的資料庫檔案。
C Sharp / C#
public Startup(IHostingEnvironment env)
{
    using(var client = new DatabaseContext())
    {
        client.Database.EnsureCreated();
    }
}

建立資料模型與修正 DatabaseContext 類別

  • 滑鼠右擊 [Models] 資料夾,選擇 [加入] > [類別]
  • 在 [新增項目] 對話窗中名稱欄位,輸入 [Todo]
    新增項目對話窗
  • 使用底下程式碼來宣告 Todo 這個類別
C Sharp / C#
public class Todo
{
    public int Id { get; set; }

    public string Title { get; set; }
    public bool HasCompleted { get; set; }
}
  • 打開 [DatabaseContext.cs] 檔案,在 DatabaseContext 類別中,參考底下程式碼,加入 Todos 這個屬性的宣告
C Sharp / C#
using Microsoft.EntityFrameworkCore;

namespace XamarinCore.Models
{
    public class DatabaseContext : DbContext
    {
        public virtual DbSet<Todo> Todos { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("Filename=MyDatabase.db");
        }
    }
}

使用移轉來建立資料庫

  • 滑鼠右擊專案節點,選擇 [編輯 XamarinCore.csproj]
  • 找到 <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />宣告,在這行後面,加入 <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />,完成後的結果如下所示,最後,請按下儲存功能。
XML
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.1" />
  </ItemGroup>

  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
  </ItemGroup>

</Project>
  • 請 [建置] 這個專案
  • 滑鼠右擊專案節點,選擇 [在檔案總管中開啟資料夾]
    新增項目對話窗
  • 當檔案總管開啟之後,請在位置列中輸入 [cmd],通知作業系統開啟命令提示字元視窗
  • 請在命令提示字元視窗中,輸入 dotnet ef migrations add InitialCreate,並且按下 [Enter]。當您看到這段文字 Done. To undo this action, use 'ef migrations remove' 出現,那就表示這段命令已經成功執行。
Console
D:\Vulcan\Projects\XamarinCore\XamarinCore>dotnet ef migrations add InitialCreate
Done. To undo this action, use 'ef migrations remove'
  • 請在命令提示字元視窗中,輸入 dotnet ef database update,並且按下 [Enter]。當您看到這段文字 Applying migration '20180308090145_InitialCreate'. Done. 出現,那就表示這段命令已經成功執行。
  • 底下畫面截圖,就是剛剛在命令提示字元視窗中,所下達的兩個命令。
    使用移轉來建立資料庫
  • 現在,我們可以查看 Visual Studio 2017 的方案總管,您將會看到在這個專案節點內,多了一個 [Migrations] 資料夾,並且在該資料夾內也有兩個檔案存在;另外,我們也看到了 [MyDatabase.db] 這個 SQLite Database 資料庫檔案也產生出來了。
    SQLite資料庫已經建立完成

結論

好的,現在我們可以透過 Entity Framework Core 所提供的各項功能與機制,開始進行 SQLite 資料庫的資料表存取動作了。

其他相關文章

更多關於 Xamarin / Xamarin.Forms 教學、技術分享、用法文章,請參考 I ♥ Xamarin