iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0
Software Development

30天學會Golang系列 第 27

Day27 - Go的 jwt 解說

  • 分享至 

  • xImage
  •  

帳密加密

再講 jwt 前,需要先做一件事情,就是加密,day26 的內容中,我們時做了帳號與密碼的寫入與讀取,但是基於安全性,我們都會在存入密碼前先對密碼作加密,常見的有以下幾種加密方式:

  • Base64
  • MD5
  • SHA1
  • SHA256

那在密碼加密的部分我們這邊選用 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" ,結果如下:

https://ithelp.ithome.com.tw/upload/images/20221008/20150797HrBou8VLa7.png

這樣對於使用者的資料能有更安全的保障,那除了對使用者的密碼做加密以外,還有沒有其他方法可以增強安全性呢,答案是有的,我們可以再做更近一步的提升,就是透過 jwt,但是 jwt 是什麼呢?

jwt 主要其實就是一個避免使用者不斷重複輸入帳號密碼的一個方式,以前網站我們只要一離開,下次登入就需要重新登入,必須輸入帳號密碼,但是不斷的輸入,就有可能被身旁的人偷偷背下來,導致被盜用帳戶,或者是有惡意程式偷偷紀錄使用者輸入的文字,那這樣也是非常危險的,jwt 為此而生,他的概念如下圖所示:

https://ithelp.ithome.com.tw/upload/images/20221008/20150797ZjCpsf7oB6.png

我們照著流程解說:

  1. 使用者輸入帳號密碼登入 (這個流程還是不可少,否則無法知道是否是本人)
  2. 伺服器確認帳密後,回傳一組由三串字串所組成的訊息 (jwt)
  3. 使用者的瀏覽器會將 jwt 存在瀏覽器的內存中
  4. 接下來的 "一段時間" 內,使用者只要想拜訪該網站,瀏覽器會直接丟 jwt 給伺服器做認證

或許這裡會有個問題,所以 jwt 裡面是帳號密碼嗎?
答案是否定的,因為瀏覽器的內存是可以直接看到裡面的資訊,因此放這些敏感資訊是危險的。

但是如果不放帳號密碼,那瀏覽器要丟給伺服器什麼資料才能確認是對的資料呢?
這其實就與 jwt 的訊息組成有關了, 前面有提到 jwt 是由三串字串組合而成,分別是 header、payload 與 verify signature 所組成,並且用 "." 來區隔:

  • header 裡面裝的是加密方式
  • payload 通常會裝使用者 id 或一些常用但是不敏感的資訊,使用者 id 是由資料庫定義
  • verify signature 則是前面兩個資訊再搭配一個字定義的字串 (一般稱為 secret,伺服器端自己定義,表示只有伺服器端知道),做加密演算法而得的

其中第一串與第二串都是透過 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" ,

https://ithelp.ithome.com.tw/upload/images/20221008/20150797IAmhzipiN7.png

https://ithelp.ithome.com.tw/upload/images/20221008/20150797TcFCxHPAWc.png

但到這裡會有個問題,就算有 jwt 又如何,要怎麼做驗證還是沒概念啊?
那這邊的答案非常的優雅,因為 secret 只有伺服器端知道,而 jwt 中包含了三個訊息,前兩個只是單純的訊息,第三個是前兩個再搭配 secret 的加密訊息,我們只需要將收到的前兩個字串與 secret 做加密後,看是不是與第三個字串的訊息相同就知道這個是不是有效的 jwt!

第27天報到,jwt 真的是個很棒的想法,明天來實作一下

參考來源

  1. https://jwt.io/
  2. https://reurl.cc/NR743q
  3. https://medium.com/starbugs/how-to-store-password-in-database-sefely-6b20f48def92

代碼連結

https://github.com/luckyuho/ithome30-golang/tree/main/day27


上一篇
Day26 - Go的 gorm 資料庫處理
下一篇
Day28 - Go的 jwt 實作
系列文
30天學會Golang31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言