Latest

OAuth 2.0 的身份認證:OpenID Connect

OAuth 2.0 的身份認證:OpenID Connect

OAuth 2 讓網路服務可以存取第三方的受保護資源,因此,有些開發者會進一步利用 OAuth 2 來進行使用者認證。但這中間存在著一些語義落差,因為 OAuth 2 當初設計目的是「授權」而不是「認證」,兩者關注的焦點會有些不同。OpenID Connect 是基於 OAuth 2 的一套身份認證協定,讓開發者可以在 OAuth 2 授權的基礎上,再加入標準的認證流程。在這篇文章中,我會說明授權跟認證的場景有何差異,並講解 OpenID Connect 如何滿足認證需求。 因為 OpenID Connect 是建構在 OAuth 2 的基礎上,我會假設這篇文章的讀者已經知道 OAuth 2 的組件與流程,如果你不熟悉,可以先閱讀另外兩篇文章 * OAuth 2.0:

By Ken Chen
更好的選擇?用 JWT 取代 Session 的風險

更好的選擇?用 JWT 取代 Session 的風險

因為 HTTP 是無狀態協定,為了保持使用者狀態,需要後端實作 Session 管理機制。在早期方式中,使用者狀態會跟 HTTP 的 Cookie 綁定,等到有需要的時候,例如驗證身份,就能使用 Cookie 內的資訊搭配後端 Session 來進行。但自從 JWT 出現後,使用者資訊可以編碼在 JWT 內,也開始有人用它來管理使用者身份。前些日子跟公司的資安團隊討論,發現 JWT 用來管理身份認證會有些風險。在這篇文章中,我會比較原本的 Session 管理跟 JWT 的差異,並說明可能的風險所在。 Session 管理 Session 是什麼意思?為什麼需要管理?我們可以從 HTTP 無狀態的特性聊起。所謂的無狀態,翻譯成白話,就是後面請求不會受前面請求的影響。想像現在有個朋友跟你借錢,

By Ken Chen

Goroutine 的併發治理:掌握生命週期

從併發的角度來看,Goroutine 跟 Thread 的概念很類似,都是將任務交給一個執行單元來處理。然而不同的是,Goroutine 將調度放在用戶態,因此更加輕量,也能避免多餘的 Context Switch。我們可以說,Go 的併發處理是由語言原生支援,有著更好的開發者體驗,但也因此更容易忘記底層仍存在著輕量成本,當這些成本積沙成塔,就會造成 Out of Memory。這篇文章會從 Goroutine 的生命週期切入,試著說明在併發的情境中,應該如何保持 Goroutine 的正常運作。 因為這篇講的內容會比較底層,如果對應用情境不熟的人,建議先看過同系列 * Goroutine 的併發治理:由錯誤處理談起 * Goroutine 的併發治理:值是怎麼傳遞? * Goroutine 的併發治理:管理 Worker Pool 再回來看這篇,應該會更容易理解。 Goroutine 的資源使用量 讓我們看個最簡單的例子,假設現在同時開

By Ken Chen

Goroutine 的併發治理:管理 Worker Pool

併發會需要多個 Goroutine 來同時執行任務,Goroutine 雖然輕量,也還是有配置成本,如果每次新的任務進來,都需要重新建立並配置 Goroutine,一方面不容易管理 Goroutine 的記憶體,一方面也會消耗 CPU 的運算效能。這時 Worker Pool 就登場了,我們可以在執行前,先將 Goroutine 配置好放到資源池中,要用時再調用閒置資源來處理,藉此資源回收重複利用。這篇文章會從 0 開始建立 Work Pool,試著丟進不同的場景需求,看看如何實現。 基本的 Worker Pool Worker Pool 的概念可以用這張圖來解釋 Job 會放在 Queue 中送給 Pool 內配置好的 Worker,Worker 處理完後再將結果送到另一個 Queue 內。因為這是很常見的併發模式,

By Ken Chen

Goroutine 的併發治理:值是怎麼傳遞?

