離線授權(quán)碼設(shè)計(jì) 對(duì)于自己的軟件產(chǎn)品,希望別人付費(fèi)、或者在我們授權(quán)的情況下才允許使用。那么我們應(yīng)該如何去這個(gè)授權(quán)碼 / 許可證的機(jī)制呢?
前言 離線授權(quán)的方案無(wú)非就是兩種,一種是軟件層面的授權(quán),一種是硬件層面的授權(quán)。
軟件層面 :我們向用戶提供一串特定的字符串,用戶在軟件中輸入我們提供的字符串,在使用端校驗(yàn)用戶輸入的授權(quán)碼,即可完成軟件的授權(quán)操作。
硬件層面 :我們向用戶提供一個(gè) USB Key(加密狗),用戶在運(yùn)行軟件時(shí)我們可以通過(guò)硬件加密狗來(lái)判斷授權(quán)信息,這種加密狗在網(wǎng)上 幾塊錢到十幾塊錢就能買一個(gè),有的加密狗還內(nèi)置獨(dú)立時(shí)鐘,可以杜絕用戶進(jìn)行時(shí)鐘回?fù)艿取_@種加密狗可以用在對(duì)授權(quán)安全級(jí)別更高的場(chǎng)景中,但需要承受額外的成本:用戶的使用成本,授權(quán)分發(fā)成本(加密狗+快遞費(fèi)+快遞時(shí)間)。
軟件層面的加密方案使用起來(lái)更加靈活,硬件層面加密狗安全級(jí)別更高,本篇文章主要是探討軟件層面的授權(quán)方式。
授權(quán)碼要點(diǎn) 那么設(shè)計(jì)一個(gè)相對(duì)安全的離線授權(quán)碼,需要滿足那些哪些特征呢?
? 綁定性:每個(gè)授權(quán)碼對(duì)應(yīng)一臺(tái)設(shè)備的授權(quán),客戶無(wú)法通過(guò)一個(gè)授權(quán)碼激活多臺(tái)設(shè)備。 ? 安全性:授權(quán)碼應(yīng)難以被偽造或篡改 ? 可驗(yàn)證:授權(quán)碼應(yīng)能被系統(tǒng)快速驗(yàn)證其合法性 ? 可復(fù)現(xiàn):當(dāng)客戶授權(quán)碼遺失時(shí),我們可以根據(jù)客戶的機(jī)器碼重新生成授權(quán)碼,避免用戶無(wú)法使用軟件的情況。 ? 時(shí)效性(可選):授權(quán)碼可包含授權(quán)有效期,限定授權(quán)使用時(shí)長(zhǎng)。 ? 可擴(kuò)展(可選):可根據(jù)自己需求來(lái)靈活擴(kuò)展授權(quán)碼內(nèi)容,以方便攜帶更多信息。 你可以以任何形式生成一個(gè)滿足上方條件的授權(quán)碼,無(wú)需糾結(jié)生成的思路是否與我的思路一致
唯一標(biāo)識(shí) 在離線授權(quán)場(chǎng)景中,我們需要確定授權(quán)設(shè)備的唯一標(biāo)識(shí),這個(gè)標(biāo)識(shí)需要具有不可更改性質(zhì),在正常情況下該唯一標(biāo)識(shí)都不會(huì)發(fā)生變化(例如重啟軟件、重啟系統(tǒng)不會(huì)發(fā)生變化,但重裝系統(tǒng)、更換硬件等行為會(huì)發(fā)生變化是可接受的)。
在這種場(chǎng)景中,我們唯一標(biāo)識(shí)一般采用 CPUID、主板 BIOS UUID、網(wǎng)卡 MAC 地址、或自定義算法來(lái)生成唯一ID,提醒一下 如果產(chǎn)品是服務(wù)器應(yīng)用,不應(yīng)使用 硬盤序列號(hào)作為唯一標(biāo)識(shí)的組成部分,因?yàn)榉?wù)器硬盤都有明確的生命周期會(huì)定期更換。當(dāng)更換后就會(huì)造成授權(quán)失效的情況。
這個(gè)唯一標(biāo)識(shí)沒(méi)有明確的授權(quán)規(guī)定,大家可以根據(jù)自己的需求來(lái)選擇,也可以通過(guò)現(xiàn)成的開源庫(kù)來(lái)獲取。
例如我開發(fā)的主語(yǔ)言是 Golang,我一般會(huì)使用 github.com/denisbrodbeck/machineid 這個(gè)庫(kù)來(lái)獲取硬件ID,并且可以在各個(gè)系統(tǒng)下運(yùn)轉(zhuǎn)良好。
授權(quán)碼方案 對(duì)稱方案 對(duì)稱方案是指授權(quán)碼的生成邏輯、校驗(yàn)邏輯完全一樣。所謂校驗(yàn)就是客戶端內(nèi)部生成正確的授權(quán)碼,然后和用戶輸入的授權(quán)碼做對(duì)比,對(duì)比成功,則說(shuō)明授權(quán)有效。
以我個(gè)人習(xí)慣為例,授權(quán)碼的字符串一般包含“機(jī)器碼”、“項(xiàng)目名稱”、“到期時(shí)間”、“鹽值”,其中鹽值用于增加授權(quán)碼的復(fù)雜性,即使客戶知道了授權(quán)碼的生成方式,在不知道鹽值的情況下,無(wú)法自行偽造授權(quán)碼。
生成過(guò)程 我們先生成一個(gè)明文的字符串,然后通過(guò)特定分隔符拼接出明文的授權(quán)碼,格式可以是: 機(jī)器碼:項(xiàng)目名稱:到期時(shí)間:鹽值
,例如:
123456:tpamis:281231:abcdef
? 123456
:客戶提供的機(jī)器碼,這里以 123456 代替 ? tpamis
:授權(quán)項(xiàng)目名稱,同一個(gè)機(jī)器上可能存在多個(gè)軟件產(chǎn)品,可以加一個(gè)項(xiàng)目名稱用于區(qū)分。 ? 281231
:到期時(shí)間,這里取 2028-12-31
的后6位作為到期時(shí)間。這里也可以寫成授權(quán)天數(shù),先選定一個(gè)時(shí)間作為起始日期,然后在這個(gè)時(shí)間上疊加偏移時(shí)間,得到的最終時(shí)間作為授權(quán)到期時(shí)間。例如:我出生的時(shí)間是: 1998-11-11
,我以此作為起始時(shí)間。偏移時(shí)間:9628 天,得到授權(quán)到期時(shí)間為 2028-12-31
。 ? abcdef
:鹽值,可以防止彩虹表攻擊,即使攻擊者知道生成算法,也無(wú)法通過(guò)預(yù)計(jì)算破解授權(quán)碼。 接著,我們通過(guò)一個(gè) MD5 算法(也可使用其他 HASH 摘要算法)得到一個(gè)簽名 "cfb9fc32230fa1a19423ef8b6af63a61",此簽名就是一個(gè)授權(quán)碼。
但這時(shí)的授權(quán)碼有一個(gè)問(wèn)題,簽名是不可逆的,所以我們無(wú)法在驗(yàn)證簽名時(shí)通過(guò)簽名得到授權(quán)到期時(shí)間,所以我們需要人為的拼接一下:
? 直接拼接: cfb9fc32230fa1a19423ef8b6af63a61281231
? 使用分隔字符: cfb9fc32230fa1a19423ef8b6af63a61-281231
? 舍去后六位,以 UUID 的方式呈現(xiàn): cfb9fc32-230f-a1a1-9423-ef8b6a281231
掩藏到期時(shí)間 這個(gè)時(shí)你會(huì)發(fā)現(xiàn) 后六位明晃晃的擺在那里,太容易被人猜到是到期時(shí)間了,雖然不會(huì)被篡改,但仍希望隱藏在授權(quán)碼中,我們可以通過(guò) 十進(jìn)制轉(zhuǎn)十六進(jìn)制來(lái)實(shí)現(xiàn):281231 轉(zhuǎn)十六進(jìn)制為:44a8f,這時(shí)授權(quán)碼變成: cfb9fc32-230f-a1a1-9423-ef8b6a44a8f
。
同時(shí)你可以將到期時(shí)間藏在授權(quán)碼中間,替換掉對(duì)應(yīng)位置的簽名值,得到這樣的授權(quán)碼: cf44a8f2-230f-a1a1-9423ef8b6af63a61
。
驗(yàn)證授權(quán)碼 到這一步,已經(jīng)完成了授權(quán)碼的生成過(guò)程,客戶端程序需要提前內(nèi)置鹽值和同樣的生成算法,在用戶輸入授權(quán)碼激活時(shí),先從授權(quán)碼中截取出到期時(shí)間。再用客戶端生成的授權(quán)碼進(jìn)行對(duì)比驗(yàn)證是否正確,以及是否過(guò)期。
用戶體驗(yàn)優(yōu)化 簽名生成的授權(quán)碼以及可以滿足正常的離線授權(quán)的功能了,但有一些不允許插入U(xiǎn)盤或聯(lián)網(wǎng)的場(chǎng)景中,客戶可能需要手動(dòng)輸入授權(quán)碼,我們應(yīng)該盡可能縮短授權(quán)碼長(zhǎng)度,方便用戶輸入。
我們可以在前面授權(quán)碼的基礎(chǔ)上,參考 Windows 的授權(quán)碼長(zhǎng)度 (AAAAA-BBBBB-CCCCC-DDDD-EEEEE),每組5個(gè)字符,5 組共25個(gè)字符,通過(guò)橫杠分割方便用戶輸入。
前面 MD5 生成的簽名是 128 位的二進(jìn)制,轉(zhuǎn)換為16進(jìn)制后,長(zhǎng)度為 32 個(gè)字符。其實(shí)我們可以 MD5 轉(zhuǎn)為 36 進(jìn)制得到: CAQ3DFUC0YPEHDZLA7ZKHHYLD
。
我們?cè)倨唇由锨懊娴玫降氖M(jìn)制到期時(shí)間,并轉(zhuǎn)為全大寫,就得到了方便輸入的授權(quán)碼格式: CAQ3D-FUC0Y-PEHDZ-LA7ZK-HHYLD-44A8F
。
如果需要長(zhǎng)度和 Windows 一模一樣,則需要在 36 進(jìn)制轉(zhuǎn)換后,再截取掉 5 位,拼接時(shí)間戳。
這里提供一個(gè) Go 語(yǔ)言 十六進(jìn)制轉(zhuǎn)36進(jìn)制的方法示例:
package main import ( "crypto/md5" "encoding/hex" "fmt" "math/big" ) // 將 MD5 哈希值轉(zhuǎn)換為 36 進(jìn)制字符串 func md5ToBase36 (md5Hash string ) string { // 將 MD5 哈希值(十六進(jìn)制)轉(zhuǎn)換為大整數(shù) bigInt := new (big.Int) bigInt.SetString(md5Hash, 16 ) // 定義 36 進(jìn)制的字符集 const charset = "0123456789abcdefghijklmnopqrstuvwxyz" // 將大整數(shù)轉(zhuǎn)換為 36 進(jìn)制 base36 := "" base := big.NewInt( 36 ) zero := big.NewInt( 0 ) remainder := new (big.Int) for bigInt.Cmp(zero) > 0 { bigInt.DivMod(bigInt, base, remainder) base36 = string (charset[remainder.Int64()]) + base36 } return base36 } func main () { // 計(jì)算字符串的 MD5 哈希值 data := "123456:tpamis:281231:abcdef" hash := md5.Sum([] byte (data)) md5Hash := hex.EncodeToString(hash[:]) // 轉(zhuǎn)換為十六進(jìn)制字符串 fmt.Println( "MD5 哈希值:" , md5Hash) // 輸出: cfb9fc32230fa1a19423ef8b6af63a61 // 將 MD5 哈希值轉(zhuǎn)換為 36 進(jìn)制 base36Code := md5ToBase36(md5Hash) fmt.Println( "36 進(jìn)制編碼:" , base36Code) // 輸出: caq3dfuc0ypehdzla7zkhhyld }
幾個(gè)問(wèn)題補(bǔ)充 為什么要轉(zhuǎn) 36進(jìn)制而不是直接截取 MD5?
因?yàn)榻厝『筮^(guò)短的 MD5 增加了碰撞的概率,轉(zhuǎn)為 36進(jìn)制,會(huì)包含 0-9、A-Z 36 個(gè)字符。36進(jìn)制相比 16進(jìn)制更為緊湊,在縮短長(zhǎng)度同時(shí)保留較高的信息密度。且 36 進(jìn)制在客戶輸入時(shí),不需要考慮大小寫問(wèn)題,用戶體驗(yàn)更好。
摘要算法選擇
MD5 算法已被證實(shí)存在碰撞漏洞,但在安全性要求相對(duì)不是很高的場(chǎng)景中,還是可以接受的。如果需要安全性更高的摘要算法,可以換成例如 SHA-256、國(guó)密 SM3 等更為安全算法,在得到簽名后截取 32 位 當(dāng)作 MD5 使用,實(shí)現(xiàn)安全性、用戶體驗(yàn)的兼顧。
36進(jìn)制字符集
因?yàn)?36 進(jìn)制是自行實(shí)現(xiàn)的,所以可以自定義字符集,大家可以在這一步對(duì)字符集排布順序進(jìn)行打亂,實(shí)現(xiàn)混淆效果。
非對(duì)稱方案 前面的授權(quán)碼方案可以提供一個(gè)方便輸入,長(zhǎng)度較短的授權(quán)碼字符串,但攜帶授權(quán)信息較少、密鑰(鹽值)需要內(nèi)置在客戶端中,遇到逆行破解場(chǎng)景,有鹽值泄露的可能性。
授權(quán)碼設(shè)計(jì) 非對(duì)稱加密我們可以使用 RSA 算法來(lái)實(shí)現(xiàn)。當(dāng)我們使用 RSA 算法時(shí),我們可以通過(guò)授權(quán)碼給客戶端攜帶更多信息,所以我們就以 JSON 為許可證內(nèi)容的載體。我們可以這樣設(shè)計(jì):
{ "iss" : "tpamis" , "sub" : "pord" , "aud" : "123456" , "exp" : "1861804800" , "nbf" : "1743436800" , "iat" : "1742728673" , "rge" : [ "功能a" , "功能b" , "功能b" ] }
接參考了 JWT 官方規(guī)定的 Payload 字段,設(shè)計(jì)的許可證 JSON 內(nèi)容,且這些字段可以根據(jù)需求隨意添加調(diào)整。
? iss (issuer):授權(quán)項(xiàng)目 ? sub (subject):授權(quán)方式,prod 正式授權(quán)、test 測(cè)試授權(quán) ? aud (audience):授權(quán)對(duì)象,客戶的機(jī)器碼 ? exp (expiration time):過(guò)期時(shí)間 ? nbf (Not Before):生效時(shí)間 ? iat (Issued At):簽發(fā)時(shí)間 生成授權(quán)碼 我們生成一對(duì) RSA 公私鑰,公鑰保存在客戶端,私鑰在我們手里,我們根據(jù)用戶的機(jī)器碼,生成一個(gè)授權(quán)碼,以上面為例,我額外擴(kuò)展了授權(quán)模塊功能,可以精確控制客戶允許使用的功能范圍。
{"iss":"tpamis","sub":"pord","aud":"123456","exp":"1861804800","nbf":"1743436800","iat":"1742728673","rge":["功能a","功能b","功能b"]}
2. 然后我們使用 RSA 私鑰加密生成密文,這個(gè)密文就是我們需要提供給客戶的授權(quán)碼。 I0UQvjrw3achexYK/D2ciNbsN+d28meH56aQPvosR9ZAKX2xp+kFNMfOjgBH+ZCL5+ir0h+pibfyva5weFBEEy1WgPMSqSiFGL5jfNIpzRY+Ct8hqsrjZm20TONvEjE7gwhFHW0m0NvdpFmwvbOjQPLk5ipZkNWW2l/DvEkYyogVMxCAfcNmczv1x9c1MeyXp0ru7GQifF1q1wGn4SBljc61zfUbtsv5aHk7zibOrNu4DsXnGjnYmwRCqYogAhB7g4Wzxfx0chMED9ulakTC8G5rBwT2w+LNgxKP+Si/nsOL0PeBzwrLTYulJIQEoqNsjMkDJ4JbXa/uoWrRoIuMTg==
當(dāng)許可證內(nèi)容過(guò)多,導(dǎo)致密文過(guò)長(zhǎng)時(shí),可以考慮輸出一個(gè)密鑰文件給客戶,客戶在產(chǎn)品中上傳、選擇 密鑰文件,我們通過(guò)讀取密鑰文件來(lái)獲取授權(quán)碼。這樣體驗(yàn)更好。
授權(quán)碼驗(yàn)證 用戶將我們的授權(quán)碼輸入到軟件中,客戶端使用公鑰解密,解密成功則讀取進(jìn)入進(jìn)行進(jìn)一步的授權(quán)校驗(yàn),解密失敗直接提示授權(quán)碼錯(cuò)誤。
說(shuō)明:將公鑰內(nèi)置到客戶端,在極端情況下,存在反編譯的可能性。所以我們可以假設(shè)公鑰已經(jīng)泄露、不安全的狀態(tài)。不過(guò)只要私鑰不泄露,別人拿到公鑰也只能解密我們的許可證密文,而不能偽造一個(gè)許可證,相對(duì)來(lái)說(shuō)也是可以接受的。
代碼示例 以 Go 語(yǔ)言為例 加密解密過(guò)程如下
package main import ( "encoding/json" "fmt" "github.com/dromara/dongle" ) func main () { // 聲明 map[string]any 許可證結(jié)構(gòu) licenseData := map [ string ]any{ "iss" : "tpamis" , "sub" : "pord" , "aud" : "123456" , "exp" : "1861804800" , "nbf" : "1743436800" , "iat" : "1742728673" , "rge" : [] string { "功能a" , "功能b" , "功能b" }, } // 將 map 轉(zhuǎn)換為 JSON 字符串 jsonData, _ := json.Marshal(licenseData) // 使用RSA 私鑰 授權(quán)Json cipherText := dongle.Encrypt.FromBytes(jsonData).ByRsa(pkcs1PrivateKey).ToBase64String() fmt.Println( "RSA密文" , cipherText) // 客戶端 使用 公鑰進(jìn)行解密 licenseJson := dongle.Decrypt.FromBase64String(cipherText).ByRsa(pkcs1PublicKey).ToString() fmt.Println( "許可證Json" , licenseJson) } var pkcs1PublicKey = [] byte ( `-----BEGIN RSA PUBLIC KEY----- MIGJAoGBAK12MTd84qkCZzp4iLUj8YSUglaFMsFlv9KlIL4+Xts40PK3+wbsXPEw cujGeUmdgMeZiK7SLLSz8QeE0v7Vs+cGK4Bs4qLtMGCiO6wEuyt10KsafTyBktFn dk/+gBLr7B/b+9+HaMIIoJUdsFksdAg3cxTSpwVApe98loFNRfqDAgMBAAE= -----END RSA PUBLIC KEY-----` ) var pkcs1PrivateKey = [] byte ( `-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQCtdjE3fOKpAmc6eIi1I/GElIJWhTLBZb/SpSC+Pl7bONDyt/sG 7FzxMHLoxnlJnYDHmYiu0iy0s/EHhNL+1bPnBiuAbOKi7TBgojusBLsrddCrGn08 gZLRZ3ZP/oAS6+wf2/vfh2jCCKCVHbBZLHQIN3MU0qcFQKXvfJaBTUX6gwIDAQAB AoGAFwAfEo56t5JcAcLNzccQVVYj2jkbO820G8hNiSxYA5WLD0QaAxcAU/Lqqbb3 ii1aUB0ppJS13NgnU6nnGGdZzUYBG1Hai6EkVyCGrI4amQ93AaVdKncL8gJ4RZAm YzPPUwSMEESsu24pS1NF1G1Y8C+28b/Wr0oqOsCvL6PhsMECQQDwsPJJoWRx7ZJw E1K5KLT0cXKyrIpyXY3I6tyA5imCzOzccf3d1vDgB0L9sdSO7bG3ceSwpAeiWEbg 5jGZemPzAkEAuH6U4pEI4AMbWnatpK55Rc235NDgmT3VyIuRaKC02YXAZ+jznFep XMd4DTli4R9r3j2YVhUpyDVbdQpFH98DMQJAQpOvcU6DSkA80WOG7lCkPTlkUKgJ Y7kdDwZoF/+SW+vzWMbvQf3CgzV/Ak2+TgrRrbyDVZkJw45HjM4fyiRgoQJBALH/ /qlxgPyQQs3O/s2KQBsm1auAE5IF5MLuVUZ69sF/mBko2hEXSqHnGV645TuKU0pC Zz12ga9WO3z6gaK0SaECQQDah1pKt9ViBBy4USXK3OWXEloHuTwmyr9AbLqqI5tQ 2eNuH0NkuJYQmnXmHLbKOELoYocldEBXmkzPXSN+X9kV -----END RSA PRIVATE KEY-----` )
JWT 方案 JWT 方案則是直接使用標(biāo)準(zhǔn)的 JWT 庫(kù)來(lái)實(shí)現(xiàn)授權(quán)碼的簽發(fā)功能,客戶端和服務(wù)端共享 JWT 的密鑰來(lái)驗(yàn)證授權(quán)簽名的有效性。
本方案的優(yōu)點(diǎn)是可以充分利用現(xiàn)有的標(biāo)準(zhǔn)化 JWT 庫(kù),只需定義一個(gè)密鑰,而無(wú)需進(jìn)行過(guò)多的開發(fā),但許可證內(nèi)容會(huì)直接暴露給客戶,也可以考慮 JWE,需自行抉擇。
JWT Header { "alg" : "HS256" , "typ" : "JWT" } JWT Payload { "iss" : "tpamis" , "sub" : "pord" , "aud" : "123456" , "exp" : "1861804800" , "nbf" : "1743436800" , "iat" : "1742728673" , "rge" : [ "功能a" , "功能b" , "功能b" ] } 密鑰 dbkuaizi.com JWT Token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0cGFtaXMiLCJzdWIiOiJwb3JkIiwiYXVkIjoiMTIzNDU2IiwiZXhwIjoiMTg2MTgwNDgwMCIsIm5iZiI6IjE3NDM0MzY4MDAiLCJpYXQiOiIxNzQyNzI4NjczIiwicmdlIjpbIuWKn-iDvWEiLCLlip_og71iIiwi5Yqf6IO9YiJdfQ.izIAwMyiLxPiHrWf-FDGu3fHMvfaC7bqEh40ha8YYAA
結(jié)尾 沒(méi)有絕對(duì)的安全 任何安全領(lǐng)域的手段都是相對(duì)安全,沒(méi)有絕對(duì)的安全。再?gòu)?fù)雜的授權(quán)碼的本質(zhì)只能是 防君子不防小人 ,例如用戶可以通過(guò)逆向 得到你的鹽值、甚至直接修改你的程序邏輯跳過(guò)驗(yàn)證的邏輯,更何況我們的場(chǎng)景中只有離線校驗(yàn)的邏輯。
防止時(shí)鐘回?fù)?/span> 時(shí)鐘回?fù)苁侵福褪怯脩糁苯油ㄟ^(guò)修改系統(tǒng)時(shí)間到軟件授權(quán)到期之前,從而繼續(xù)使用軟件的目的。那么我們?cè)陔x線場(chǎng)景中如何去避免這樣的情況發(fā)生呢? 首先我需要聲明的是,離線授權(quán)場(chǎng)景中,不能徹底避免這種情況(除了獨(dú)立時(shí)鐘的加密狗),我們能做的就是通過(guò)代碼邏輯盡可能規(guī)避這種情況的發(fā)生,一般有這幾種手段:
記錄上一次運(yùn)行時(shí)間 眾所周知,時(shí)間是不會(huì)倒流的,也就是說(shuō)軟件的第二次運(yùn)行時(shí)間, 不可能小于 上一次運(yùn)行時(shí)的系統(tǒng)時(shí)間。 所以,在軟件每次運(yùn)行時(shí)我們可以先記錄當(dāng)前時(shí)間,并與上一次系統(tǒng)時(shí)間做對(duì)比,若第二次運(yùn)行時(shí)的系統(tǒng)時(shí)間小于之前記錄的時(shí)間,則認(rèn)為出現(xiàn)了系統(tǒng)時(shí)鐘回?fù)艿膯?wèn)題,直接提示用戶并終止軟件運(yùn)行。
安全的存儲(chǔ)時(shí)間標(biāo)記 那么如何保證我們存儲(chǔ)的時(shí)間沒(méi)有被篡改呢?我們可以通過(guò) (時(shí)間戳.鹽)+md5 的方式實(shí)現(xiàn): 例如:時(shí)間戳為 1717487962 鹽 是 abcdefg, 通過(guò): md5('1717487962.abcdefg') 摘要算法,獲得時(shí)間戳簽名:a06de98fc28e45bf38a9a5f27630cd03。 然后將這樣的字符串進(jìn)行存儲(chǔ):1717487962.a06de98fc28e45bf38a9a5f27630cd03,啟動(dòng)時(shí)再通過(guò)上面的邏輯進(jìn)行校驗(yàn),即可保證 記錄的時(shí)間不被修改。
如何防止用戶刪除時(shí)間標(biāo)記 可以在打包的時(shí)候,就記錄一個(gè)打包時(shí)間作為初始化時(shí)間戳,這樣即使用戶第一次運(yùn)行也必須有時(shí)間戳。 若沒(méi)讀取不到時(shí)間戳標(biāo)記,則說(shuō)明用戶人為清理了時(shí)間標(biāo)記,結(jié)束運(yùn)行。
與業(yè)務(wù)數(shù)據(jù)強(qiáng)關(guān)聯(lián) 如果你產(chǎn)品運(yùn)行過(guò)程中產(chǎn)生的業(yè)務(wù)數(shù)據(jù)很重要,也可以使用業(yè)務(wù)中的時(shí)間戳來(lái)做時(shí)鐘回?fù)苄r?yàn),例如使用最后一條訂單的創(chuàng)建時(shí)間。 若業(yè)務(wù)數(shù)據(jù)很重要,用戶總不可能為了繼續(xù)使用軟件而刪掉業(yè)務(wù)數(shù)據(jù)吧。
拋磚引玉 授權(quán)碼的設(shè)計(jì)方案有很多,這里只是整理了我用過(guò)的幾種方案,大家可以根據(jù)自己的需要對(duì)邏輯進(jìn)行調(diào)整,如果你有更好的方案,歡迎留言交流。
閱讀原文:原文鏈接
該文章在 2025/3/25 10:29:44 編輯過(guò)