美文网首页
【Go Web开发】发送用户激活tokens

【Go Web开发】发送用户激活tokens

作者: Go语言由浅入深 | 来源:发表于2022-03-18 23:37 被阅读0次

    上一篇文章我们实现了用户激活token创建,接下来是将激活token添加到用户注册处理程序registerUSerHandler中,这样在用户注册时生成一个激活token然后添加到欢迎邮件中发送给用户确认,类似如下:

    Hi,
    Thanks for signing up for a Greenlight account. We're excited to have you on board!
    
    For future reference, your user ID number is 123.
    
    Please send a request to the `PUT /v1/users/activated` endpoint with the following JSON body to activate your account:
    
    {"token": "Y3QMGX3PJ3WLRL2YRTQGQ6KRHU"}
    
    Please note that this is a one-time use token and it will expire in 3 days. 
    
    Thanks,
    
    The Greenlight Team
    

    邮件中最重要的部分是我们提示用户通过调用PUT请求到API服务激活账号,而不是点击一个包含token的链接。

    让用户点击链接通过GET请求当然很方便,但对于API服务来说优缺点,尤其是:

    • 这违反了HTTP的原则,GET方法只能用于检索资源的“安全”请求,而不能用于修改某些内容的请求(比如用户的激活状态)。
    • 用户的网络浏览器或反病毒软件可能会在后台预取链接URL,无意中激活帐户。Stack Overflow很好地解释了这种风险:

      这可能会导致这样一种情况:一个恶意的用户(Eve)想要使用别人的邮箱(Alice)创建一个帐户。Eve注册了,Alice收到了一封邮件。Alice打开邮件,因为她对一个她没请求的帐户感到好奇。她的浏览器(或反病毒软件)在后台请求URL,无意中激活了帐户。

    最重要的是,你要确保任何改变应用程序状态的操作(包括激活用户)都只通过POST、PUT、PATCH或DELETE请求执行——而不是通过GET请求。

    注意:如果你的API是一个网站的后端,那么你可以调整这封邮件,要求用户点击一个链接,把用户带到你的网站上的一个页面。然后,用户可以单击页面上的一个按钮来“确认激活”,这将对您的API服务执行PUT请求来激活用户。我们将在下一章中更详细地讨论这个模式。

    但现在,我们先更新欢迎电子邮件模板,包括激活token,如下所示:

    {{define "subject"}} Welcome to Greenlight!{{end}}
    
    {{define "plainBody"}}
    Hi,
    
    Thanks for singing up for a Greenlight account. We're excited to have you on board!
    
    for future reference, your use ID number is {{.userID}}.
    
    please send a request to the `PUT /v1/users/activated` endpoint with flowing JSON
    
    body to activate your account
    
    {"token": "{{.activationToken}}"}
    
    please note that this is a one-time use token and ti will expire in 3 days
    
    Thanks,
    
    The Greenlight Team
    {{end}}
    
    {{define "htmlBody"}}
    
    <!doctype html>
    <html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <p>Hi,</p>
        <p>Thanks for signing up for a Greenlight account. We're excited to have you on board!</p>
         <p>For future reference, your user ID number is {{.userID}}.</p>
          <p>Please send a request to the <code>PUT /v1/users/activated</code> endpoint with the
          fllowing JSON body to activate your account:</p>
          <pre><code>
          {"token": {{.activationToken}}}
          </code></pre>
          <p>Please note that this is a one-time use token and it will expire in 3 days.</p>
        <p>Thanks,</p>
        <p>The Greenlight Team</p>
    </body>
    
    </html>
    {{end}}
    

    接下来需要更新registerUserHandler处理程序生成新的激活token并以动态数据写入到邮件模版中包括用户ID。如下所示:

    File: cmd/api/user.go


    package main
    
    ...
    
    func (app *application) registerUserHandler(w http.ResponseWriter, r *http.Request) {
    
            ...
    
            //插入用户信息到数据库
        err = app.models.Users.Insert(user)
        if err != nil {
            switch {
            //如果错误是ErrDuplicateEmail,使用v.AddError()方法手动添加校验错误信息
            case errors.Is(err, data.ErrDuplicateEmail):
                v.AddError("email", "a user with this email address already exists")
                app.failedValidationResponse(w, r, v.Errors)
            default:
                app.serverErrorResponse(w, r, err)
            }
            return
        }
        //用户数据插入表之后,为用户生成新的激活token
        token, err := app.models.Tokens.New(user.ID, 3 * 24 * time.Hour, data.ScopeActivation)
        if err != nil {
            app.serverErrorResponse(w, r, err)
            return
        }
    
        //使用background创建goroutine异步发送邮件
        app.background(func() {
            //现在要传入多个数据到邮件模版,我们创建一个map
            data := map[string]interface{}{
                "activationToken": token.Plaintext,
                "userID": user.ID,
            }
            // 发送欢迎邮件,并传入map作为动态数据
            err = app.mailer.Send(user.Email, "/user_welcome.tmpl", data)
            if err != nil {
                app.logger.Error(err, nil)
            }
        })
    
        //将返回码改为202,表示客户端请求被接受,但处理没有完成。
        err = app.writeJSON(w, http.StatusAccepted, envelope{"user": user}, nil)
        if err != nil {
            app.serverErrorResponse(w, r, err)
        }
    }
    

    下面我们来看看是功能否正常。重启服务,使用faith@example.com邮箱注册一个新的用户。

    $ BODY='{"name": "Faith Smith", "email": "faith@example.com", "password": "pa55word"}'
    $  curl -d "$BODY" localhost:4000/v1/users
    {
            "user": {
                    "id": 1,
                    "create_at": "2022-01-03T14:10:11+08:00",
                    "name": "Faith Smith",
                    "email": "faith@example.com",
                    "activated": false
            }
    }
    

    如果你查看Mailtrap收件箱,将看到新的欢迎邮件包含激活token,如下所示:


    在上图中可以看到向邮箱faith@example.com发送了激活token为:7IYVRNWW2W3DXUM3S7Q3OVRAUU。如果你跟随本本系列文章操作,你的token应该是和我这里不同的26个字符串。出于兴趣,我们快速查看一下PostgreSQL数据库中的token表。同样,在你的数据库中存储的确切值是不同的,但它应该看起来像这样:

    $ psql $GREENLIGHT_DB_DSN                             
    psql (13.4)
    Type "help" for help.
    
    greenlight=> select * from tokens;
                                    hash                                | user_id |         expiry         |   scope  
    --------------------------------------------------------------------+---------+------------------------+------------
     \x9290066d763e5a0b0b702273b64d36c12da0daef491807b781576dbff2d1a8c1 |       1 | 2022-01-06 14:10:11+08 | activation
    (1 row)
    

    我们看到这里存储的是激活token的哈希值:

    9290066d763e5a0b0b702273b64d36c12da0daef491807b781576dbff2d1a8c1
    

    正如前面提到的,psql总是将bytea列中的值显示为十六进制编码的字符串。因此,我们在这里看到的是明文7IYVRNWW2W3DXUM3S7Q3OVRAUU的SHA-256哈希值的十六进制编码,我们在欢迎邮件中发送了这个token。

    注意:你可以在线验证激活token(7IYVRNWW2W3DXUM3S7Q3OVRAUU)的SHA-256的哈希值,和数据库中存储的16进制字符串是相匹配的。

    注意邮件中的user_id是1(这里因为之前数据已删除),过期时间正确设置为3天之后,scope只为activation。

    附加内容

    生成tokens接口

    您可能还想提供一个独立的接口来生成并向用户发送激活token。如果你需要重新发送激活token,例如当用户在3天的时间限制内没有激活他们的帐户,或他们从来没有收到欢迎电子邮件,生成token接口是有用的。

    实现token生成接口模式和其他接口是差不多的,因此就不在这重复。

    相关文章

      网友评论

          本文标题:【Go Web开发】发送用户激活tokens

          本文链接:https://www.haomeiwen.com/subject/owawdrtx.html