再講 jwt 前,需要先做一件事情,就是加密,day26 的內容中,我們時做了帳號與密碼的寫入與讀取,但是基於安全性,我們都會在存入密碼前先對密碼作加密,常見的有以下幾種加密方式:
那在密碼加密的部分我們這邊選用 SHA1,Base64 與 SHA1 的差別主要差在 BASE64 是可以直接反推回原字串 SHA1 則相對困難,需要透過暴力破解才能得到,那我們修改一下昨天的程式碼,修改的部分在 app/controllers/user/handler.go 這個檔案,程式碼如下:
// package user
// import (
// "crypto/sha1"
// "fmt"
// User "it/day27/app/models/user"
// )
// // 註冊使用者
// // 這邊可以重複註冊,如果不希望有重複的 email 出現,可在資料庫中設定 email 屬性為 unique
// func RegisterUser(email, password string) bool {
passwordSha1 := sha1It(password)
err := User.CreateUser(email, passwordSha1)
// return err == nil
// }
// // 使用者登入
// // 如果資料庫中沒有找到對應的使用者帳密,回傳 err = record not found,有找到則 err = nil
// func LoginUser(email, password string) bool {
passwordSha1 := sha1It(password)
err := User.LoginUser(email, passwordSha1)
// return err == nil
// }
// 加密字串
const EncryptCode = "it"
// sha加密
func sha1It(password string) string {
h := sha1.New()
h.Write([]byte(password))
bs := h.Sum([]byte(EncryptCode))
encryptCode := fmt.Sprintf("%x", bs)
return encryptCode
}
就可以看到原本昨天使用者的密碼從 "a" 變成 "697486f7e437faa5a7fce15d1ddcb9eaeaea377667b8" ,結果如下:
這樣對於使用者的資料能有更安全的保障,那除了對使用者的密碼做加密以外,還有沒有其他方法可以增強安全性呢,答案是有的,我們可以再做更近一步的提升,就是透過 jwt,但是 jwt 是什麼呢?
jwt 主要其實就是一個避免使用者不斷重複輸入帳號密碼的一個方式,以前網站我們只要一離開,下次登入就需要重新登入,必須輸入帳號密碼,但是不斷的輸入,就有可能被身旁的人偷偷背下來,導致被盜用帳戶,或者是有惡意程式偷偷紀錄使用者輸入的文字,那這樣也是非常危險的,jwt 為此而生,他的概念如下圖所示:
我們照著流程解說:
或許這裡會有個問題,所以 jwt 裡面是帳號密碼嗎?
答案是否定的,因為瀏覽器的內存是可以直接看到裡面的資訊,因此放這些敏感資訊是危險的。
但是如果不放帳號密碼,那瀏覽器要丟給伺服器什麼資料才能確認是對的資料呢?
這其實就與 jwt 的訊息組成有關了, 前面有提到 jwt 是由三串字串組合而成,分別是 header、payload 與 verify signature 所組成,並且用 "." 來區隔:
其中第一串與第二串都是透過 base64 進行編碼,第三串則是透過定義的加密方式來加密前兩串與 secret,用個例子來說這個概念:
header: "sha256"
payload: "id: 1"
secret: "it"
verify signature: sha256( base64("sha256") + "." + base64("id: 1") + "." + "it")
or
verify signature: sha256( base64("sha256") + "." + base64("id: 1") + "." + base64("it"))
可以到這個 jwt 網站玩玩看: https://jwt.io/
下面有兩張圖,分別是無 secret 與有 secret 的差別,可以看到前面兩串都是相同的,但第三個字串有變化,就是因為加入了 secret: "it" ,
但到這裡會有個問題,就算有 jwt 又如何,要怎麼做驗證還是沒概念啊?
那這邊的答案非常的優雅,因為 secret 只有伺服器端知道,而 jwt 中包含了三個訊息,前兩個只是單純的訊息,第三個是前兩個再搭配 secret 的加密訊息,我們只需要將收到的前兩個字串與 secret 做加密後,看是不是與第三個字串的訊息相同就知道這個是不是有效的 jwt!
https://github.com/luckyuho/ithome30-golang/tree/main/day27