無密碼驗證可以讓你只輸入一個 email 而無需輸入密碼即可登入系統。這是一種比傳統的電子郵件/密碼驗證方式登入更安全的方法。
下面我將為你展示,如何在 Go 中實現一個 HTTP API 去提供這種服務。
流程
- 使用者輸入他的電子郵件地址。
- 伺服器建立一個臨時的一次性使用的代碼(就像一個臨時密碼一樣)關聯到使用者,然後給使用者郵箱中發送一個“魔法連結”。
- 使用者點擊魔法連結。
- 伺服器提取魔法連結中的代碼,擷取關聯的使用者,並且使用一個新的 JWT 重新導向到用戶端。
- 在每次有新請求時,用戶端使用 JWT 去驗證使用者。
必需條件
- 資料庫:我們為這個服務使用了一個叫 CockroachDB 的 SQL 資料庫。它非常像 postgres,但它是用 Go 寫的。
- SMTP 伺服器:我們將使用一個第三方的郵件伺服器去發送郵件。開發的時我們使用 mailtrap。Mailtrap 發送所有的郵件到它的收件匣,因此,你在測試時不需要建立多個假郵件帳戶。
從 Go 的首頁 上安裝它,然後使用 go version
(1.10.1 atm)命令去檢查它能否正常工作。
從 CockroachDB 的首頁 上下載它,展開它並添加到你的 PATH
變數中。使用 cockroach version
(2.0 atm)命令檢查它能否正常工作。
資料庫模式
現在,我們在 GOPATH
目錄下為這個項目建立一個目錄,然後使用 cockroach start
啟動一個新的 CockroachDB 節點:
cockroach start --insecure --host 127.0.0.1
它會輸出一些內容,找到 SQL 地址行,它將顯示像 postgresql://root@127.0.0.1:26257?sslmode=disable
這樣的內容。稍後我們將使用它去串連到資料庫。
使用如下的內容去建立一個 schema.sql
檔案。
DROP DATABASE IF EXISTS passwordless_demo CASCADE;CREATE DATABASE IF NOT EXISTS passwordless_demo;SET DATABASE = passwordless_demo;CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email STRING UNIQUE, username STRING UNIQUE);CREATE TABLE IF NOT EXISTS verification_codes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL DEFAULT now());INSERT INTO users (email, username) VALUES ('john@passwordless.local', 'john_doe');
這個指令碼建立了一個名為 passwordless_demo
的資料庫、兩個名為 users
和 verification_codes
的表,以及為了稍後測試而插入的一些假使用者。每個驗證代碼都與使用者關聯並儲存建立時間,以用於去檢查驗證代碼是否到期。
在另外的終端中使用 cockroach sql
命令去運行這個指令碼:
cat schema.sql | cockroach sql --insecure
環境配置
需要配置兩個環境變數:SMTP_USERNAME
和 SMTP_PASSWORD
,你可以從你的 mailtrap 帳戶中獲得它們。將在我們的程式中用到它們。
Go 依賴
我們需要下列的 Go 包:
- github.com/lib/pq:它是 CockroachDB 使用的 postgres 驅動
- github.com/matryer/way: 路由器
- github.com/dgrijalva/jwt-go: JWT 實現
go get -u github.com/lib/pqgo get -u github.com/matryer/waygo get -u github.com/dgrijalva/jwt-go
代碼
初始化函數
建立 main.go
並且通過 init
函數裡的環境變數中取得一些配置來啟動。
var config struct { port int appURL *url.URL databaseURL string jwtKey []byte smtpAddr string smtpAuth smtp.Auth}func init() { config.port, _ = strconv.Atoi(env("PORT", "80")) config.appURL, _ = url.Parse(env("APP_URL", "http://localhost:"+strconv.Itoa(config.port)+"/")) config.databaseURL = env("DATABASE_URL", "postgresql://root@127.0.0.1:26257/passwordless_demo?sslmode=disable") config.jwtKey = []byte(env("JWT_KEY", "super-duper-secret-key")) smtpHost := env("SMTP_HOST", "smtp.mailtrap.io") config.smtpAddr = net.JoinHostPort(smtpHost, env("SMTP_PORT", "25")) smtpUsername, ok := os.LookupEnv("SMTP_USERNAME") if !ok { log.Fatalln("could not find SMTP_USERNAME on environment variables") } smtpPassword, ok := os.LookupEnv("SMTP_PASSWORD") if !ok { log.Fatalln("could not find SMTP_PASSWORD on environment variables") } config.smtpAuth = smtp.PlainAuth("", smtpUsername, smtpPassword, smtpHost)}func env(key, fallbackValue string) string { v, ok := os.LookupEnv(key) if !ok { return fallbackValue } return v}
appURL
將去構建我們的 “魔法連結”。
port
將要啟動的 HTTP 伺服器。
databaseURL
是 CockroachDB 地址,我添加 /passwordless_demo
前面的資料庫地址去表示資料庫名字。
jwtKey
用於簽名 JWT。
smtpAddr
是 SMTP_HOST
+ SMTP_PORT
的聯合;我們將使用它去發送郵件。
smtpUsername
和 smtpPassword
是兩個必需的變數。
smtpAuth
也是用於發送郵件。
env
函數允許我們去獲得環境變數,不存在時返回一個回退值。
主函數
var db *sql.DBfunc main() { var err error if db, err = sql.Open("postgres", config.databaseURL); err != nil { log.Fatalf("could not open database connection: %v\n", err) } defer db.Close() if err = db.Ping(); err != nil { log.Fatalf("could not ping to database: %v\n", err) } router := way.NewRouter() router.HandleFunc("POST", "/api/users", jsonRequired(createUser)) router.HandleFunc("POST", "/api/passwordless/start", jsonRequired(passwordlessStart)) router.HandleFunc("GET", "/api/passwordless/verify_redirect", passwordlessVerifyRedirect) router.Handle("GET", "/api/auth_user", authRequired(getAuthUser)) addr := fmt.Sprintf(":%d", config.port) log.Printf("starting server at %s
編譯自:https://nicolasparada.netlify.com/posts/passwordless-auth-server/ 作者: Nicolás Parada
原創:LCTT https://linux.cn/article-9748-1.html 譯者: qhwdw
本文由 LCTT 原創翻譯,Linux中國首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 LCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽 :D