如何通过登录将会话数据存储在 chi 路由器上下文中

How to store session data in a chi router context from login

在 Golang 应用程序中,我使用 gorilla/sessions 和 mySQL 后端来在会话中存储数据,但我想将数据存储在 chi 路由器上下文中。如何将身份验证令牌字符串或结构添加到上下文中?我看到很多示例解释如何 read/use 它们,但 none 我看到解释如何将数据插入登录函数的上下文中。

例如,chi 文档 here 有以下代码来检查管理员用户,这对我有用,但它没有提供关于 auth 对象最初如何进入上下文的任何线索。还有另一个中间件函数描述了如何将文章结构加载到上下文中并从 URL 获取其 id 参数,但这对我不起作用。我真的不想使用 cookie,因为那样会破坏使用上下文的全部目的。

// AdminOnly middleware restricts access to just administrators.
func AdminOnly(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        isAdmin, ok := r.Context().Value("acl.admin").(bool)
        if !ok || !isAdmin {
            http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
            return
        }
        next.ServeHTTP(w, r)
    })
}

在下面的示例中,我想在上下文中存储用户结构或身份验证令牌。

此外,我将如何处理上下文中的闪烁?我可以这样做还是应该坚持使用基于数据库的 Web 应用程序会话?

理想情况下,我想对 Web 应用程序服务模板和单独的 API 应用程序使用相同的方法。

这是我的 Web 应用程序登录代码,经过一些简化以删除了废话:

func (rs *appResource) login(w http.ResponseWriter, r *http.Request) {

// appResource contains db connection, session store and templates

    var authUser AuthUser //struct

    session, err := rs.store.Get(r, "admin-data")

    email := r.FormValue("email")

    password := r.FormValue("password")

    sqlStatement := "SELECT id, venue_id, first_name, last_name, email, password FROM Users WHERE email=?"

    row := rs.db.QueryRow(sqlStatement, email)

    err = row.Scan(&authUser.UserID, &authUser.VenueID, &authUser.FirstName, &authUser.LastName, &authUser.Email, &authUser.Password)

    if err = bcrypt.CompareHashAndPassword([]byte(authUser.Password), []byte(password)); err != nil {
        session.AddFlash("Your password is incorrect")
        session.Save(r, w)
        http.Redirect(w, r, "/signin", http.StatusFound)
        return
    }

    authUser.Authenticated = true

    session.Values["authUser"] = authUser

    firstName := authUser.FirstName

    message := fmt.Sprintf("Welcome, %s!", firstName)

    session.AddFlash(message)

    session.Save(r, w)

    http.Redirect(w, r, "/", http.StatusFound) 
}

从有关上下文的 go 文档中,要在上下文中设置一个值,您只需使用 WithValue

给你一个想法,最简单的例子是:

package main

import (
    "fmt"
    "context"
)

func main() {

    const key = "myKey"

    ctx := context.Background()

    ctx := context.WithValue(ctx, key, "someVal")

    v, ok := ctx.Value(key).(string)
    if !ok {
        fmt.Println("key does not exist in the context")
        return
    }

    fmt.Printf("found %v", v)

}

现在,按照示例,在上下文中设置新值非常容易。当我们谈论 r.Context() 时,来自它的上下文先前已使用 WithContext*http.Request 请求中设置。举个简单的例子:

package main

import (
    "http"
    "context"
)

func main() {

    req, err := http.NewRequest(
        http.MethodGet,
        "/someEdp",
        nil,
    )

    if err != nil {
        fmt.Println(err)
        return
    }

    ctx := context.Background()

    ctx := context.WithValue(ctx, "auth-token", "mySecretToken")

    req = req.WithContext(ctx)

    // Do the request

}

要在您的处理程序中读取它就像:

func (rs *appResource) login(w http.ResponseWriter, r *http.Request) {

    ...

    ctx := r.Context()

    v, ok := ctx.Value("auth-token").(string)
    if !ok {
        fmt.Println("ups")
        return
    }

    fmt.Printf("found %v", v)

    // Do something

}

现在,如果您想使用这种机制在上下文中安全地存储和读取授权令牌,我建议您看一下这篇关于 context keys 的文章,作者是@Mat Ryer。

基本上,"intercepts" 您的请求的任何人都可能从您的请求中读取授权令牌,因为您使用的是字符串作为密钥。

相反,您应该定义私有上下文密钥,这将允许您从上下文中set/read授权令牌。

package main

import (
    "fmt"
    "context"
)

type contextKey string

var contextKeyAuthtoken = contextKey("auth-token")

func setToken(ctx context.Context, token string) context.Context {
    return context.WithValue(ctx, contextKeyAuthtoken, token)
}

func getToken(ctx context.Context) (string, bool) {
    tokenStr, ok := ctx.Value(contextKeyAuthtoken).(string)
        return tokenStr, ok
}

func main() {

    ctx := context.Background()

    ctx = setToken(ctx, "someToken")

    t, ok := getToken(ctx)
    if !ok {
        fmt.Println("unauthorized")
        return
    }

    fmt.Printf("authorized with token %v", t)

}

这是我为上面的代码段设置的 playground

因此,您将在请求的消费者(您的接收者处理程序)中使用 getToken 并在另一个服务(?)或您服务的另一个逻辑组件中使用 setToken

通过这种方式,只有您才能从上下文中读取您的授权令牌,并允许用户执行某些操作或不执行某些操作。