Kerry 的筆記本
  • Table of contents
  • Kerry的Mac裝機必要
  • ASP.NET Core 教育訓練文件
    • .NET 9 OpenAPI 介紹與教學
    • 目錄
    • ASP.NET Core Authentication系列(一)理解Claim, ClaimsIdentity, ClaimsPrincipal
    • ASP.NET Core Authentication系列(三)Cookie選項
    • ASP.NET Core Authentication系列(二)實現認證、登錄和註銷
    • ASP.NET Core Authentication系列(四)基於Cookie實現多應用間單點登錄(SSO)
    • ASP.NET Core Consul 教學
    • ASP.NET Core Hangfire 排程管理
    • ASP.NET Core KeyCloak 實作
    • ASP.NET Core NLog-依照Environment使用Nlog.Config檔案
    • ASP.NET Core NLog-如何使用 NLog 將 log 寫到檔案
    • ASP.NET Core Nlog-發送訊息到ElasticSearch
    • 目錄
    • ASP.NET Core Quartz.NET 管理介面
    • ASP.NET Core RDLC 報表設計
    • ASP.NET Core SFTP (使用第三方套建 SSH.Net) - 類別庫為案例
    • ASP.NET Core 中使用 HttpReports 進行接口統計,分析, 可視化, 監控,追踪等
    • ASP.NET 使用 MassTransit 與 RabbitMQ,實現事件發佈、訂閱
    • Asp.Net Core 分散式Session – 使用 Redis
    • ASP.NET Core 前台會員修改個人資料
    • ASP.NET Core 前台會員忘記密碼與重設密碼
    • ASP.NET Core 前台會員登入
    • ASP.NET Core 前台會員註冊
    • ASP.NET Core 呼叫 API 發生 CORS 錯誤
    • ASP.NET Core 如何套網頁設計樣版
    • ASP.NET Core 客製化Model Validation 預設錯誤訊息
    • ASP.NET Core 後台查詢頁面教學
    • ASP.NET Core 網站生命週期
    • ASP.NET Feature Management 使用說明與教學
    • ASP.NET RulesEngine 介紹
    • ASP.NET WinForms APP 程式安裝檔
    • LinePay 支付完成後返回 LINE 應用而不跳出外部瀏覽器
    • EntityFramework
      • EF Core Migrations 完整教學手冊
      • EntityFramework Core DB Migrations
      • 使用 Entity Framework Core (EF Core) 的 Migrations 功能進行版本控制
    • NET 6
      • .NET 6 Autofac範例
      • .NET 6 Automapper範例
      • .NET 6 BenchmarkDotNet範例
      • .NET 6 Bogus範例
      • .NET 6 Dapper範例
      • .NET 6 Dapper語法說明
      • .NET 6 EFCore範例
      • .NET 6 EFCore語法說明
      • .NET 6 EPPlus圖表範例
      • .NET 6 EPPlus範例
      • .NET 6 Hangfire範例
      • .NET 6 HttpClient單元測試範例
      • .NET 6 MailKit前置作業
      • .NET 6 MailKit範例
      • .NET 6 Moq範例
      • .NET 6 NLog範例
      • .NET 6 NLog進階範例
      • .NET 6 Serilog範例
      • .NET 6 Serilog進階範例
      • .NET 6 Telegram.Bot前置作業
      • .NET 6 Telegram.Bot範例
      • .NET 6 Text.Json範例
      • .NET 6 swagger授權
      • .NET 6 swagger範例
      • .NET 6 xUnit範例
      • .NET 6 取得appsettings檔案內容
      • .NET 6 更改回傳Json時為大駝峰命名
      • .NET 6 解決System.Text.Json序列化後會將所有非ASCII轉為Unicode
    • WDMIS
      • CORS
      • FeatureManagement
      • Serilog
      • Spectre.Console
      • 資料模型實戰:從 MSSQL 設計到 .NET 8 WebAPI 實作(以刀具管理為例)
  • Azure
    • 如何在 ASP.NET CORE 5.0 WEB 應用程序中實現 AZURE AD 身份驗證
    • Azure App Configuration 使用教學
    • Azure Blob Storage
    • Azure DevOps 持續整合(CI) + Artifacts
  • CSharp
    • ASP.NET await 與 wait 的差異
    • AutoMapper —— 類別轉換超省力
    • C# 中的 HTTPClient — 入門指南
    • C# 正則表達式:從零到英雄指南
    • C# 集合, List<> 取交集、差集、聯集的方法
    • C#單元測試教學
    • CORS 介紹與設定方式
    • CSharp Coding Conventions
    • Using jQuery Unobtrusive AJAX in ASP.NET Core Razor Pages
    • 深入Dapper.NET源碼
    • 菜雞與物件導向
      • 菜雞與物件導向 (0): 前言
      • 菜雞與物件導向 (1): 類別、物件
      • 菜雞與物件導向 (10): 單一職責原則
      • 菜雞與物件導向 (11): 開放封閉原則
      • 菜雞與物件導向 (12): 里氏替換原則
      • 菜雞與物件導向 (13): 介面隔離原則
      • 菜雞與物件導向 (14): 依賴反轉原則
      • 菜雞與物件導向 (15): 最少知識原則
      • 菜雞與物件導向 (2): 建構式、多載
      • 菜雞與物件導向 (3): 封裝
      • 菜雞與物件導向 (4): 繼承
      • 菜雞與物件導向 (5): 多型
      • 菜雞與物件導向 (6): 抽象、覆寫
      • 菜雞與物件導向 (7): 介面
      • 菜雞與物件導向 (8): 內聚、耦合
      • 菜雞與物件導向 (9): SOLID
      • 菜雞與物件導向 (Ex1): 小結
  • DBeaver
    • 如何強制讓 DBeaver 在 Mac 上使用英文介面
  • DesignPattern
    • OAuth
    • Repository 模式 (Repository Pattern)
    • Single Sign On 實作方式介紹 (CAS)
    • 【SOP製作教學】新手適用,SOP範例、流程圖、製作流程全公開!
    • 【SOP製作教學】流程圖教學、重點範例、BPMN符號介紹!
    • 【SOP製作教學】流程圖符號整理、BPMN2.0進階符號教學!
    • 多奇數位 C# 程式碼撰寫規範 (C# Coding Guideline)
    • 軟體分層設計模式 (Software Layered Architecture Pattern)
    • 開源程式碼檢測平台 SonarQube
    • 菜雞新訓記
      • 菜雞新訓記 (0): 前言
      • 菜雞新訓記 (1): 使用 Git 來進行版本控制吧
      • 菜雞新訓記 (2): 認識 Api & 使用 .net Core 來建立簡單的 Web Api 服務吧
      • 菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧
      • 菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧
      • 菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧
      • 菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧
      • 菜雞新訓記 (7): 使用 Fluent Validation 來驗證參數吧
  • DevOps
    • Repository 模式 (Repository Pattern)
    • pipeline工具研究
    • 單例模式 (Singleton Pattern)
    • 單元測試
    • 軟體分層設計模式 (Software Layered Architecture Pattern)
    • 雙重檢查鎖定模式 (Double-Checked Locking Pattern)
  • Docker
    • Docker 中部署 .NET 8 Web App 並支援 HTTPS
    • Docker指令大全
    • 第七章 安裝Nomad
    • Docker - 第三章 | 安裝 MSSQL
    • Docker - 第九章 | 安裝 datalust seq
    • 第二章 docker-compose 教學
    • Docker - 第五章 | 安裝 Redis
    • 第八章 安裝SonarQube
    • Docker - 第六章 | 安裝RabbitMQ
    • 第十一章 安裝 VtigerCRM
    • 第十二章 安裝KeyCloak
    • Docker - 第十章 | 安裝 Redmine
    • 第四章 安裝MySQL
    • Docker Desktop (含更改 Docker Image 路徑)
  • Git
    • Git Flow 指令大全(完整指令整理) 🚀
    • Git 安裝及配置SSH Key
    • Git 建立到上傳
    • 將現有專案的遠端儲存庫直接更改為新的儲存庫
    • Git 流程規劃
    • Git 語法大全
    • 30 天精通 Git 版本控管
      • 30 天精通 Git 版本控制
        • 第 01 天:认识 Git 版本控制
        • 第 02 天:在 Windows 平台必装的三套 Git 工具
        • 第 03 天:建立仓库
        • 第 04 天:常用的 Git 版本控制指令
        • 第 05 天:了解仓库、工作目录、物件与索引之间的关系
        • 第 06 天:解析 Git 资料结构 - 物件结构
        • 第 07 天:解析 Git 资料结构 - 索引结构
        • 第 08 天:关于分支的基本观念与使用方式
        • 第 09 天:比对文件与版本差异
        • 第 10 天:认识 Git 物件的绝对名称
        • 第 11 天:认识 Git 物件的一般参照与符号参照
        • 第 12 天:认识 Git 物件的相对名称
        • 第 13 天:暂存工作目录与索引的变更状态
        • 第 14 天: Git for Windows 选项设定
        • 第 15 天:标签 - 标记版本控制过程中的重要事件
        • 第 16 天:善用版本日志 git reflog 追踪变更轨迹
        • 第 17 天:关于合并的基本观念与使用方式
        • 第 18 天:修正 commit 过的版本历史记录 Part 1
        • 第 19 天:设定 .gitignore 忽略清单
        • 第 20 天:修正 commit 过的版本历史记录 Part 2
        • 第 21 天:修正 commit 过的版本历史记录 Part 3
        • 第 22 天:修正 commit 过的版本历史记录 Part 4 (Rebase)
        • 第 23 天:修正 commit 过的版本历史记录 Part 5
        • 第 24 天:使用 GitHub 远端仓库 - 入门篇
        • 第 25 天:使用 GitHub 远端仓库 - 观念篇
        • 第 26 天:多人在同一个远端仓库中进行版控
        • 第 27 天:通过分支在同一个远端仓库中进行版控
        • 第 28 天:了解 GitHub 的 fork 与 pull request 版控流程
        • 第 29 天:如何将 Subversion 项目汇入到 Git 仓库
        • 第 30 天:分享工作中几个好用的 Git 操作技巧
      • zh-tw
        • 第 01 天:認識 Git 版本控管
        • 第 02 天:在 Windows 平台必裝的三套 Git 工具
        • 第 03 天:建立儲存庫
        • 第 04 天:常用的 Git 版本控管指令
        • 第 05 天:了解儲存庫、工作目錄、物件與索引之間的關係
        • 第 06 天:解析 Git 資料結構 - 物件結構
        • 第 07 天:解析 Git 資料結構 - 索引結構
        • 第 08 天:關於分支的基本觀念與使用方式
        • 第 09 天:比對檔案與版本差異
        • 第 10 天:認識 Git 物件的絕對名稱
        • 第 11 天:認識 Git 物件的一般參照與符號參照
        • 第 12 天:認識 Git 物件的相對名稱
        • 第 13 天:暫存工作目錄與索引的變更狀態
        • 第 14 天: Git for Windows 選項設定
        • 第 15 天:標籤 - 標記版本控制過程中的重要事件
        • 第 16 天:善用版本日誌 git reflog 追蹤變更軌跡
        • 第 17 天:關於合併的基本觀念與使用方式
        • 第 18 天:修正 commit 過的版本歷史紀錄 Part 1
        • 第 19 天:設定 .gitignore 忽略清單
        • 第 20 天:修正 commit 過的版本歷史紀錄 Part 2
        • 第 21 天:修正 commit 過的版本歷史紀錄 Part 3
        • 第 22 天:修正 commit 過的版本歷史紀錄 Part 4 (Rebase)
        • 第 23 天:修正 commit 過的版本歷史紀錄 Part 5
        • 第 24 天:使用 GitHub 遠端儲存庫 - 入門篇
        • 第 25 天:使用 GitHub 遠端儲存庫 - 觀念篇
        • 第 26 天:多人在同一個遠端儲存庫中進行版控
        • 第 27 天:透過分支在同一個遠端儲存庫中進行版控
        • 第 28 天:了解 GitHub 的 fork 與 pull request 版控流程
        • 第 29 天:如何將 Subversion 專案匯入到 Git 儲存庫
        • 第 30 天:分享工作中幾個好用的 Git 操作技巧
  • Hands-On Labs - LineBotSDK 實作手札 (C#, .net core)
    • 00. 如何申請LINE Bot
    • CLI
      • 使用CLI來發送新的Channel Access Token(LINE Bot)
      • 使用CLI免費發送LINE Notify通知
    • basic
      • 如何發送LINE訊息(Push Message)
      • 如何發送LINE Template Messages
      • 如何發送ImageMap訊息
      • 如何發送Flex Message
      • 如何在訊息後面加上QuickReply快捷選項
    • liff
      • Lab 21: 建立第一個LIFF應用
    • webhook
      • 如何建立可Echo的基本LINE Bot
      • 如何在WebHook中取得用戶個人資訊(名稱、頭像、狀態)
      • 如何在WebHook中取得用戶上傳的圖片(Bytes)
  • Markdown
    • Markdown Cheatsheet 中文版
    • Markdown語法大全
    • 使用HackMD建立書本目錄
    • 使用HackMD建立簡報
  • SAP ABAP
    • ABAP開發環境和總體介紹
    • SAP MM模塊常用表總結
    • SAP QM數據庫表清單
    • SAP欄位與表的對應關係
  • SQL Server
    • [SQL SERVER] Like in
    • SQL Server 中,移除資料庫中所有的關聯限制
    • SQL Server 刪除資料庫中所有資料表
    • SQL Server View、Function 及 Stored Procedure 定義之快速備份
    • SSMS v18 清除登入畫面中,下拉選單歷史紀錄
    • [MS SQL]如何透過Database Mail進行郵件發送
    • [SQL SERVER]撰寫Stored Procedure小細節
    • 使用 Data Migration Assistant 移轉 SQL Server 資料庫與帳戶
    • 使用SSIS創建同步資料庫數據任務
  • Tools
    • 免費 FTP 伺服器 FileZilla Server 安裝教學 (新版設定)
  • VisualStudio
    • .NET CLI 指令碼介紹
    • Visual Studio 使用 Git 版本控制
    • 使用 Visual Studio 2022 可透過 .editorconfig 鎖定文字檔案的儲存編碼格式分享
  • Web API
    • ASP.NET Core 6 Web API 進行 JWT 令牌身份驗證
    • [ASP.NET Core]如何使用SwaggerAPI說明文件
    • ASP.NET Core Web Api實作JWT驗證筆記
    • ECFIT API 範例
    • JWT Token Authentication And Authorizations In .Net Core 6.0 Web API
    • 微服務架構 - 從狀態圖來驅動 API 的設計
  • Windows
    • [C#] 伺服器監控常用語法 (事件檢視器、CPU 硬碟使用率、程式執行狀況)
    • Configure IIS Web Server on Windows Server 2019
    • Log Paser Studio 分析 IIS W3C Log
    • Windows Server 2019 如何安裝 IIS 運行 ASP.NET 專案
    • 如何檢查安裝在 IIS 上的 .NET Core Hosting Bundle 版本
    • [IIS] 如何解決網站第一個請求 Request 特別慢 ?
    • IIS 不停機更版設置
    • SQL Server 2019 Standard 繁體中文標準版安裝
    • WINDOWS共用資料夾的網路認證密碼放在哪?如何清除?
    • 如何設定 ASP.NET CORE 網站應用程式持續執行在 IIS 上
  • 專案管理
    • SSDLC (Secure Software Development Life Cycle)
    • 系統開發原則
    • MIS及專案管理-使用Redmine
      • 第10章 - [日常管理]MIS部門週會工作進度追蹤
      • 第11章 - [日常管理]MIS部門主管月會報告管理
      • 第12章 - [日常管理]機房工作日誌
      • 第13章 - [日常管理]MIS部門耗用工時及工作進度檢討
      • 第14章 - [日常管理]MIS文件知識庫
      • 第15章 - [日常管理]整理及管理分享
      • 第16章 - [異常管理]使用者問題回報系統
      • 第17章 - [異常管理]資安事件及異常紀錄
      • 第18章 - [異常管理]整理及管理分享
      • 第19章 - [變革管理]MIS的專案及專案管理五大階段
      • 第1章 - [MIS及專案管理]中小企業MIS的鳥事
      • 第20章 - [變革管理]MIS的新專案管理:起始階段
      • 第21章 - [變革管理]MIS的新專案管理:規劃階段
      • 第22章 - [變革管理]MIS的新專案管理:執行階段
      • 第23章 - [變革管理]MIS的新專案管理:監控階段
      • 第24章 - [變革管理]MIS的新專案管理:結束階段
      • 第25章 - [變革管理]整理及管理分享
      • 第26章 - [ISMS管理]ISMS平台整體規劃
      • 第27章 - [ISMS管理]ISMS文管中心
      • 第28章 - [ISMS管理]ISMS表單紀錄的管理
      • 第29章 - [ISMS管理]整理及管理分享
      • 第2章 - [MIS及專案管理]專案管理的概念及MIS應用
      • 第30章 - 初心、來時路及感謝:系列文章總結回顧
      • 第3章 - [MIS及專案管理]管理工具的選擇
      • 第4章 - [Redmine]Redmine的安裝及設定
      • 第5章 - [Redmine]Redime系統邏輯說明
      • 第6章 - [Redmine]自行建立及維護表單
      • 第7章 - [Redmine]專案版面的規劃
      • 第8章 - [日常管理]AR管理
      • 第9章 - [日常管理]資訊服務申請
  • 微服務架構
    • DDD + CQRS + MediatR 專案架構
    • 微服務架構 #2, 按照架構,重構系統
    • 淺談微服務與網站架構的發展史
    • API First Workshop 設計概念與實做案例
      • API First #1 架構師觀點 - API First 的開發策略 - 觀念篇
      • API First #2 架構師觀點 - API First 的開發策略 - 設計實做篇
    • 基礎建設 - 建立微服務的執行環境
      • Part #1 微服務基礎建設 - Service Discovery
      • Part #2 微服務基礎建設 - 服務負載的控制
      • Part #3 微服務基礎建設 - 排隊機制設計
      • Part #4 可靠的微服務通訊 - Message Queue Based RPC
      • Part #5 非同步任務的處理機制 - Process Pool
    • 實做基礎技術 API & SDK Design
      • API & SDK Design #1, 資料分頁的處理方式
      • API & SDK Design #2, 設計專屬的 SDK
      • API & SDK Design #3, API 的向前相容機制
      • API & SDK Design #4, API 上線前的準備 - Swagger + Azure API Apps
      • API & SDK Design #5 如何強化微服務的安全性 API Token JWT 的應用
    • 建構微服務開發團隊
      • 架構面試題 #1, 線上交易的正確性
      • 架構面試題 #2, 連續資料的統計方式
      • 架構面試題 #3, RDBMS 處理樹狀結構的技巧
      • 架構面試題 #4 - 抽象化設計;折扣規則的設計機制
    • 架構師觀點 - 轉移到微服務架構的經驗分享
      • Part #1 改變架構的動機
      • Part #2 實際改變的架構案例
    • 案例實作 - IP 查詢服務的開發與設計
      • 容器化的微服務開發 #1 架構與開發範例
      • 容器化的微服務開發 #2 IIS or Self Host
  • 系統評估
    • RPA 與 WebAPI 評估
    • 數位轉型:從現有系統到數位化的未來
    • 數位轉型:從現有系統到數位化的未來
  • 面試
    • CV_黃子豪_2024
    • HR 問題集
    • .NET 工程師 面試問題集
    • 資深工程師 問題集
    • 資深開發人員 / 技術主管
    • 題目
Powered by GitBook
On this page
  • API & SDK Design# 5 如何強化微服務的安全性? API Token / JWT 的應用
  • API Token 原理說明
  • API Token 要解決的難題
  • RSA 數位簽章
  • API Token 實作 (土炮版)
  • 阻擋 Replay Attack
  • JWT (Json Web Token)
  • 延伸應用
  • 1, 軟體的啟用序號
  • 2, 雲端分散式的系統驗證
  • 以下開放 Q & A:
  • 結論
  1. 微服務架構
  2. 實做基礎技術 API & SDK Design

API & SDK Design #5 如何強化微服務的安全性 API Token JWT 的應用

PreviousAPI & SDK Design #4, API 上線前的準備 - Swagger + Azure API AppsNext建構微服務開發團隊

Last updated 1 year ago

API & SDK Design# 5 如何強化微服務的安全性? API Token / JWT 的應用

API Token 原理說明

接下來就來重現當天線上讀書會的內容了。讀書會全程都有 (1小時),不想花一小時看得,可以 直接看 slideshare 的,搭配 github 上面的 一起服用。這些連結請到文章最後面就看的到。

API Token 要解決的難題

首先先來看看,一般的網站開發,若碰到熱門的服務導向架構,你需要從 A 網站或服務取得的授權,再到 B 網站去使用 他的服務,同時 A B 兩個服務之間沒有辦法很有效率且即時的溝通時,如何解決兩個網站之間的信任問題? 尤其是在 internet 上,離開服務的範圍都是不安全的,你傳遞的資訊送到 client side (不論是 APP 或是 browser) 更是不安全,資安專家 有上百種的方式可以把你想隱藏的資訊挖出來破解…

舉個例子,如果 A 服務是付款,同時讓你取得遊戲點數,你可以去 B 服務訂購商品。B 服務寄出商品後會月底跟 A 服務商 收取貨款。這種情況下如果資訊的傳遞沒有保護好,client 在 A 付了 100 元,卻可以在 B 訂購 1000 元的商品,這樣你還 敢做生意嗎?

API Token 要解決的難題

簡單的用一張圖來代表這個情境,你必須想一個方式來確保這些資訊的傳遞是安全的,否則賠錢的生意絕對做不久的…

先來看看實體的世界怎麼解決問題? 大家應該都買過車票,我舉個類似的情境。你在 7-11 訂了張車票,也付款取票了。 當你要去車站搭車時,車站的驗票系統或是站長確認無誤,讓你進站搭車了。這時站長可能當場打電話給 7-11 確認這張 車票嗎? (Orz, 突然覺得我這例子有點蠢..) 當然不會這樣做。所以車站憑 “什麼” 確認這張票的正確性? 同時票上面 也註記了你的服務使用範圍,例如你可以搭 1/1 72車次 莒光號,從台北到花蓮站,座位是 9 車 16 號…

最基本的是車票本身有防偽造的設計,例如蓋章,雷射標籤,磁卡等等。這機制如果夠可靠,車站只需要檢查車票是不是 假的 (是不是真的從售票單位賣出來的?),只需要確認上面標記的資訊有無被竄改 (台北到花蓮,有沒有被塗改?)。這些 資訊若都能簡單的驗證,那麼就可以省掉大幅的溝通成本,不需要跟 7-11 當場確認就可以讓乘客搭車了。

RSA 數位簽章

當你把問題拆解成: B 要能驗證資訊是原封不動從 A 送出的,這問題其實就簡化很多了。關鍵技術就只有一個: 數位簽章。 先不講技術,來講 “簽章” 的目的是什麼?

現實世界的文件,合約等等,都是印出紙本,要你看完後在上面簽名,代表你同意上面的內容。或是新聞稿、公文等,你在 上面簽名,代表這是你本人發出來的訊息,不是路人甲假冒你名字發言的。為什麼簽個名就有這效果? 因為每個人的筆跡不同, 簽名是無法偽造的,所以把明文跟簽名放在一起,就有這種效用。

數位的世界怎麼重現這樣的情境? 那就是密碼學裡常講到的 “數位簽章” (digital signature) 了。 講到密碼學,我想大家頭就開始痛了,我點到為止就好,我們常聽到的 RSA 加密,各位只要記得三個原則:

這原則你就暫時把它當作鐵律,別再去懷疑他了。他是有數學的原理在背後支持的,當這三條鐵律在有限的時間內不可能被 破解的話,我們能怎麼用它來解決問題? 先來看看 RSA 怎麼做到數位簽章的效果?

Hint: Hash

在這產生簽章的過程中,Hash 是個必須要懂得技巧。你可以用特定演算法,把一連串的資料算出他的 Hash。如果原始資料 改了一個 byte, 那算出來的 Hash 就會完全不一樣。若不一樣的資料算出一樣的 Hash, 這就是 Hash 碰撞。好的演算法,很少會碰到碰撞的狀況。 尤其是原始資料看起來格式等等都正確,只是改了你想改的內容時,還能算出一樣的 Hash, 那機率更低。

數位簽章的原理就是拿原始資料的 Hash, 用你的 private key 算出簽章,附加在你的原始資料中,就是簽章過的資料了。

由 A 送出的簽章過的資料,送到 B 手上,B 只要用相反的程序驗證就可以了。其中的關鍵就是,經過 private key 加密的 Hash, 只有對應的 public key 能解開。若你用特定對象的 public key 解開後驗證無誤,你就能確認手上的資料是他的 private key 產生出來的。然而 private key 只有對方手上才有,整個過程就可以肯定資料來源跟正確性。

實際上驗證的步驟是:

  1. 分解出資料跟簽章兩部分

  2. 將簽章 (加密過的 Hash) 用 public key 解密,可以還原出 Hash

  3. 將 (1) 分解出的資料,自己重新計算一次 Hash #2

  4. 比對 Hash 與 Hash #2,若兩者相同,則資訊安全無誤,是 A 送出來的內容沒有被竄改過

這麼神奇嗎? 沒錯! 這就是數學神奇的地方。搭配這簡單的應用,你就能確認手上的資訊是安全的了。即使在傳遞的過程 中被 1000 個 hacker 看光了,那也無所謂,這資訊沒被竄改就足夠了,不是嗎?

API Token 實作 (土炮版)

好,數位簽章的故事講完,開始應用了。前面講的數位簽章能怎樣幫我們解決難題?

回到剛才的場景,當你通過帳號密碼等等各種驗證機制後,A 服務已經願意跟你交易了,你付了 100 元,A 產生了簽章過的 100 元 代幣給你,這就是 SESSION TOKEN。你可以憑著這 SESSION TOKEN 到 B 去使用,所有的帳都記在 A 的帳上。B 怎麼確認 A 同意你這樣做? 用上述流程驗證簽章過的 SESSION TOKEN 就可以了。

當然 A 能附加在 SESSION TOKEN 上的資訊不只是 “100元” 這麼簡單,你可以附加任何你想要的資訊上去,只要最後記得 正確地完成簽章程序就好了。

該怎麼做? 開始來看 code 了。其實這些做法我大約一年前有三篇文章講過了,這邊我摘要的說明就好。為了簡化簽章跟驗證的程序, 我設計了簡單的 TokenData 與 TokenHelper 兩個類別。TokenData 用來存放 Token 本身的資料,他會被 Json 的 library 進行 serialization / deserialization。TokenHelper 則是做編碼及解碼動作用的工具類別。

直接來看 code, 先看 TokenData, 所有的 TokenData 都必須衍生自 TokenData 這個基底類別:

/// <summary>
/// 如何自訂 TokenData ?
/// 
/// 1. 繼承自 TokenData
/// 2. 加上你的自訂項目,標上 [JsonProperty]
/// 3. 覆寫 (override) bool IsValidate( ), 自訂你的授權驗證邏輯
/// 
/// 完成
/// </summary>
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public abstract class TokenData
{
    internal TokenData() { }

    /// <summary>
    /// 對應 TokenData 衍生類別的 Type Name
    /// </summary>
    [JsonProperty]
    public string TypeName { get; internal set; }

    [JsonProperty]
    public DateTime ExpireDate { get; set; }
    

    /// <summary>
    /// (可覆寫) 驗證 Token Data 資料是否合法
    /// </summary>
    /// <returns></returns>
    public virtual bool IsValidate()
    {
        if (this.GetType().FullName != this.TypeName) return false;
        if (DateTime.Now > this.ExpireDate) return false;
        return true;
    }
}

這次範例裡面用到的 SessionToken:

public class SessionToken : TokenData
{

    [JsonProperty]
    public DateTime CreateDate { get; set; }

    [JsonProperty]
    public string ClientID { get; set; }

    [JsonProperty]
    public string UserHostAddress { get; set; }

    [JsonProperty]
    public bool EnableAdminFunction { get; set; }

    [JsonProperty]
    public bool EnableVIPFunction { get; set; }

    [JsonProperty]
    public bool EnableMemberFunction { get; set; }
}

接著來看看 TokenHelper 如何替 TokenData 做編碼及解碼? 編碼 (Encode) 的部分:

public static string EncodeToken(string keyName, TokenData token)
{
    if (_RSACSP_STORE[keyName].PublicOnly) throw new InvalidOperationException("Need Private KEY.");

    // TokenData 經過序列化之後的 binary data (使用 BSON format)
    byte[] data_buffer = null;
    {
        MemoryStream dataMS = new MemoryStream();
        using (BsonWriter bw = new BsonWriter(dataMS))
        {
            JsonSerializer js = new JsonSerializer();
            token.TypeName = token.GetType().FullName;
            js.Serialize(bw, token);
        }
        data_buffer = dataMS.ToArray();
    }
    
    // data_buffer 的簽章
    byte[] sign_buffer = null;
    {
        //sign_buffer = _PublicKeyStoreDict[_CurrentSiteID].SignData(
        sign_buffer = _RSACSP_STORE[keyName].SignData(
            data_buffer,
            _HALG);
    }

    // 打包 data_buffer, sign_buffer
    return string.Format(
        @"{1}{0}{2}",
        _SplitChar,
        Convert.ToBase64String(data_buffer),
        Convert.ToBase64String(sign_buffer));
}

接著是解碼 (Decode) 的部分:

public static T DecodeToken<T>(string keyName, string tokenText) where T : TokenData, new()
{
    bool isSecure;
    bool isValidate;
    T token = TryDecodeToken<T>(keyName, tokenText, out isSecure, out isValidate);

    if (isSecure == false) throw new TokenNotSecureException();

    if (isValidate == false) throw new TokenNotValidateException();

    return token;
}


public static T TryDecodeToken<T>(string keyName, string tokenText, out bool isSecure, out bool isValidate) where T : TokenData, new()
{
    string[] parts = tokenText.Split(_SplitChar);

    if (parts == null || parts.Length != 2) throw new TokenFormatException();

    byte[] data_buffer = Convert.FromBase64String(parts[0]);
    byte[] sign_buffer = Convert.FromBase64String(parts[1]);

    // 還原 token 物件,將資料反序列化還原為 object, 同時驗證 token 的授權是否合法
    T token = null;
    {
        MemoryStream ms = new MemoryStream(data_buffer, false);
        using (BsonReader br = new BsonReader(ms))
        {
            JsonSerializer js = new JsonSerializer();
            token = js.Deserialize<T>(br);

            if (token == null) throw new TokenFormatException();
        }
        isValidate = token.IsValidate();
    }
    
    isSecure = _RSACSP_STORE[keyName].VerifyData(
        data_buffer,
        _HALG,
        sign_buffer);

    return token;
}

接著就是上述數位簽章的程序了,計算 Hash 並且用 private key 加密 (這段都被包在 .SignData() 內了), 最後的結果跟 data 合併再一起。

解碼的過程就是反過來,加上驗證的結果就結束了。

這些基礎準備好之後,後續用起來就簡單了,在 A 的部分 (請參考 AUTH 這個 project) 這樣使用,可以得到 一串 SessionToken 字串:

/// <summary>
/// 已經註冊過的開發者,可以憑 APIKEY,向 AUTH 認證服務
/// 建立 SESSION。SESSION 建立成功後會取得 SESSION TOKEN,
/// 可以憑 TOKEN 道別的服務去呼叫 API。
/// 
/// SESSION TOKEN 有效期限為 60 分鐘
/// </summary>
/// <returns></returns>
public IHttpActionResult Post()
{
    string apikey = this.Request.Headers.GetValues("X-APIKEY").First();
    ApiKeyToken apikeyToken = TokenHelper.DecodeToken<ApiKeyToken>("APIKEY", apikey);

    SessionToken sessionToken = TokenHelper.CreateToken<SessionToken>();
    sessionToken.ClientID = apikeyToken.ClientID;
    sessionToken.UserHostAddress = System.Web.HttpContext.Current.Request.UserHostAddress;
    sessionToken.CreateDate = DateTime.Now;
    sessionToken.ExpireDate = DateTime.Now.AddHours(1.0);
    sessionToken.EnableAdminFunction = false;
    sessionToken.EnableMemberFunction = !apikeyToken.Tags.Contains("BAD");
    sessionToken.EnableVIPFunction = apikeyToken.Tags.Contains("VIP");

    return new TokenTextResult("SESSION", sessionToken);
}

client 拿到 SessionToken 後,呼叫 B 的 API 時會把 SessionToken 一起帶過去。在 B 只要這樣 還原 SessionToken 物件,沒有產生任何 Exception 就算驗證成功:

/// <summary>
/// 骰出指定次數的骰子
/// </summary>
/// <param name="count">要骰幾次?</param>
/// <returns></returns>
public IEnumerable<int> Get(int count)
{
    // session check
    SessionToken session = TokenHelper.DecodeToken<SessionToken>(
        "SESSION", 
        this.Request.Headers.GetValues("X-SESSION").First());

    // 以下略過
}

API 專案則代表遊樂場,他會依據你的 SESSION TOKEN,確認無誤且沒有過期的話,就提供遊樂設施給你使用。隨著 Tags 註記的不同,你能夠享用的設施層級也不同。被標記為 VIP 則可無限次使用 (骰骰子可骰無限次)。若被標記為 奧客 (BAD) 則只能骰五次。沒有特別標記就是一般會員,最多可以骰 10 次。

各位可以自己試看看,你也許可以還原 SESSION 的內容,但是除非你有拿到 A 的 private key, 否則你再怎麼樣 也無法產生你想要的 SESSION TOKEN 內容。就算只是想從已產生的 SESSION TOKEN 去把 BAD 改為 VIP 也沒辦法。 因為你改不動簽章的部分。一但你改了原始資訊,簽章還原的 Hash 還是修改前的 Hash, 而 B 重新計算的 Hash #2 則是修改後的 Hash, 演算法告訴我們這樣的情況下 Hash 會不同,造假的 SESSION TOKEN 就被揪出來了。

阻擋 Replay Attack

簡單的說,Hacker 根本不用去跟你的 TokenData 加解密等等去奮鬥,他只要去偷一份真實的 SessionData,在他 過期之前使用就夠了。因為他偷來的是 100% 真實,100% 正常程序產生的 SessionToken, 你要驗證也一定是對的.. 這種情況我們該怎麼應付?

其實,只要多一個小動作,你就能阻絕那些竊聽 / 攔截 Token 的攻擊了。在現實世界,這就像 有小偷把你的車票偷走,複製一份 (而且複製的非常精美,分不出真假)。結果偷兒就用這張假車票去搭車了… 預防方法很簡單,想辦法在車票上記名,然後搭車再出示身分證件。

這樣的機制,在數位的世界要怎麼做? 其實 Token 的應用是可以有很多變化的,先來重新來看一下 SessionToken 的定義長什麼樣子:

public class SessionToken : TokenData
{
    [JsonProperty]
    public DateTime CreateDate { get; set; }

    [JsonProperty]
    public string ClientID { get; set; }

    [JsonProperty]
    public string UserHostAddress { get; set; }

    [JsonProperty]
    public bool EnableAdminFunction { get; set; }

    [JsonProperty]
    public bool EnableVIPFunction { get; set; }

    [JsonProperty]
    public bool EnableMemberFunction { get; set; }
}

除了 TokenData 就內含 ExpireDate 來紀載這個 Token 的有效期限之外,SessionToken 也額外附加 了 UserHostAddress, 用來紀載授權的 Client 的 IP Address. 因為 Session 是控制很短的一段時間內 的授權,通常是幾分鐘,到幾個小時不等,我先假設這段期間內使用者的 IP 不會變化。

加了這個欄位能幹嘛? 這欄位在 client 跟 A (AUTH) 取得授權,產生 SessionToken 時就決定好了,A 偵測到 使用者 IP 就把他寫進 Token, 簽章之後整筆 Token 到了 B (API),驗證玩 Token 後接著驗證使用者 IP 是否 符合,通過再提供服務。

這裡使用 IP 也是類似的意思。當然 IP 不是 100% 適合的資訊,這只是當作範例。如果你的 client 是手機的 APP,行動網路 IP 一直在變化,這時你若能取得 device unique ID, 或是電話號碼等等,都是個值得考慮的作法。

除非 hacker 偷倒 token, 還能偽造你的 IP, 或是 device unique ID 等等,否則你這作法相對的就更安全 了,不用花太多額外成本,就能阻擋掉大部分的惡意攻擊對象。

JWT (Json Web Token)

最前面的前言,說過我的想法,不一定什麼都要自己從頭做。但是有機會的話自己試著做一次 POC,對了解問題 是很有幫助的。上面貼的 100 行左右的 TokenData / TokenHelper 就是這個目的之下寫出來的,但是當你真正 要正式上線使用的時候,找一套使用的人多,工具及語言支援廣泛的套件來用,是比較合適的。

他的格式區分為 header, payload, signature 三段, 有標準的套件可以產生 & 解碼,其中加密演算法及強度等 也都可以自由控制。

支援的工具及平台也很廣泛 (這就是土炮的方案最不足的一環了),大概現在還有人在用的語言都有支援… Orz.. 官網 > Libraries 就可以看到支援的清單,.NET, Java, Python, Node.js, JavaScript, Perl, Ruby… 就不一個一個列下去了。

本來我想把上面的 sample code 改一個 JWT 版本的,不過感覺很多餘… 一來 Token 的用法,其實不用改就 可以表達得很清楚了。再者如果是要示範 JWT 的語法,我講應該不如官網講得清楚 @@

這篇看完,或是看完這篇前面 10 min 的影片大概就會用了

延伸應用

其實,看似很困難的密碼學,包裝成 Token, 再搞懂原理後其實還蠻實用的。我舉兩個我常用的應用方式,不論你 是自己土炮,或是用 JWT 都適用!

1, 軟體的啟用序號

第一個就是軟體的啟用序號。過去軟體往往都花很多心思在搞定這問題,搞了一堆複雜的機制,最後沒擋到盜版,卻 苦了合法的使用者…。其實用了數位簽章,序號本身就很難破解了啊,強度夠的話理論上是不可能的,除非有人把 private key 流出來..

做法很簡單,你把授權的資訊當作 TokenData, 最後加上簽章,整串就是啟動序號了。軟體啟動時只要驗證過無誤 再按照 TokenData 上的描述,啟用對應的功能跟期限就可以了。Public Key 怎麼拿? 既然都是 publc key, 那就 直接放到 code 裡面就可以了。

不過這樣不代表萬無一失喔,這只代表序號本身是夠安全的…,如果我直接反組譯,跳過檢查的 code 還是沒輒。 更 X 的是我另外產生一組 public / private key, 換掉 code 裡面的 public key, 之後我就可以拿造假的 private key 產生新的授權…

如果你面對的對手是有能力改 source code or binary code, 那這部分就要另外傷腦筋了。正規的解決方式是:

你編譯出來的 code 也簽章就好了~ 這個各大開發工具,各大平台其實都支援了。最簡單的例子,手機 APP 就預設 都採取這種做法了。你必須用自己的 key sign 過後的 APP 才能上架,原理也是相同。兩者合併使用就萬無一失了。

2, 雲端分散式的系統驗證

牽扯到雲端、分散式系統、SOA、甚至是微服務 (microservices) 都會有類似的議題:

  1. 哪些服務是合法建立的? 是可以信賴的?

  2. 那些服務可以使用哪些其他網站的服務?

這邊一樣可以用同樣方式解決。我的概念是: 把每個服務當成一個點,服務跟服務之間若允許使用,就會有個箭頭 (線)。 每個點都要產生一組 SiteToken 給他,用來驗證 site id (authentication), 每個線都要產生一組 ServiceToken 給他, 用來驗證用來驗 authorization.

畫張示意圖,如下, 紅字代表 token 裡面要存放的資訊:

每個 site 都有自己的 site token, 綁定 site id 跟 site url, 因此 site id 就是無法偽造的了,也無法把這服務架在 未被授權的 URL。服務端被呼叫前,只要先檢查 service token 的兩端 site id 是否正確 (身分驗證)? 通過後再 驗證 service id 是否符合 (服務授權)? 都通過後就能直接提供服務了。就如同前面的範例 SessionToken,你可以 按照 service token 裡面的資訊決定要提供什麼層級的服務。

這一切的一切,你只要做好一件事情: 保管好你的 private key, 所有的 token 都由你親自產生提供給系統維護者就可以了。 這樣你至少就可以擋掉未經授權的使用者,或是有人架設私服來使用你的服務了。

以下開放 Q & A:

這是在當天讀書會有人問到的問題,不過當場沒有回答到,我在這裡佔點篇幅補充一下:

Q: 有人問到,我講的用 private key 加密,用 public key 解密用反了。應該是 public key 加密,private key 解密才對。

A: 其實兩種方法都可以。RSA 只有要求用哪個 KEY 加密,你就要用同一對的另一個 KEY 去解密。 兩種都有不同的使用時機。我舉幾個例子:

  1. X 要寄私人信件給 Y,這是機密的信件,X 只想讓 Y 看的到內容,其他人一律不給看。 這時正確的做法,就是 X 拿 Y 的 public key 加密後寄給 Y。因為全世界只有 Y 手上有他自己的 private key, 所以 這封信只有 Y 能解的開。

  2. X 要張貼公告給所有人,要證明這公告是 X 親自送出的。 這時正確的做法,是 X 用自己的 private key 替這公告加密。其他任何一個人只要拿 X 的 public key 解密, 能解開就代表這公告是 X 發出的無誤。如果公告內容太龐大,可以先把公告內容化簡為 Hash, 再針對 Hash 加密 即可。 針對 Hash + 加密的作法,其實也就是我們講的數位簽章。

  3. 極機密的信件,例如 FBI 要送指令給特務… 要確認訊息是 FBI 發出的,而且要確認只有指定的 SPY 能解讀。 這時正確做法是,合併使用兩者的 KEY 就可以了。 FBI 拿自己的 private key 加密訊息,再拿 SPY 的 public key 加密第二次。SPY 收到訊息後,拿自己的 private key 解密,成功後再拿 FBI 的 public key 解密。 只有發訊息者跟收訊息者,兩邊都是正確的,才有可能完成這整個程序。

所以,不同情況下有不同的應用,用在 Token 的情境時,用 private key 加密是正確的作法… :D

結論

安全問題一直很頭痛,分散式的安全更頭痛。微服務架構很強調 “去中心化”,對於做法的正確性就更講究了。 這種情況下架構的正確性是第一要務,千萬不要自己硬搞一些奇奇怪怪自以為安全的機制啊,多用標準的演算法 以及 framework 才是上策!

RSA基本概念
RSA基本概念-產生數位簽章
RSA基本概念-驗證數位簽章

程式碼加起來大約 100 行左右而已,稍微看一下應該不難看懂。其實只有幾個關鍵而已,TokenData 用來代表 你要保護的資料內容,你可以自己衍生出來擴充。只要配合 Json 的規範去標記 attrib 就可以了。編碼之前 會先用 Json 把你的 TokenData 編成字串 (範例我用 , Binary Json, 效率跟體積會好一些)。

好,這麼一來你可以不用擔心 SessionToken 的內容被亂改了。裡面標記的資訊都是可以信賴的,不需要懷疑他。 這個例子我是舉遊樂場的例子,AUTH 代表售票口,使用者透過 API TOKEN 認證通過後,就可以取得 SESSION TOKEN。 裡面註記了使用期限 (ExpireDate), (UserID),還有 A 對你的註記 (標籤 Tags)。這些

整個 Demo 的過程很多細節,文章不大容易表達,有興趣的可以直接看影片的 Demo, 那邊解說得比較詳細。同時還有 示範如何用 Swagger 測試 API 的方式。正好 DEMO 的東西也都放在 Azure 上面,用的跟 講的是同一個服務: Azure API Apps。 有興趣的朋友可以參考這段 : 11:50 ~ 43:00

甚麼是 “Replay Attack” ? 先來看看 的說法: https://en.wikipedia.org/wiki/Replay_attack

這邊我推薦的,就是熱門的套件: JWT 是什麼? 能幹嘛? 其實前面你都看懂的話,那就都講完了! 唯一不同的是他的格式是有 RFC 規範的。我從官網 截一張圖來說明:

示意圖

所以我就用官網的資源代替了,想進一步了解的可以參考 :

示意圖
示意圖
BSON
你的名字
上一篇文章
影片
Wiki
JWT - Json Web Token
這邊
錄影
投影片
source code