ASP.NET Web API & JWT 使用者身分驗證練習
至於要如何在使用端 Client 來進行使用者身分驗證,並且取得 JWT Token 權杖,接著使用該權杖來呼叫其他 Web API 的方法,可以參考 使用 HttpClient 進行 JWT 身分驗證與呼叫需要授權的 API 和重新更新 Token 權杖的程式設計範例 ;在這篇文章中,也包含了如何在用戶端進行更新 JWT Token 的作法。
各位學員可以先依照這份文件中的說明步驟,逐一進行練習,了解如何設計出可以產生一個存取權杖的登入服務,並且使用該存取權杖,呼叫其他需要需要有通過使用者驗證才能夠使用的 Web API 服務。
我們會使用 PostMan 這個工具來協助我們進行相關 Web API 的測試工作。
這份文件將使用 Visual Studio 2017 工具來展示如何做到這樣的需求
01 建立 ASP.NET Web API 專案
我們首先需要建立一個 ASP.NET Web API (.NET Framework) 專案,接著,從無到有的接著設計出具有存取權杖的身分驗證才能夠存取的 Web API 服務。
- 開啟 Visual Studio 2017 IDE
- 從功能表點選 [檔案] > [新增] > [專案]
- 在 [新增專案] 對話窗左半部,點選 [已安裝] > [Visual C#] > [Web]
- 在這個對話的中間上方,選擇 [.NET Framework 4.6.1]
- 接著選取 [ASP.NET Web 應用程式 (.NET Framework)]
- 在下方 [名稱] 欄位,輸入
AuthToken
- 在 [方案名稱] 欄位,輸入
AuthTokenSolution
- 最後,點選右下方的 [確定] 按鈕
- 當出現 [新增 ASP.NET Web 應用程式 - AuthToken] 對話窗
- 請選擇 [Web API]
- 右半部的 [驗證] 請設定為
無驗證
- 其他設定可以參考這個螢幕截圖
- 執行這個 Web API 專案,就會看到這個畫面,那就表示您的 Web API 專案已經建立完成
這個練習的完成專案,可以參考資料夾 [01 CreatProject]
02 ASP.NET Web API 專案之準備工作
在這裡,我們需要安裝 NuGet 套件與建立會用到的回傳資料模型和基本的登入與兩個 Web API服務。
安裝 JWT.NET 套件
- 滑鼠右擊 [參考] 專案節點,選擇 [管理 NuGet 套件]
- 在 [NuGet: AuthToken] 視窗中,點選 [瀏覽] 標籤頁次
- 在 [搜尋] 文字輸入盒內,輸入
JWT.NET
,搜尋這個套件 - 勾選 [包含搶鮮版] 檢查盒,安裝 4.0 以上的版本
建立 Web API 回傳資料模型
- 滑鼠右擊 [Models] 資料夾,選取 [加入] > [類別]
- 在 [新增項目] 對話窗中,在下方的 [名稱] 欄位中,輸入
APIResult
- 點選 [新增] 按鈕,完成新增這個類別檔案
- 將這個新產生的類別
APIResult
以底下程式碼替換
/// <summary>
/// 呼叫 API 回傳的制式格式
/// </summary>
public class APIResult
{
/// <summary>
/// 此次呼叫 API 是否成功
/// </summary>
public bool Success { get; set; } = true;
/// <summary>
/// 呼叫 API 失敗的錯誤訊息
/// </summary>
public string Message { get; set; } = "";
/// <summary>
/// 呼叫此API所得到的其他內容
/// </summary>
public object Payload { get; set; }
}
建立全域常數類別
- 滑鼠右擊專案節點 [AuthToken],選擇 [新增資料夾]
- 在新增的資料夾位置,輸入
Helpers
資料夾 - 滑鼠右擊 [Helpers] 資料夾,選取 [加入] > [類別]
- 在 [新增項目] 對話窗中,在下方的 [名稱] 欄位中,輸入
MainHelper
- 點選 [新增] 按鈕,完成新增這個類別檔案
- 將這個新產生的類別
MainHelper
以底下程式碼替換
public class MainHelper
{
// 這裡是要進行雜湊簽章會用到的金鑰
public const string SecretKey = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
}
建立登入驗證 Controllers
- 滑鼠右擊 [Controllers] 資料夾,選取 [加入] > [控制器]
- 在 [新增 Scaffold] 對話窗中,選擇 [Web API2 控制器 - 空白]
- 點選右下方的 [新增] 按鈕
- 當出現 [加入控制器] 對話窗後,於 [控制器名稱] 欄位,輸入
Login
,使得我們要產生的控制器名稱為LoginController
- 點選右下方的 [加入] 按鈕
- 將這個新產生的控制器
LoginController
以底下程式碼替換這裡尚未做任何使用者帳號與密碼的身分驗證程式碼
using AuthToken.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace AuthToken.Controllers
{
public class LoginController : ApiController
{
[HttpGet]
public APIResult Get()
{
return new APIResult()
{
Success = true,
Message = "",
Payload = "Access Token"
};
}
}
}
建立無須驗證便可存取的 Controllers
- 滑鼠右擊 [Controllers] 資料夾,選取 [加入] > [控制器]
- 在 [新增 Scaffold] 對話窗中,選擇 [Web API2 控制器 - 空白]
- 點選右下方的 [新增] 按鈕
- 當出現 [加入控制器] 對話窗後,於 [控制器名稱] 欄位,輸入
NoAuth
,使得我們要產生的控制器名稱為NoAuthController
- 點選右下方的 [加入] 按鈕
- 將這個新產生的控制器
NoAuthController
以底下程式碼替換
using AuthToken.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace AuthToken.Controllers
{
public class NoAuthController : ApiController
{
[HttpGet]
public APIResult Get()
{
return new APIResult()
{
Success = true,
Message = "不需要提供存取權杖 Access Token 就能使用的 API",
Payload = new string[] { "無須存取權杖1", "無須存取權杖2" }
};
}
}
}
建立需要通過身分驗證便可存取的 Controllers
- 滑鼠右擊 [Controllers] 資料夾,選取 [加入] > [控制器]
- 在 [新增 Scaffold] 對話窗中,選擇 [Web API2 控制器 - 空白]
- 點選右下方的 [新增] 按鈕
- 當出現 [加入控制器] 對話窗後,於 [控制器名稱] 欄位,輸入
NeedAuth
,使得我們要產生的控制器名稱為NeedAuthController
- 點選右下方的 [加入] 按鈕
- 將這個新產生的控制器
NeedAuthController
以底下程式碼替換這裡尚未進行存取權杖的驗證與取回權杖內的資料程式碼
using AuthToken.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace AuthToken.Controllers
{
public class NeedAuthController : ApiController
{
[HttpGet]
public APIResult Get()
{
return new APIResult()
{
Success = true,
Message = "需要提供存取權杖 Access Token 才能使用的 API",
Payload = new string[] { "有提供存取權杖1", "有提供存取權杖2" }
};
}
}
}
進行驗證專案可以正常運作
- 請建置該專案,確認沒有錯誤產生
- 執行該專案您專案的開啟網站的埠 (Port) 可能會與這個專案不同,請自行修改 Port 號碼成為您專案用的 Port 號碼
- 開啟
PostMan
工具,選擇 Http 方法為GET
- 在 URL 位址輸入
http://localhost:54891/api/Login
- 確認得到底下結果
{
"Success": true,
"Message": "",
"Payload": "Access Token"
}
- 在 URL 位址輸入
http://localhost:54891/api/NoAuth
- 確認得到底下結果
{
"Success": true,
"Message": "不需要提供存取權杖 Access Token 就能使用的 API",
"Payload": [
"無須存取權杖1",
"無須存取權杖2"
]
}
- 在 URL 位址輸入
http://localhost:54891/api/needAuth
- 確認得到底下結果
{
"Success": true,
"Message": "需要提供存取權杖 Access Token 才能使用的 API",
"Payload": [
"有提供存取權杖1",
"有提供存取權杖2"
]
}
這個練習的完成專案,可以參考資料夾 [02 Setup]
03 設計HTTP基本認證,並回傳有效存取權杖
在這裡,我們將會繼續剛剛完成的專案,設計 LoginController 可以進行
HTTP基本認證
,相關 HTTP基本認證 說明,可以參考該連結- 在 LoginController.cs 檔案中,加入底下命名空間參考
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Text;
- 將
LoginController
類別,修正成為底下程式碼
public class LoginController : ApiController
{
HttpResponseMessage response;
public string Account { get; set; }
public string Password { get; set; }
[HttpGet]
public HttpResponseMessage Get()
{
if (CanHandleAuthentication(this.Request) == true)
{
#region 檢查帳號與密碼是否正確
// 這裡可以修改成為與後端資料庫內的使用者資料表進行比對
var expectAccount = "vulcan";
var expectPassword = "123abc";
if (expectAccount == Account &&
expectPassword == Password)
{
// 帳號與密碼比對正確,回傳帳密比對正確
response = this.Request.CreateResponse<APIResult>(HttpStatusCode.OK, new APIResult()
{
Success = true,
Message = $"帳號:{Account} / 密碼:{Password}",
Payload = "Access Token"
});
}
else
{
// 帳號與密碼比對不正確,回傳帳密比對不正確
response = this.Request.CreateResponse<APIResult>(HttpStatusCode.Unauthorized, new APIResult()
{
Success = false,
Message = $"",
Payload = "帳號或密碼不正確"
});
}
#endregion
return response;
}
else
{
// 沒有收到正確格式的 Authorization 內容,回傳無法驗證訊息
response = this.Request.CreateResponse<APIResult>(HttpStatusCode.Unauthorized, new APIResult()
{
Success = false,
Message = $"",
Payload = "沒有收到帳號與密碼"
});
return response;
}
}
/// <summary>
/// 檢查與解析 Authorization 標頭是否存在與解析用戶端傳送過來的帳號與密碼
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public bool CanHandleAuthentication(HttpRequestMessage request)
{
// 驗證結果是否正確
bool isSuccess = false;
#region 檢查是否有使用 Authorization: Basic 傳送帳號與密碼到 Web API 伺服器
if ((request.Headers != null
&& request.Headers.Authorization != null
&& request.Headers.Authorization.Scheme.ToLowerInvariant() == "basic"))
{
#region 取出帳號與密碼,帳號與密碼格式為 帳號:密碼
var authHeader = request.Headers.Authorization;
// 取出有 Base64 編碼的帳號與密碼
var encodedCredentials = authHeader.Parameter;
// 進行 Base64 解碼
var credentialBytes = Convert.FromBase64String(encodedCredentials);
// 取得 .NET 字串
var credentials = Encoding.ASCII.GetString(credentialBytes);
// 判斷格式是否正確
var credentialParts = credentials.Split(':');
if (credentialParts.Length == 2)
{
// 取出使用者傳送過來的帳號與密碼
Account = credentialParts[0];
Password = credentialParts[1];
isSuccess = true;
}
#endregion
}
#endregion
return isSuccess;
}
}
使用 PostMan 來測試登入 API 是否可以正常運作
- 打開 PostMan 工具
Authorization: Basic dnVsY2FuOjEyM2FiYw==
- 設定 PostMand 的 Http 方法為
GET
,增加一個Authorization
標頭,其值為Basic dnVsY2FuOjEyM2FiYw==
,如下圖所示,並請點選右上方的 [Send] 按鈕,將會得到底下結果:
{
"Success": true,
"Message": "帳號:vulcan / 密碼:123abc",
"Payload": "Access Token"
}
- 產生出不正確的帳號或密碼的標頭,底下的標頭之帳號為
vulcan
/ 密碼為123
,如下所示,進行測試
Authorization: Basic dnVsY2FuOjEyMw==
- 設定 PostMand 的 Http 方法為
GET
,增加一個Authorization
標頭,其值為Basic dnVsY2FuOjEyMw==
,如下圖所示,並請點選右上方的 [Send] 按鈕,將會得到底下結果:
{
"Success": false,
"Message": "",
"Payload": "帳號或密碼不正確"
}
- 接著,不要設定 Authorization 標頭,進行測試,將會得到底下結果:
{
"Success": false,
"Message": "",
"Payload": "沒有收到帳號與密碼"
}
產生使用者的存取權杖 Access Token
- 在 LoginController.cs 檔案中,加入底下命名空間參考
using AuthToken.Helpers;
using JWT;
using JWT.Algorithms;
using JWT.Builder;
- 找到這行註解
// 帳號與密碼比對正確,回傳帳密比對正確
,在這行註解的上方,加入底下程式碼
#region 產生這次通過身分驗證的存取權杖 Access Token
string secretKey = MainHelper.SecretKey;
#region 設定該存取權杖的有效期限
IDateTimeProvider provider = new UtcDateTimeProvider();
// 這個 Access Token只有一個小時有效
var now = provider.GetNow().AddHours(1);
var unixEpoch = UnixEpoch.Value; // 1970-01-01 00:00:00 UTC
var secondsSinceEpoch = Math.Round((now - unixEpoch).TotalSeconds);
#endregion
var jwtToken = new JwtBuilder()
.WithAlgorithm(new HMACSHA256Algorithm())
.WithSecret(secretKey)
.AddClaim("iss", Account)
.AddClaim("exp", secondsSinceEpoch)
.AddClaim("role", new string[] { "Manager", "People" })
.Build();
#endregion
- 找到這行註解
// 帳號與密碼比對正確,回傳帳密比對正確
,在這行註解的下方,將這行程式碼this.Request.CreateResponse
,修改成為底下程式碼,這樣,當使用者傳入一個合法的帳號與密碼,就可以回傳這次身分驗證的存取權杖
response = this.Request.CreateResponse<APIResult>(HttpStatusCode.OK, new APIResult()
{
Success = true,
Message = $"帳號:{Account} / 密碼:{Password}",
Payload = $"{jwtToken}"
});
使用 PostMan 來測試登入 API 是否可以回傳存取權杖 Access Token
- 打開 PostMan 工具
Authorization: Basic dnVsY2FuOjEyM2FiYw==
- 設定 PostMand 的 Http 方法為
GET
,增加一個Authorization
標頭,其值為Basic dnVsY2FuOjEyM2FiYw==
,如下圖所示,並請點選右上方的 [Send] 按鈕,將會得到底下結果:
{
"Success": true,
"Message": "帳號:vulcan / 密碼:123abc",
"Payload": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2dWxjYW4iLCJleHAiOjE1MTY5NTg4NzUuMCwicm9sZSI6WyJNYW5hZ2VyIiwiUGVvcGxlIl19.cHAktZhijjdbbP5mhb1ICwGdN1GwUrfAyPObq3ZMi-4"
}
其中,這個字串
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2dWxjYW4iLCJleHAiOjE1MTY5NTg4NzUuMCwicm9sZSI6WyJNYW5hZ2VyIiwiUGVvcGxlIl19.cHAktZhijjdbbP5mhb1ICwGdN1GwUrfAyPObq3ZMi-4
就是存取權杖,日後要存取需要通過身分驗證的 API 的時候,只需要提供這個存取權杖,後端 Web API就會知道,這次呼叫 Web API是由哪個使用者來呼叫的。
這個練習的完成專案,可以參考資料夾 [03 BasicAuth]
04 設計 Web API 需要有存取權杖,才能夠存取這些API服務
首先,我們來練習,如何在用戶端的專案內,使用
HttpClient
抓取到存取權杖,接著使用不同情境的存取權杖,學習如何將存取權杖傳送到後端 Web API 專案上,接著,驗證與分析這個存取權證是否正確,並回傳 Web API 的執行結果。如何撰寫用戶端的程式碼,取得存取權杖
- 滑鼠右擊方案 [AuthTokenSolution],選擇 [加入] > [新增專案]
- 在 [新增專案] 對話窗左半部,點選 [已安裝] > [Visual C#]
- 在這個對話的中間上方,選擇 [.NET Framework 4.6.1]
- 接著選取 [主控台應用程式 (.NET Framework)]
- 在下方 [名稱] 欄位,輸入
GetAccessToken
- 最後,點選右下方的 [確定] 按鈕
- 滑鼠右擊主控台應用程式
GetAccessToken
的參考節點 - 選擇 [管理 NuGet 套件]
- 在 [NuGet: GetAccessToken] 視窗中,點選 [瀏覽] 標籤頁次
- 在 [搜尋] 文字輸入盒內,輸入
JSON.NET
,搜尋這個套件 - 取消勾選 [包含搶鮮版] 檢查盒,選擇第一個套件
Newtonsoft.Json
- 點選 [安裝] 按鈕
- 將
GetAccessToken
專案內的Program.cs
檔案內容,使用底下程式碼替換
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace GetAccessToken
{
class Program
{
static void Main(string[] args)
{
var TARGETURL = "http://localhost:54891/api/Login";
using (HttpClientHandler handler = new HttpClientHandler())
{
using (HttpClient client = new HttpClient(handler))
{
var byteArray = Encoding.ASCII.GetBytes("vulcan:123abc");
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Basic",
Convert.ToBase64String(byteArray));
HttpResponseMessage response = client.GetAsync(TARGETURL).Result;
HttpContent content = response.Content;
Console.WriteLine("Response StatusCode: " + (int)response.StatusCode);
string result = content.ReadAsStringAsync().Result;
var fooAPIResult = Newtonsoft.Json.JsonConvert.DeserializeObject<APIResult>(result);
Console.WriteLine($"存取權杖為 : {fooAPIResult.Payload}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
}
};
}
}
/// <summary>
/// 呼叫 API 回傳的制式格式
/// </summary>
public class APIResult
{
/// <summary>
/// 此次呼叫 API 是否成功
/// </summary>
public bool Success { get; set; } = true;
/// <summary>
/// 呼叫 API 失敗的錯誤訊息
/// </summary>
public string Message { get; set; } = "";
/// <summary>
/// 呼叫此API所得到的其他內容
/// </summary>
public object Payload { get; set; }
}
}
- 此時,您的方案內共有兩個專案,預設起始專案應該為
AuthToken
- 按下 [F5] 按鍵,開始進行 Web API 專案進行除錯
- 滑鼠右擊專案
GetAccessToken
,點選 [偵錯] > [開始新執行個體] - 此時,會顯示一個命令提示字元視窗,如下圖,您會看到,我們已經把存取權杖顯示在螢幕上。
- 請把這個存取權杖複製到剪貼簿內。
- 開啟 jwt.io 網頁
- 在
Encoded
區域,將這個存取權杖貼到這裡,此時,您會看到右半部將會出現解碼後的這個存取權杖詳細內容。
建立檢查是否存在合法的存取權杖
- 滑鼠右擊專案 [AuthToken],選擇 [加入] > [新增資料夾]
- 在新增的資料夾位置,輸入
Filters
資料夾 - 滑鼠右擊 [Filters] 資料夾,選取 [加入] > [類別]
- 在 [新增項目] 對話窗中,在下方的 [名稱] 欄位中,輸入
JwtAuthAttribute
- 點選 [新增] 按鈕,完成新增這個類別檔案
- 將這個新產生的類別
JwtAuthAttribute
以底下程式碼替換
using AuthToken.Helpers;
using AuthToken.Models;
using JWT;
using JWT.Algorithms;
using JWT.Builder;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Security.Principal;
using System.Threading;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace AuthToken.Filters
{
public class JwtAuthAttribute : AuthorizeAttribute
{
public string ErrorMessage { get; set; } = "";
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
if (string.IsNullOrEmpty(ErrorMessage) == false)
{
setErrorResponse(actionContext, ErrorMessage);
}
else
{
base.HandleUnauthorizedRequest(actionContext);
}
}
public override void OnAuthorization(HttpActionContext actionContext)
{
// TODO: key應該移至config
if (actionContext.Request.Headers.Authorization == null || actionContext.Request.Headers.Authorization.Scheme != "Bearer")
{
setErrorResponse(actionContext, "沒有看到存取權杖錯誤");
}
else
{
try
{
#region 進行存取權杖的解碼
string secretKey = MainHelper.SecretKey;
var json = new JwtBuilder()
.WithAlgorithm(new HMACSHA256Algorithm())
.WithSecret(secretKey)
.MustVerifySignature()
.Decode<Dictionary<string, object>>(actionContext.Request.Headers.Authorization.Parameter);
#endregion
#region 將存取權杖所夾帶的內容取出來
var fooRole = json["role"] as Newtonsoft.Json.Linq.JArray;
var fooRoleList = fooRole.Select(x => (string)x).ToList<string>();
#endregion
#region 將存取權杖的夾帶欄位,儲存到 HTTP 要求的屬性
actionContext.Request.Properties.Add("user", json["iss"] as string);
actionContext.Request.Properties.Add("role", fooRoleList);
#endregion
#region 設定目前 HTTP 要求的安全性資訊
var fooPrincipal =
new GenericPrincipal(new GenericIdentity(json["iss"] as string, "MyPassport"), fooRoleList.ToArray());
if (HttpContext.Current != null)
{
HttpContext.Current.User = fooPrincipal;
}
#endregion
#region 角色權限檢查(檢查控制器或動作之屬性(Attribute上設的 Roles的設定內容)
if (string.IsNullOrEmpty(Roles) == false)
{
// 是否有找到匹配的角色設定
bool fooCheckRoleResult = false;
// 切割成為多個角色成員
var fooConditionRoles = Roles.Split(',');
// 逐一檢查,這個使用用者是否有在這個角色條件中
foreach (var item in fooConditionRoles)
{
var fooInRole = fooPrincipal.IsInRole(item.Trim());
if (fooInRole == true)
{
fooCheckRoleResult = true;
break;
}
}
if(fooCheckRoleResult == false)
{
setErrorResponse(actionContext, "無效的角色設定,沒有權限使用這個 API");
}
}
#endregion
}
catch (TokenExpiredException)
{
setErrorResponse(actionContext, "權杖已經逾期");
}
catch (SignatureVerificationException)
{
setErrorResponse(actionContext, "權杖似乎不正確,沒有正確的數位簽名");
}
catch (Exception ex)
{
setErrorResponse(actionContext, $"權杖解析發生異常 : {ex.Message}");
}
}
base.OnAuthorization(actionContext);
}
private void setErrorResponse(HttpActionContext actionContext, string message)
{
ErrorMessage = message;
var response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, message);
response.Content = new ObjectContent<APIResult>(new APIResult()
{
Success = false,
Message = ErrorMessage,
Payload = null
}, new JsonMediaTypeFormatter());
actionContext.Response = response;
}
}
}
- 現在,只要把這個
JwtAuthAttribute
屬性,套用到相關的控制器 (Controller) 或者動作 (Action) 方法上,因此,當要存取這些控制器或者動作的時候,就必須要提供有效存取權杖,方能呼叫這些 Web API,否則,會有錯誤回報。現在,我們打開這個NeedAuthController.cs
檔案,將類別NeedAuthController
替換成為底下程式碼。
[JwtAuth]
public class NeedAuthController : ApiController
{
[HttpGet]
public APIResult Get()
{
var fooUser = Request.Properties["user"] as string;
return new APIResult()
{
Success = true,
Message = $"授權使用者為 {fooUser}",
Payload = new string[] { "有提供存取權杖1", "有提供存取權杖2" }
};
}
}
- 現在,執行這個 Web API 專案,並且使用 PostMan 來存取這個網址 http://localhost:54891/api/needAuth,但是,看到底下的回傳內容,這表示,當要存取這個
NeedAuth
Web API 的時候,我們需要提供有效的存取權杖。
{
"Success": false,
"Message": "沒有看到存取權杖錯誤",
"Payload": null
}
- 讓我們重新執行這個網址 http://localhost:54891/api/Login,得到最新的存取權杖
{
"Success": true,
"Message": "帳號:vulcan / 密碼:123abc",
"Payload": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2dWxjYW4iLCJleHAiOjE1MTY5Nzk5MzUuMCwicm9sZSI6WyJNYW5hZ2VyIiwiUGVvcGxlIl19.4iGEoBA0SoCiSTBXdAn0J3J_hvVNS1ZeZIP6OeJAPm8"
}
- 我們在 PostMan 上,找到這個 API http://localhost:54891/api/needAuth ,在這個API頁面,加入
Authorization
標頭,該標頭的值要為Bearer Token
,在我們這個例子中就是如下的標頭定義;當設定完成之後,按下 [Send] 按鈕,就發現到我們現在可以呼叫這個 Web API了。
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2dWxjYW4iLCJleHAiOjE1MTY5Nzk5MzUuMCwicm9sZSI6WyJNYW5hZ2VyIiwiUGVvcGxlIl19.4iGEoBA0SoCiSTBXdAn0J3J_hvVNS1ZeZIP6OeJAPm8
- 但是,若我們竄改了這個存取權杖,或者權杖的內容被刪去了幾個字元,在這裡,我們把剛剛的權杖刪除最後兩個字元,因此,底下的標題將是我們要送出的內容
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2dWxjYW4iLCJleHAiOjE1MTY5Nzk5MzUuMCwicm9sZSI6WyJNYW5hZ2VyIiwiUGVvcGxlIl19.4iGEoBA0SoCiSTBXdAn0J3J_hvVNS1ZeZIP6OeJAP
- 此時,您會得到底下的結果,告訴您的權杖是有問題,並且無法使用這個 Web API
{
"Success": false,
"Message": "權杖解析發生異常 : Illegal base64url string.",
"Payload": null
}
- 由於,發出的存取權杖有效期限為一個小時,若你使用一個小時前發出的權杖,來呼叫這個 Web API,例如,使用底下的標頭。
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2dWxjYW4iLCJleHAiOjE1MTY5Njk0MTUuMCwicm9sZSI6WyJNYW5hZ2VyIiwiUGVvcGxlIl19.8KgvjlRONFIpSnIYD2iTjpY4IWH9vn_ntvI_LwhvBHQ
- 這個時候,就會得到底下的
權杖已經逾期
的訊息。
{
"Success": false,
"Message": "權杖已經逾期",
"Payload": null
}
這個練習的完成專案,可以參考資料夾 [04 CallWithToken]
對於已經具備擁有 .NET / C# 開發技能的開發者,可以使用 Xamarin.Forms Toolkit 開發工具,便可以立即開發出可以在 Android / iOS 平台上執行的 App;對於要學習如何使用 Xamarin.Forms & XAML 技能,現在已經推出兩本電子書來幫助大家學這這個開發技術。
這兩本電子書內包含了豐富的逐步開發教學內容與相關觀念、各種練習範例,歡迎各位購買。
想要購買
Xamarin.Forms 快速上手
電子書,請點選 這裡
想要購買
XAML in Xamarin.Forms 基礎篇
電子書,請點選 這裡
您好
回覆刪除非常感謝您提供這份文件造福人群
但是我有個疑問
如果我想要銷毀權杖該如何做
會這麼問是因為我想要加上登出的功能
謝謝您~~
權杖一旦發出,是無法回收銷毀的,通常的做法都是發出的權杖,但是具有短時間有效性,一旦過時之後,就需要重新更新權杖,至於過時的權杖,也不用回收或者銷毀了,因為這個權杖已經沒有用處了。
刪除作者已經移除這則留言。
回覆刪除非常感謝您用心提供這麼詳細的步驟,對於初次接觸Auth這塊的人可以快速找到該有的方向。這篇文章幫了我一個大忙,再次感謝。
回覆刪除感謝您的鼓勵
刪除hi, 感謝提供詳細jwt auth實作說明
回覆刪除另外想請問,若以呼叫端看待JWT Token時,可能會面對兩個問題,不知道您是否有建議的解法:
1. Token過期更新問題:是否每次call api前都自我檢查是否快或已過期? 來決定重新Sign一次Token, 還是有更好的做法?
2. Client端Token保存方式:Client端取得合法Token後,該存在cookie中嗎? 還是放在memory cache? (如果有reuse情況)
上述問題,如果用每次call api都取得一個10-30秒的token,好像都可以解,但實務上會這樣做嗎? >< 謝謝
1. 一旦 Token 過期,在呼叫 api 的時候,就會驗證失敗,因此,需要重新驗證與取得新的 Token,當然,最好的做法那就是透過一個 Refresh Token 來取得新的 Access Token;若不幸 Refresh Token 也過期,那就只有重新登入驗證了。
刪除2. 保存方式都可以喔,也可以使用 Local Storage
3. Token 有效期限僅有 10-30 秒,似乎有些短,通常都是建議發出 Token 的時候,有效期限可以有 15分鐘~2個小時之內,而 Refresh Token 可以更久,如長達 1個星期以上;實際的時間,需要看當時的專案需求而定,時間長短,各有其優缺點。
謝謝您
刪除不好意思,想再加問一個問題:
假設是前後端分離狀況,例如Blazor架構,我故意把CRUD的部分另外做成Web Api
在這個情況下,我該如何設計對Web Api的授權? (不想被任意使用者呼叫)
目前想到的方法有:
A. 建立一個固定的Api Key,前端在呼叫Web Api時,固定在Header中傳入這個Api Key
B. User在登入網站時,順便向後台Web Api取得Token(例如JWT),如果Token過期後,網站視為登出,請User再登入一次
目前想到的是如果是企業內部,相對安全性較高時,選擇A應該比較省事
但如果API是來自外部時,A方案好像會有安全問題.
不知道這樣認知是否正確? 謝謝
若使用 Server Side 的模式, Blazor 專案可以直接呼叫伺服器上的 服務 Service 即可,無需使用 Web API
刪除若想使用 WebAssembly 的 模式,對於 Blazo 專案,同樣可以設計 Web API 服務,並且需要有 JWT 權杖的授權語認證機制即可,至於 Access Token,可以依照需求儲存取來