黃驊市海邊深圳優(yōu)化排名公司
上篇文章詳細(xì)講解了一次性密碼 OTP 相關(guān)的知識(shí),基于時(shí)間的一次性密碼 TOTP 是 OTP 的一種實(shí)現(xiàn)方式。這種方法的優(yōu)點(diǎn)是不依賴(lài)網(wǎng)絡(luò),因此即使在沒(méi)有網(wǎng)絡(luò)的情況下,用戶(hù)也可以生成密碼。所以這種方式被許多流行的網(wǎng)站使用到雙因子或多因子認(rèn)證中,包括 Google、GitHub、Facebook 和 Salesforce 等等。
因?yàn)?TOTP 是標(biāo)準(zhǔn)化的協(xié)議并且被廣泛采用,所以有很多對(duì)應(yīng)的移動(dòng)應(yīng)用或者 web 應(yīng)用實(shí)現(xiàn),被稱(chēng)為身份驗(yàn)證器應(yīng)用,例如 Google Authenticator、Microsoft Authenticator 等。Golang 也有很多優(yōu)秀的三方庫(kù)可以幫助我們快速實(shí)現(xiàn)?TOTP 的服務(wù)端實(shí)現(xiàn),其中比較有代表性的是?pquerna/otp 庫(kù),接下來(lái)就使用這個(gè)庫(kù)來(lái)演示一下 TOTP 的服務(wù)端實(shí)現(xiàn)流程。
為用戶(hù)生成?TOTP Key
用戶(hù)開(kāi)啟雙因子認(rèn)證時(shí),為用戶(hù)生成 TOTP Key,用于生成 TOTP 密碼。將這個(gè)密碼保存在數(shù)據(jù)庫(kù)或者秘鑰管理系統(tǒng)中,生成 key 的關(guān)鍵代碼如下:
key, err := totp.Generate(totp.GenerateOpts{Issuer: "Github",AccountName: "user@example.com",Period: 30,Digits: otp.DigitsSix,Algorithm: otp.AlgorithmSHA1,})
這幾個(gè)參數(shù)的意思如下:
- Issuer 意思是應(yīng)用名稱(chēng),例如 Github。
- AccountName 意思要給哪個(gè)用戶(hù)生成 key。
- Period 意思是 TOTP 密碼的有效時(shí)間,也是不同 TOTP 密碼的生成時(shí)間間隔,一般為 30 秒。
- Digits 意思是生成的密碼長(zhǎng)度,一般為 6 位。
- Algorithm,用于 HMAC 簽名的算法,默認(rèn)是 SHA1。
把密鑰和密碼生成規(guī)則分享給用戶(hù)
通常是將秘鑰和密碼規(guī)則信息以二維碼的形式展示給用戶(hù),用戶(hù)使用身份驗(yàn)證器應(yīng)用掃描二維碼保存相關(guān)信息并且生成密碼。二維碼中的內(nèi)容格式一般如下:
otpauth://totp/Github:user@example.com?algorithm=SHA1&digits=6&issuer=Github&period=30&secret=5RLOAFJOB6LRV7WOKFIMDZ5IESZ7L3JM
為用戶(hù)提供“恢復(fù)碼” Recovery Codes
生成“恢復(fù)碼” Recovery Codes (使用隨機(jī)生成的字符串即可)存儲(chǔ)到數(shù)據(jù)庫(kù)或者秘鑰管理系統(tǒng)中。當(dāng)用戶(hù)不能訪問(wèn)自己的 TOTP 設(shè)備(例如將 TOTP 應(yīng)用中的 TOTP 秘鑰刪除了、將 TOTP 應(yīng)用卸載了、手機(jī)丟失了等)時(shí),就無(wú)法登錄自己的帳戶(hù)了。因?yàn)檫@種情況比較常見(jiàn),所以很多網(wǎng)站都會(huì)給用戶(hù)提供“備份代碼”或“恢復(fù)代碼”,并且每個(gè)只能使用一次,可以臨時(shí)用來(lái)代替 TOTP 密碼。
校驗(yàn)用戶(hù)輸入的 TOTP 密碼
用戶(hù)再次登錄后,觸發(fā)雙因子認(rèn)證,要求用戶(hù)輸入 TOTP 密碼,服務(wù)端檢驗(yàn)這個(gè)密碼。校驗(yàn)的關(guān)鍵代碼如下:
// 驗(yàn)證一次性密碼
isValid := totp.Validate(passcode, key.Secret())
模擬生成密鑰、校驗(yàn)密碼的代碼
package mainimport ("fmt""time""github.com/pquerna/otp""github.com/pquerna/otp/totp"
)func main() {// 生成密鑰key, err := totp.Generate(totp.GenerateOpts{Issuer: "Github",AccountName: "user@example.com",Period: 30,Digits: otp.DigitsSix,Algorithm: otp.AlgorithmSHA1,})if err != nil {panic(err)}fmt.Println("Secret URL: ", key.URL())// 模擬生成一個(gè)一次性密碼now := time.Now()passcode, err := totp.GenerateCode(key.Secret(), now)if err != nil {panic(err)}// 驗(yàn)證一次性密碼valid := totp.Validate(passcode, key.Secret())if valid {fmt.Println("Valid passcode!")} else {fmt.Println("Invalid passcode!")}
}