无密码验证:服务器 登录更安全

开发 后端
无密码验证可以让你只输入一个 email 而无需输入密码即可登入系统。这是一种比传统的电子邮件/密码验证方式登入更安全的方法。

[[233116]]

无密码验证可以让你只输入一个 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 节点:

  1. cockroach start --insecure --host 127.0.0.1

它会输出一些内容,找到 SQL 地址行,它将显示像 postgresql://root@127.0.0.1:26257?sslmode=disable 这样的内容。稍后我们将使用它去连接到数据库。

使用如下的内容去创建一个 schema.sql 文件。

  1. DROP DATABASE IF EXISTS passwordless_demo CASCADE;
  2. CREATE DATABASE IF NOT EXISTS passwordless_demo;
  3. SET DATABASE = passwordless_demo;
  4.  
  5. CREATE TABLE IF NOT EXISTS users (
  6. id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  7. email STRING UNIQUE,
  8. username STRING UNIQUE
  9. );
  10.  
  11. CREATE TABLE IF NOT EXISTS verification_codes (
  12. id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  13. user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE,
  14. created_at TIMESTAMPTZ NOT NULL DEFAULT now()
  15. );
  16.  
  17. INSERT INTO users (email, username) VALUES
  18. ('john@passwordless.local', 'john_doe');
  19.  

这个脚本创建了一个名为 passwordless_demo 的数据库、两个名为 users 和 verification_codes 的表,以及为了稍后测试而插入的一些假用户。每个验证代码都与用户关联并保存创建时间,以用于去检查验证代码是否过期。

在另外的终端中使用 cockroach sql 命令去运行这个脚本:

  1. cat schema.sql | cockroach sql --insecure 

环境配置

需要配置两个环境变量:SMTP_USERNAME 和 SMTP_PASSWORD,你可以从你的 mailtrap 帐户中获得它们。将在我们的程序中用到它们。 

Go 依赖

我们需要下列的 Go 包:

  1. go get -u github.com/lib/pq
  2. go get -u github.com/matryer/way
  3. go get -u github.com/dgrijalva/jwt-go 

代码 

初始化函数

创建 main.go 并且通过 init 函数里的环境变量中取得一些配置来启动。

  1. var config struct {
  2. port int
  3. appURL *url.URL
  4. databaseURL string
  5. jwtKey []byte
  6. smtpAddr string
  7. smtpAuth smtp.Auth
  8. }
  9.  
  10. func init() {
  11. config.port, _ = strconv.Atoi(env("PORT", "80"))
  12. config.appURL, _ = url.Parse(env("APP_URL", "http://localhost:"+strconv.Itoa(config.port)+"/"))
  13. config.databaseURL = env("DATABASE_URL", "postgresql://root@127.0.0.1:26257/passwordless_demo?sslmode=disable")
  14. config.jwtKey = []byte(env("JWT_KEY", "super-duper-secret-key"))
  15. smtpHost := env("SMTP_HOST", "smtp.mailtrap.io")
  16. config.smtpAddr = net.JoinHostPort(smtpHost, env("SMTP_PORT", "25"))
  17. smtpUsername, ok := os.LookupEnv("SMTP_USERNAME")
  18. if !ok {
  19. log.Fatalln("could not find SMTP_USERNAME on environment variables")
  20. }
  21. smtpPassword, ok := os.LookupEnv("SMTP_PASSWORD")
  22. if !ok {
  23. log.Fatalln("could not find SMTP_PASSWORD on environment variables")
  24. }
  25. config.smtpAuth = smtp.PlainAuth("", smtpUsername, smtpPassword, smtpHost)
  26. }
  27.  
  28. func env(key, fallbackValue string) string {
  29. v, ok := os.LookupEnv(key)
  30. if !ok {
  31. return fallbackValue
  32. }
  33. return v
  34. }
  35.  
  • appURL 将去构建我们的 “魔法链接”。
  • port 将要启动的 HTTP 服务器。
  • databaseURL 是 CockroachDB 地址,我添加 /passwordless_demo 前面的数据库地址去表示数据库名字。
  • jwtKey 用于签名 JWT。
  • smtpAddr 是 SMTP_HOST + SMTP_PORT 的联合;我们将使用它去发送邮件。
  • smtpUsername 和 smtpPassword 是两个必需的变量。
  • smtpAuth 也是用于发送邮件。

env 函数允许我们去获得环境变量,不存在时返回一个回退值。 

主函数

  1. var db *sql.DB
  2.  
  3. func main() {
  4. var err error
  5. if db, err = sql.Open("postgres", config.databaseURL); err != nil {
  6. log.Fatalf("could not open database connection: %v\n", err)
  7. }
  8. defer db.Close()
  9. if err = db.Ping(); err != nil {
  10. log.Fatalf("could not ping to database: %v\n", err)
  11. }
  12.  
  13. router := way.NewRouter()
  14. router.HandleFunc("POST", "/api/users", jsonRequired(createUser))
  15. router.HandleFunc("POST", "/api/passwordless/start", jsonRequired(passwordlessStart))
  16. router.HandleFunc("GET", "/api/passwordless/verify_redirect", passwordlessVerifyRedirect)
  17. router.Handle("GET", "/api/auth_user", authRequired(getAuthUser))
  18.  
  19. addr := fmt.Sprintf(":%d", config.port)
  20. log.Printf("starting server at %s  
责任编辑:庞桂玉 来源: Linux中国
相关推荐

2013-05-29 14:27:40

2019-04-30 10:27:46

无服务器云计算安全

2016-03-17 10:20:57

2021-03-26 14:30:54

安全服务器架构的安全

2010-05-18 10:32:14

IIS服务器

2018-02-24 10:15:36

无服务器容器云计算

2009-03-04 06:37:00

2018-04-24 07:35:51

2020-03-25 11:06:46

无服务器开源安全工具

2019-09-20 15:05:23

软件数据库硬件

2010-05-19 15:00:37

IIS服务器

2019-07-09 10:57:04

云计算无服务器计算开发

2009-07-22 18:55:29

2022-01-05 09:28:31

无服务器计算服务器应用程序

2018-01-23 08:07:06

2010-05-13 18:09:41

2010-04-22 15:24:36

邮件安全网络加密服务器

2017-11-23 10:29:55

2023-01-12 07:57:26

2009-02-27 15:15:00

点赞
收藏

51CTO技术栈公众号