iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0
Software Development

重新認識主幹開發(Trunk-Based Development)系列 第 21 篇

Day 21:抽象分支:資料與介面相容

  • 分享至 

  • xImage
  •  

回顧先前在切換寄信實作的案例中,當需要切換回原本的立即寄送,只會改變後續通知的寄送方式。但先前已排入佇列的任務仍然存在,不會因為切換實作就消失,因此在做還原設計的時候就必須要考慮這個問題。

如果改版會改變保存的資料格式,同樣要確認切換後的程式是否能繼續處理,不能只看程式是否能換回去。

Nathan 團隊接著因應 Sharon 提出的安全要求,準備將密碼雜湊演算法從 bcrypt 改為 Argon2id,團隊評估後決定也使用抽象分支逐步完成替換。

原本的密碼處理方式

網站原本有一個 PasswordService 類別,負責驗證與設定密碼。團隊對於資安也是有概念的,僅在資料庫儲存了 bcrypt 演算法的雜湊。

登入程式會呼叫它的 verify(),傳入使用者輸入的密碼。PasswordService 讀取該帳號的密碼雜湊,驗證密碼是否相符。註冊與修改密碼時,則呼叫 reset(),傳入新密碼。這個方法使用 bcrypt 產生雜湊,再存入帳號資料。

這兩個方法是團隊的程式提供的操作。密碼驗證與儲存的邏輯全都集中在 PasswordService,呼叫它的功能不需要自行處理雜湊。

新舊雜湊會同時存在

改用 Argon2id 的問題在於,無法從既有的 bcrypt 雜湊反推出原始密碼,再用它產生 Argon2id 雜湊。因此,在這次逐步替換的過程中,必定會有新舊資料同時存在的狀況。

把 reset() 改為使用 Argon2id 後,只有新註冊或修改密碼的帳號會保存新格式。新註冊的使用者沒有問題,但既有使用者如果沒有修改密碼的話,資料仍然會是 bcrypt 的格式。

Mandy 提出建議:要更新這些帳號,可以利用使用者登入的時機。使用者登入會輸入密碼,當確認密碼正確後,就可以利用這次輸入的密碼產生 Argon2id 雜湊,來取代原本的 bcrypt 雜湊。

因此,過渡期間的 verify() 需要能驗證兩種格式,並在驗證成功後,判斷是否需要更新舊雜湊。仍保存 bcrypt 雜湊的帳號,也能正常驗證密碼;已更新的帳號則能在下一次登入時,以新格式驗證密碼。

保留共同的密碼操作

團隊可以應用抽象分支的做法:從 PasswordService 抽出 Password 介面,保留 verify() 與 reset() 這兩個操作。先讓原本類別實作介面,再把呼叫端改成透過依賴注入取得密碼物件。

調整依賴時保留原本行為,並分步驗證與整合的做法,已在抽象分支的依賴注入中說明,本篇文章省略此細節。

接著新增 PasswordMigration,同樣實作 Password 介面,負責過渡期間的密碼處理。兩個實作提供的操作如下:

操作 原本實作 過渡實作啟用升級後
verify() 驗證 bcrypt 密碼。 驗證密碼;若通過且仍是 bcrypt,就更新為 Argon2id,再回傳結果。
reset() 產生並保存 bcrypt 雜湊。 產生並保存 Argon2id 雜湊。

登入程式仍然呼叫 verify() 取得驗證結果,註冊與修改密碼的程式仍然呼叫 reset() 設定密碼。至於帳號保存的是 bcrypt 還是 Argon2id,以及是否需要更新雜湊,都由 PasswordMigration 判斷與處理,這些功能不必知道密碼的格式為何。

驗證成功後更新雜湊

程式架構如下。原本處理 bcrypt 的程式整理成 BcryptHash,另外新增 Argon2idHash,兩者都實作 Password 介面的 verify() 與 reset()。PasswordMigration 也實作相同介面,並使用這兩個類別完成密碼驗證與雜湊儲存。

https://ithelp.ithome.com.tw/upload/images/20261005/201025626jUEm3FQCo.png

下圖以已啟用升級、帳號仍保存 bcrypt,且驗證與儲存都成功的情況為例:

https://ithelp.ithome.com.tw/upload/images/20261005/201025629t0N5ogouB.png

如果帳號已保存 Argon2id,就交給 Argon2idHash 的 verify() 驗證,不需要再做這次格式轉換。密碼不符時就回傳驗證失敗,不更新資料。

確認相容後再切換寫入

PasswordMigration 可以先整合到主幹,正式功能繼續使用原本實作。團隊先驗證新實作與登入、註冊及修改密碼的功能是否能一起正常運作;驗證失敗時,先修正再切換。

這次切換還要考慮資料相容。如果某台主機已寫入 Argon2id,但其他主機的程式或環境尚未支援,使用者下次登入就可能失敗。因此,要先確認所有相關程式與環境都能處理新舊格式,再開始更新資料。

團隊可以透過設定,分開控制新密碼的寫入格式與登入後的升級步驟。先讓 PasswordMigration 支援兩種格式的驗證,reset() 仍交給 BcryptHash,並停用升級。完成驗證後,將 Password 改為綁定 PasswordMigration,逐步部署。

等所有相關程式與環境都已準備好,再讓 reset() 改用 Argon2idHash,並啟用登入後的升級。這樣就能先切換實作,再開始改變資料:

階段 驗證密碼 設定密碼 驗證成功後的處理
相容準備 支援 bcrypt、Argon2id。 仍寫入 bcrypt。 不更新雜湊。
啟用升級 繼續支援兩種格式。 改寫入 Argon2id。 將 bcrypt 更新為 Argon2id。

還原程式與清理過渡實作

開始更新資料後,切回原本實作不會把資料一起還原。如果升級步驟發生問題,可以先停用升級,保留 PasswordMigration 處理新舊格式,新設定的密碼仍使用 Argon2id。

若需要還原程式版本,就要在開始更新資料前,先準備並驗證供還原的版本與設定,確認它能處理兩種格式並繼續寫入新格式,不能只把綁定改回原本實作。

清理的時機則取決於舊資料是否還需要被處理。只要仍有帳號需要用 bcrypt 驗證,例如尚未再次登入的帳號,就要保留 PasswordMigration 與 BcryptHash。

等到沒有帳號需要 bcrypt,也沒有程式再寫入舊格式,就可以將 Password 改為綁定 Argon2idHash。驗證功能正常,並確認過渡類別不再被使用,也不需要用於還原後,再移除 PasswordMigration、BcryptHash、相關設定與專用測試。若仍保留最初的 PasswordService,也依相同條件清理。

Password 介面與登入、註冊及修改密碼的功能測試繼續保留。從加入過渡實作到最後移除它,呼叫端都使用相同的操作,不必跟著資料轉換的進度修改。

小結

抽象分支可以讓呼叫端維持原本的操作,逐步替換內部實作。但資料不會隨著程式切換一起更新,新舊格式並存時,仍需要能處理它們的過渡程式。

密碼案例將格式轉換留在共同的密碼操作裡,先確認相容,再開始更新資料。資料轉換完成後,才能清理不再需要的判斷與實作。

參考資料


上一篇
Day 20:抽象分支中的抽象
下一篇
Day 22:調整功能發布順序
系列文
重新認識主幹開發(Trunk-Based Development) 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言