无密码验证:服务器 | Linux 中国

无密码验证:服务器 | Linux 中国
无密码验证可以让你只输入一个 email 而无需输入密码即可登入系统。这是一种比传统的电子邮件/密码验证方式登入更安全的方法。-- Nicolás Parada

有用的原文链接请访问文末的“

原文链接

”获得可点击的文内链接、全尺寸原图和相关文章。致谢

编译自 | http://nicolasparada.netlify.com/posts/passwordless-auth-server/

 

 作者 | Nicolás Parada



 译者 | qhwdw ?? ?? ?? ?? ?? 共计翻译:

117

 篇 贡献时间:212 天

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

下面我将为你展示,如何在 

Go

[1]

 中实现一个 HTTP API 去提供这种服务。

流程

? 用户输入他的电子邮件地址。? 服务器创建一个临时的一次性使用的代码(就像一个临时密码一样)关联到用户,然后给用户邮箱中发送一个“魔法链接”。? 用户点击魔法链接。? 服务器提取魔法链接中的代码,获取关联的用户,并且使用一个新的 JWT 重定向到客户端。? 在每次有新请求时,客户端使用 JWT 去验证用户。

必需条件

? 数据库:我们为这个服务使用了一个叫 

CockroachDB

[2]

 的 SQL 数据库。它非常像 postgres,但它是用 Go 写的。? SMTP 服务器:我们将使用一个第三方的邮件服务器去发送邮件。开发的时我们使用 

mailtrap

[3]

。Mailtrap 发送所有的邮件到它的收件箱,因此,你在测试时不需要创建多个假邮件帐户。

从 

Go 的主页

[4]

 上安装它,然后使用 go version(1.10.1 atm)命令去检查它能否正常工作。

从 

CockroachDB 的主页

[5]

 上下载它,展开它并添加到你的 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

    [6]

    :它是 CockroachDB 使用的 postgres 驱动? 

    github.com/matryer/way

    [7]

    : 路由器? 

    github.com/dgrijalva/jwt-go

    [8]

    : JWT 实现
  • go

    get

    -

    u github

    .

    com

    /

    lib

    /

    pq

  • go

    get

    -

    u github

    .

    com

    /

    matryer

    /

    way

  • go

    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

    .

    DB

  • func 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