當併發時,每個 Goroutine 可以看成是一個個單獨的個體,他們維護著自己的 Call Stack,彼此互不干涉。如果希望這些默默運行的 Goroutine 攜手完成任務,就要在他們之間建立一種通訊方式。在 Go 中,資訊應該如何被傳遞?其中的權衡又有哪些?這篇文章會介紹 Goroutine 常用的三種值的傳遞方式,以及相關衍生議題。 使用閉包取得值 第一種方式是使用閉包。先來想想輸出應該要長怎樣,假設建立 100 個 Goroutine,每個 Goroutine 會收到一個值並印出來,傳給 Goroutine 的值應該都要不同,最後印出來的結果會是 0 到 99。這裡用 time.Sleep 模擬長時間的處理,用 println 印出值來觀察操作結果。程式碼是 func main() { var wg

By Ken Chen

Goroutine 的併發治理:由錯誤處理談起

當需要同時執行多個任務時,Go 開發者會多開 Goroutine 來分擔任務,這稱為併發。併發聽起來似乎很理想,能其他任務等待時,照樣執行需要運算的任務,有效利用 CPU 資源,但如果要用在生產環境,它也需要完善的管理機制。想想看,Goroutine 在哪個情況下會被啟動?哪個情況下會結束?如果任務需要回傳結果,它應該要怎麼回傳?而如果執行中發生錯誤,又應該怎麼處理? 我們可以稱呼這類主題為「併發治理」,需要開發者理解執行期的運作,而如何處理好 Goroutine 的開始與結束,讓錯誤能被意識到,可說是併發治理的第一關。 基本併發 來看個基本的併發操作。我們起 100 個 Goroutine,讓它們處理任務。如果執行時發生 error,就呼叫 HandleError 處理錯誤。 func main() { var wg sync.WaitGroup for i

By Ken Chen
設計模式作為一種語言:物件導向的語法要素

設計模式作為一種語言:物件導向的語法要素

「設計模式」這個詞出自 Christopher Alexander 的《建築模式語言》,這本書出版於 1977 年,主題圍繞著建築,城市設計和社區宜居性。作者 Alexander 是一名建築師,他在意的是,能不能找到一種切實可行的模式語言,讓讀者用以設計辦公室、車庫或公共建築。 儘管這樣的期待難免於可操作性,但在另一方面,Alexander 同時是名評論家跟創作者,在創作的過程中,他也想知道,現實繁複的表象下,是不是存在某種共通性,能用於解釋建築間特定的規律?我們可以看到作者羅列出 253 個模式,像是活動中心(30)、墓地(70)與啤酒館(90),Alexander 說:「每一模式描述我們周圍環境中一再發生的某項問題,接著描述解決該問題的關鍵所在,如此一來,你就能上萬次利用這種解決方式,而不必每次從頭來過。」 如同描述資料的資料稱為「後設資料(metadata)」,描述語言的語言稱為後設語言,而 Alexander 給這套後設語言起的名字是「

By Ken Chen

OAuth 2.0:用 Go 跟 Google 要資料

在上一篇的結論中,我們講到開發者通常最想知道,開發 OAuth 2.0 客戶端需要什麼知識。後端工程師要實現 OAuth 2.0,最常見的情境是開發一個客戶端應用,用來存取資源擁有者的受保護資源。因此在這篇中,我們將用 Go 來牛刀小試一番,開發一個網路應用,它會取得使用者同意後,跟 Google 拿取使用者姓名並顯示出來。 註冊客戶端 不是隨便哪個應用都能跟 Google 授權伺服器申請授權,要跟授權伺服器互動,首先要人家願意信任你。因此,在開始寫程式前,要先到 GCP 的 APIs & Services 中註冊客戶端,連結是這個 點選 CREATE CREDENTIALS 並選擇 OAuth client ID,創造一個新的客戶端憑證 我們要開發的是個網路應用,Application type 選

By Ken Chen
OAuth 2.0:授權許可

OAuth 2.0:授權許可

在前一篇中,我們討論了 OAuth 2.0 的角色與信道,知道 OAuth 2.0 將授權模型劃分為四個角色,讓它們經由前/後端信道交流,完成整個授權許可流程。在這篇中,我們要進一步來討論,具體的授權許可是什麼?我們將改由時序的角度出發,探討模型中的物件如何交換訊息。如果覺得這段話太抽象,可以理解成,上一篇介紹了遊戲中的角色與道具,而在這篇,我們將來介紹遊戲的流程與規則。 底下的介紹會著重在授權碼許可(Authorization Code Grant)跟隱式許可(Implicit Grant)兩種 Web 應用場景的授權許可。至於資源擁有者憑證許可(Resource Owner Password Credentials)跟客戶端憑證許可(Client Credentials),雖然在 RFC 6749 有提到,但因資安風險較高,需要資源擁有者非常信賴客戶端且沒有其他方式情況下,才會拿來使用。 Authorization

By Ken Chen
OAuth 2.0:角色與信道

OAuth 2.0:角色與信道

使用者體驗是 B2C 重要的產品面向。通常一個網路服務,會要求使用者註冊帳戶後才能開始使用——以台灣金融保險法規為例,使用者需要建立帳戶後,才能得到報價。站在行銷觀點,註冊會降低用戶的轉換率,因為它需要填寫姓名、暱稱、生日、信箱等資料,步驟相當繁瑣,對行動場景,這百分百是個負面體驗。這讓人不禁想問,這個環節是可以優化的嗎? 事實上,我們可以合理假設使用者資訊已經存在社群媒體中,例如 Google、Facebook、Twitter、GitHub,而我們需要的只是請求使用者同意,讓我們可以代表使用者,存取社群媒體中受限制的資源。也就是,我們關注的是有沒有一個授權框架,可以讓第三方應用取得對資源的訪問權限。 OAuth 2.0 這就帶到 OAuth 2.0 想要解決的問題。在傳統的 Client-Server 認證架構中,當客戶端要存取伺服端資源時,需要提供使用者的帳號密碼。如果發起請求的是第三方應用,則資源擁有者要將帳號密碼提供給第三方應用。可以想像,你需要請人幫你收信,就需要把家裡鑰匙交給對方。

By Ken Chen
配置存放於環境:Go 應用的配置實踐

配置存放於環境:Go 應用的配置實踐

在雲原生的環境中,程式通常採用容器部署,而不同環境間所需要的配置也會不同,像是開發環境的資料需要與生產環境分離;金絲雀部署要分流生產環境的流量,但不會寫資料到生產環境中;開發環境為了除錯,要印出 level 低的 log;開發環境跟生產環境要拿取的 key vault 的 key 跟 version 不同;等等。在雲原生的時代前,開發人員或維運人員通常會各自維護一份執行的程式,開發在開發環境中驗證後,交付維運部署上線,一次性處理好配置;但在雲原生時代,部署變得越來越頻繁,幾乎不太可能手動管理。這時要把問題倒過來想,不是因應開發出來的程式來設定配置,而是有沒有可能,因應部署會遇到的問題來設計開發? Store config in the environment Heroku 基於 SaaS(Software-as-a-Service) 實踐,歸納出 12 條雲原生應用的設計原則,稱為 The Twelve-Factor App,其中關於配置,

By Ken Chen
關於消息的三層語義:以 RabbitMQ 為例

關於消息的三層語義:以 RabbitMQ 為例

對分散式系統來說,消息的可靠性非常重要,想想一個金融應用的場景,如果在支付時,消息遺失了,或是重複遞送了,都會造成使用者的困擾。當我們在系統中引入消息隊列時,我們同時引入了複雜度,這意思是,系統的「處理消息」跟你想的不一定是同一件事。從可靠性的角度來看,「處理消息」的語義可以分為三個層次,第一層是「最多一次」,當你請系統處理消息時,它會幫你進行,但最多一次,並且不保證是否完成;第二層是「最少一次」,系統會幫你處理消息,而且附帶必要的錯誤處理,確保消息至少被完成一次;第三層是「準確一次」,意指消息不多不少,恰恰好被準確處理並完成了一次。 當試著從語言學的角度來看待系統時,我們才能規劃出系統的整體面貌。儘管「準確處理一次」有最佳的可靠性,但因為其處理成本,降低了系統整體的吞吐量。在〈Starbucks Does Not Use Two-Phase Commit〉一文中,Gregor Hohpe 精確描繪了星巴克的異步系統。收銀員收費後,

By Ken Chen

Go

如何優雅包裝錯誤:聊聊 Go 的 error

錯誤處理是 Golang 最常被討論的一個點。這有幾個因素,首先,這跟它「錯誤是值」的設計理念有關,開發者需要在業務流程中穿插錯誤處理,違反關注點分離的原則,當然會引發爭議。另外,在 1.13 前,Golang 標準 errors 庫的表現力有限,當需求較為複雜時,需要開發者自行發明錯誤處理輔助函式。這讓人不禁好奇,Golang 的錯誤處理設計原則是什麼?有沒有比較好的實踐?或者說,我們能不能找到一種方式,優雅地處理錯誤? 錯誤與異常 先來看看不同人的觀點,Robert Martin 在討論到錯誤處理時,是如此建議的 使用異常替代返回錯誤碼,錯誤處理代碼就能從主路徑代碼中分離出來,得到簡化 他給出的例子是 try { deletePage(page); registry.deleteReference(page.name); configKeys.deleteKey(page.name.

By Ken Chen

Go

讓錯誤成為資源:gRPC 的錯誤處理模型

錯誤處理是所有 RPC 服務都要具備的設計,但是怎樣的錯誤處理模型,算是好的模型呢?從字面上來看,錯誤處理可以分解成「錯誤」跟「處理」,如果用 RESTful 的觀點,將錯誤當成是 Resource,一個好的模型應該要能匹配不同場景的 Resource,並根據場景需求來處理這些 Resource。 錯誤模型 在 RESTful 中,通常會用 HTTP Status Code 當錯誤訊息的分類(Category),錯誤內容則放在 Payload。這樣的好處是,只要看到分類,就能先進行大方向的處理,如果需要特定資訊,再從 Payload 拿取。通常錯誤內容的格式會自行定義,以支付服務 Stripe 的 API 為例,定義的格式就有 * type (string) * code (string) * decline_

By Ken Chen
在 GitLab 顯示測試覆蓋率:以 Go 為例

Go

在 GitLab 顯示測試覆蓋率:以 Go 為例

對現代開發者來講,單元測試已經不是可選,而是必備了。單元測試能保護程式碼,讓錯誤提早現形,也能讓重構時更安心。通常我們在評估單元測試的執行狀況時,會用 coverage 當成其中一項指標。當然,coverage 還是會有一些使用的場合跟侷限,當談到專案落地,可能大家會想知道的是,coverage 該怎麼使用,才能幫助到專案? 開發、審查、回顧 我們先來看看什麼時候會需要知道 coverage?通常依照團隊的工作流程,將它分為三個階段:開發、審查、回顧。每個階段關注的場景會略有不同。 一個一個講。對開發中情形,開發者想知道的是剛寫完的邏輯是否能正常運行,有沒有對應的測試,覆蓋範圍是否已經足夠,如果還有條件分支沒覆蓋到的話,是哪裡?是不是每個錯誤都有處理了。這時最需要的是,codebase 要能 highlight 剛剛講的資訊,幫助開發者一眼掌握。 當開發完成,feature branch 被提交到原始碼管理系統,例如 GitLab,會需要一名

By Ken Chen

Go

用 Fx 來替 Go 依賴注入吧

相信平常開發時,即使沒真的用到,也會聽別人提起「依賴注入」的概念。我們都知道依賴注入的目的是解耦模組間的依賴,但具體來說,依賴注入應該要怎麼進行呢?Go 對於依賴注入有什麼比較好的實踐呢?這篇就來談談 Go 相關的依賴注入話題。 常見的實踐方式 講到依賴注入,從 OOP 的觀點來看,可以回到 Martin Fowler 的 SOLID 原則,其中的 Dependency Inversion Principle 落實到編程中,就是依賴注入。James Grenning 曾經簡單扼要說明 DIP 原則的出發點 Martin tells us that high-level modules shouldn’t depend on low-level modules. 這裡,高層級的模組指的是商業邏輯的實現。依賴反轉原則之所以重要,

By Ken Chen

Go

初探 Go 的單元測試:兼談 Stub 跟 Mock

測試是程式的防護網,能確保程式符合設計,而當開發者需要對程式進行重構,以增進品質時,測試也可以確保程式不會出現改 A 壞 B 的情況。從商業角度來看,測試能降低維護與改善程式的成本,進而提高軟體開發的競爭力。 既然測試這麼好,那為什麼常看到軟體專案中沒有測試?在我的經驗中,主要原因有兩個:首先是軟體開發初期,架構還不是很穩定,API 隨時有可能改變,在 API 不穩時,如果就開始寫大量測試,會造成後面很大的維護成本,試著想想,API 的改變,可能就牽涉到測試流程跟測試資料的改變,而之前的 Corner Case 很可能都變成沒有價值的投資,在這情況下寫測試沒有意義。 再來,程式中可能會引用到第三方套件,例如 ORM 或 HTTP 之類的外部依賴,如果要實際測試,就會需要建構測試環境,而這些也會有建置與維護成本,像是網路斷掉,可能就會在程式邏輯沒動到的狀況下,讓 HTTP 的測試失效,這些維護成本會讓寫測試的投資報酬率看起來不太划算。

By Ken Chen

Go

自動生成重複代碼:使用 Go 的 Template

開發軟體時,常常會發現有些函式或方法很類似,例如對 Callback Function 來說,開發者都需要註冊回調函式,並在適當的時機,將資料交給回調函式處理,我們可以將這兩個動作,稱為 OnAction 跟 EmitAction。儘管繼承或組合能讓程式碼重複使用類似的組件,幫助開發者節省時間,但對於較複雜的情況,像是不同類(Class)的函式名稱也要不同時,仍需要仰賴開發者自行編寫。 試著想想,如果開發者僅僅寫設定檔(Config File),就有程式能根據設定檔來自動產生程式碼,不是很美好的事嗎?這是有的,在實務上,這類用於產生程式的程式被稱為 Code Generator,例如前面介紹過的 genny。Golang 有內建 generate 這個命令行工具,能幫助開發者將 Code Generator 跟編譯更密切結合在一起。 本文會用 Callback 的 Generator 當例子,講解如何開發並使用一套 Code Generator。

By Ken Chen
如何驗證使用者身分:使用 JWT

Node.js

如何驗證使用者身分:使用 JWT

驗證與授權是開發網路應用時一定會遇到的問題,前者指的是確認使用者身分,讓 Server 明白請求者是真正的使用者,而不是其他人假冒;後者指的是該使用者有權限進行操作。JWT 處理的主要是前一個問題。 先來看看 JWT 出來前的做法。在傳統技術中,使用者會在登入時輸入帳號密碼,Server 由資料庫驗證無誤後,創建一組 Session ID,放入回應的 Cookie ,Client 後續請求都會在 Cookie 帶上 Session ID,方便 Server 檢驗。 由於只看 Session ID 無法說明使用者身分正確,還需要看該 Session ID 是否有儲存在 Server,而 Server 的數據通常儲存在資料庫,一來一往之間,就會造成 Server 端額外的開銷。JWT 只需要在 Server 儲存一組

By Ken Chen
Conan:建置並管理 C/C++ 的產出物

C/C++

Conan:建置並管理 C/C++ 的產出物

以前我們討論過如何在 C/C++ 的專案中,使用 Conan 管理第三方套件。當開發應用程式時,只要用 Conan 就能引入現成的函式庫;但如果角色轉換,開發的不是應用程式,而是函式庫,為方便他人引用,就需要將函式庫打包成 Conan Package,並上傳到 Server。在常見的開發情境中,開發者既需要引用別人的函式庫,也期待自己的函式庫能讓別人引用。 本文會講解如何編寫 Conan 的 Recipe,打包 C/C++ 的函式庫,並在實際的應用程式中引用。 需要 Clone 程式碼的,可以到這裡。 Create Conan Project 既然要打包程式,就需要先建立起專案 mkdir conan-recipe && cd conan-recipe 依照官方說明,可以用 conan

By Ken Chen
用網頁製作你的簡報:reveal.js

JavaScript

用網頁製作你的簡報:reveal.js

試著想像一個情境,你是一名工程師,需要跟他人分享你的創意,你希望雙方認知建立在相同的基礎上,當說到「狗」時,聽眾明白你指的是拉不拉多,而不是吉娃娃。投影片是個簡單有效的同步工具,但 Windows 的 PowerPoint 有幾個惱人的問題: * 僅具備簡單畫面編輯工具 * 版型固定 * 沒有 Quote * 沒有 Code Section * 沒有 Syntax Highlight 投影片應該要像攝影機,講者使用它來專注說故事。而 PowerPoint 設計的目標,是讓不具備程式能力的人,也能將畫面放上電腦。它有基礎的視覺設計能力,但並不足以應付進階應用。 reveal.js 是以 HTML 為基礎的簡報框架,藉由 CSS 跟 HTML 分離,講者可以更專注在內容,將設計交由第三方庫來處理。由於本質上是由 HTML、CSS 跟

By Ken Chen

Go

Go 的泛型替代方案:型別斷言與代碼生成

用函數式編程(Functional Programming)的風格寫程式時,會常常重複使用一些通用函式,舉個例子,假設有個 array,開發者需要從該 array 中篩選符合條件的元素,重新組成新的 array。合理的情況是,如果有個 filter 函式,只需要設定條件,剩下的事情都能交給語言處理。 Golang 的 append 有點這味道,類似 JavaScript 的 push,能新增 slice 中的元素,但 Golang 畢竟不是徹底的函數式語言,內建函式庫中沒有 filter、map、reduce 等等 array 常用的函式。如果想要自行實現,語言的強型別系統會要求開發者在使用前告知對象型別,這等於是對不同的型別都要實現幾乎相同的函式,可想而知不切實際。 同樣是強型別語言,C++ 或 C# 在面對這問題時,是靠著泛型(

By Ken Chen
雙向互動的即時訊息:Websocket 入門

Node.js

雙向互動的即時訊息:Websocket 入門

在 HTTP 設計之初,網路應用主要是交換文件,因此當提交訊息或更新訊息時,需要刷新整個頁面,這也導致大量 HTML 被重複傳輸,浪費使用頻寬。後來 AJAX 被提出,讓 HTTP 可以只取得想要的伺服端訊息,同時在沒有重新導向的情況下更新頁面,讓 HTTP 更符合現代網路應用情境。 但是對需要互動的應用,像是聊天室、遊戲、即時狀態監控等等來說,如果使用 HTTP 傳遞訊息,則需要客戶端頻繁向伺服端輪詢(Polling),有點像客戶端三不五時跟伺服端問說:「你有沒有新資料需要更新的啊?」可想而知會造成客戶端跟伺服端很大的負擔。比較理想的情況是,應該存在一個事件驅動模型,當伺服端有事件發生時,它會主動通知訂閱的客戶端,客戶端再進行更新,而這就是 WebSocket 這套通訊協定誕生的原因。 Websocket 沒有限定語言,但為了簡化操作,後端可以用 node.js,好跟前端的 JavaScript 共用一套函式庫。本文中會使用

By Ken Chen

Go

資料庫版本遷移:以 Go 為例

接續前面,繼續來討論資料庫議題吧。 在商務初期,追求的是驗證市場,這時資料庫往往只有相對簡單的版本,各種欄位也還不是很齊全。隨著商業模式逐漸成熟,資料庫會需要負擔更多的營運功能,也會需要在原有表格中加入新欄位。資料庫的版本管理問題就出現了。 我們通常稱呼資料庫版本遷移為 Migration,在 Laravel 或 RoR 中都有整合好的 Migration 工具,而 Golang 目前仍需要仰賴自己動手,golang-migrate 是現在比較成熟的專案。本文會講解如何使用 golang-migrate 的 CLI 跟函式庫,來建立資料庫 Migration。 Introduction 既然說版本遷移是 Migration,為什麼不直接稱呼 Version 就好,它跟 Version 有什麼不同?兩者的區別可以看看下圖 簡單來說,Version 指的是資料庫的狀態,而 Migration 指的是狀態到狀態之間的改變。因為後端程式會使用到資料庫,如果用到資料庫沒有的欄位,就會出現問題。

By Ken Chen