dtz30833 2019-02-21 08:39
浏览 57
已采纳

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

In a Golang app, I'm using gorilla/sessions with the mySQL backend to store data in a session but I'd like to store the data in the chi router context instead. How do I add auth token strings or structs into the context? I see plenty of examples explaining how to read/use them but none I saw explain how to insert the data into the context within a login function.

For example, the chi documentation here has the following code to check admin user which will work for me but it offers no clue as to how the auth object got into the context in the first place. There is another middleware function describing how to load an article struct into the context and gets its id parameter from the URL but that wont work for me. I don't really want to use a cookie as that would defeat the whole purpose in using the context.

// 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)
    })
}

In the example below I want to store the User struct or an auth token in the context.

Additionally, how would I handle flashes in the context? Can I do that or should I just stick to the db based sessions for web apps?

Ideally, I want to use the same method for both a web app serving templates and a separate API app.

Here is my web app login code, simplified a bit to remove the cruft:

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) 
}
  • 写回答

1条回答 默认 最新

  • douqian3712 2019-02-23 18:25
    关注

    From the go documentation about context, to set a value in the context, you simply need to use WithValue.

    To give you an idea, the simplest example would be:

    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)
    
    }
    

    Now, following the example, setting a new value in the context is pretty easy. When we are talking about r.Context(), the context coming out from it has been previously set in the *http.Request request using WithContext. Just a quick example:

    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
    
    }
    

    And to read it in your handler it would be as easy as:

    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
    
    }
    

    Now, if you want to use this mechanism to safely store & read an authorization token in the context, I suggest you to take a look at this article about context keys written by @Mat Ryer.

    Basically, anyone who "intercepts" your request, can potentially read the authorization token from your request because you are using a string as a key.

    Instead, you should define your private context key that will allow you to set/read the authorization token from the context.

    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)
    
    }
    

    Here's the playground I set for the snippet above.

    So, you would use getToken in the consumer of the request (your receiver handler) & setToken in another service (?) or another logical component of your service.

    In this way you, and only you, will be able to read from the context your authorization token and allow the user to do something or not.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 逻辑谓词和消解原理的运用
  • ¥15 请求分析基于spring boot+vue的前后端分离的项目
  • ¥15 三菱伺服电机按启动按钮有使能但不动作
  • ¥15 js,页面2返回页面1时定位进入的设备
  • ¥200 关于#c++#的问题,请各位专家解答!网站的邀请码
  • ¥50 导入文件到网吧的电脑并且在重启之后不会被恢复
  • ¥15 (希望可以解决问题)ma和mb文件无法正常打开,打开后是空白,但是有正常内存占用,但可以在打开Maya应用程序后打开场景ma和mb格式。
  • ¥20 ML307A在使用AT命令连接EMQX平台的MQTT时被拒绝
  • ¥20 腾讯企业邮箱邮件可以恢复么
  • ¥15 有人知道怎么将自己的迁移策略布到edgecloudsim上使用吗?