Как сохранить данные сеанса в контексте роутера от входа в систему - PullRequest
0 голосов
/ 21 февраля 2019

В приложении Golang я использую gorilla / session с бэкэндом mySQL для хранения данных в сеансе, но вместо этого я хотел бы хранить данные в контексте маршрутизатора chi.Как добавить строки или структуры токена аутентификации в контекст?Я вижу множество примеров, объясняющих, как их читать / использовать, но ни один из них не объясняет, как вставить данные в контекст в функции входа в систему.

Например, документация chi здесь имеетследующий код для проверки пользователя с правами администратора, который будет работать для меня, но он не дает никакого представления о том, как объект auth попал в контекст в первую очередь.Есть другая функция промежуточного программного обеспечения, описывающая, как загрузить структуру статьи в контекст и получить ее параметр id из URL, но это не будет работать для меня.Я на самом деле не хочу использовать 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)
    })
}

В следующем примере я хочу сохранить структуру User или токен аутентификации в контексте.

Кроме того, как бы я справился со вспышками в контексте?Могу ли я сделать это или я должен просто придерживаться основанных на БД сеансов для веб-приложений?

В идеале я хочу использовать один и тот же метод как для шаблонов, обслуживающих веб-приложение, так и для отдельного приложения API.

Вот код входа в мое веб-приложение, немного упрощенный для удаления разницы:

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 Ответ

0 голосов
/ 23 февраля 2019

Из документации по контексту, чтобы установить значение в контексте, вам просто нужно использовать 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(), исходящий из него контекст был предварительно установлен в запросе *http.Request с использованием WithContext .Простой пример:

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

}

Теперь, если вы хотите использовать этот механизм для безопасного хранения ипрочитайте токен авторизации в контексте, я предлагаю вам взглянуть на эту статью о ключах контекста , написанных @Mat Ryer.

По сути, любой, кто «перехватывает» ваш запрос, можетпотенциально прочитайте токен авторизации из вашего запроса, потому что вы используете строку в качестве ключа.

Вместо этого вы должны определить свой закрытый контекстный ключ, который позволит вам установить / прочитать токен авторизации из контекста.

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)

}

Вот игровая площадка , которую я установил для приведенного выше фрагмента.

Итак, вы бы использовали getToken в получателе запроса (обработчике вашего получателя) & setToken в другом сервисе (?) Или другом логическом компоненте вашего сервиса.

Таким образом вы и только вы сможете прочитать из контекста свой токен авторизации и позволить пользователю делатьчто-то или нет.

...