Подтвердить что ты не робот

Переменные сеансов в golang не сохраняются при использовании сессий горилл

Переменные сеанса не поддерживаются по запросу при использовании веб-инструментария сессий горилл. Когда я запускаю сервер и набираю localhost: 8100/page направляется на login.html, поскольку значения сеанса не существуют. После входа в систему я устанавливаю переменную сеанса в хранилище, и страница перенаправляется на home.html. Но когда я открываю новую вкладку и набираю localhost: 8100/страница должна быть направлена ​​на home.html, используя уже сохраненные переменные сеанса, но страница вместо этого перенаправляется на login.html. Ниже приведен код.

    package main

import (
    "crypto/md5"
    "encoding/hex"
    "fmt"
    "github.com/gocql/gocql"
    "github.com/gorilla/mux"
    "github.com/gorilla/sessions"
    "net/http"
    "time"
)

var store = sessions.NewCookieStore([]byte("something-very-secret"))

var router = mux.NewRouter()

func init() {

    store.Options = &sessions.Options{
        Domain:   "localhost",
        Path:     "/",
        MaxAge:   3600 * 1, // 1 hour
        HttpOnly: true,
    }
}
func main() {
    //session handling
    router.HandleFunc("/", SessionHandler)
    router.HandleFunc("/signIn", SignInHandler)
    router.HandleFunc("/signUp", SignUpHandler)
    router.HandleFunc("/logOut", LogOutHandler)
    http.Handle("/", router)
    http.ListenAndServe(":8100", nil)
}

//handler for signIn
func SignInHandler(res http.ResponseWriter, req *http.Request) {

    email := req.FormValue("email")
    password := req.FormValue("password")

    //Generate hash of password
    hasher := md5.New()
    hasher.Write([]byte(password))
    encrypted_password := hex.EncodeToString(hasher.Sum(nil))

    //cassandra connection
    cluster := gocql.NewCluster("localhost")
    cluster.Keyspace = "gbuy"
    cluster.DefaultPort = 9042
    cluster.Consistency = gocql.Quorum
    session, _ := cluster.CreateSession()
    defer session.Close()

    //select query
    var firstname string
    stmt := "SELECT firstname FROM USER WHERE email= '" + email + "' and password ='" + encrypted_password + "';"
    err := session.Query(stmt).Scan(&firstname)
    if err != nil {
        fmt.Fprintf(res, "failed")
    } else {
        if firstname == "" {
            fmt.Fprintf(res, "failed")
        } else {
            fmt.Fprintf(res, firstname)
        }
    }

    //store in session variable
    sessionNew, _ := store.Get(req, "loginSession")

    // Set some session values.
    sessionNew.Values["email"] = email
    sessionNew.Values["name"] = firstname

    // Save it.
    sessionNew.Save(req, res)
    //store.Save(req,res,sessionNew)

    fmt.Println("Session after logging:")
    fmt.Println(sessionNew)

}

//handler for signUp
func SignUpHandler(res http.ResponseWriter, req *http.Request) {

    fName := req.FormValue("fName")
    lName := req.FormValue("lName")
    email := req.FormValue("email")
    password := req.FormValue("passwd")
    birthdate := req.FormValue("date")
    city := req.FormValue("city")
    gender := req.FormValue("gender")

    //Get current timestamp and format it.
    sysdate := time.Now().Format("2006-01-02 15:04:05-0700")

    //Generate hash of password
    hasher := md5.New()
    hasher.Write([]byte(password))
    encrypted_password := hex.EncodeToString(hasher.Sum(nil))

    //cassandra connection
    cluster := gocql.NewCluster("localhost")
    cluster.Keyspace = "gbuy"
    cluster.DefaultPort = 9042
    cluster.Consistency = gocql.Quorum
    session, _ := cluster.CreateSession()
    defer session.Close()

    //Insert the data into the Table
    stmt := "INSERT INTO USER (email,firstname,lastname,birthdate,city,gender,password,creation_date) VALUES ('" + email + "','" + fName + "','" + lName + "','" + birthdate + "','" + city + "','" + gender + "','" + encrypted_password + "','" + sysdate + "');"
    fmt.Println(stmt)
    err := session.Query(stmt).Exec()
    if err != nil {
        fmt.Fprintf(res, "failed")
    } else {
        fmt.Fprintf(res, fName)
    }
}

//handler for logOut
func LogOutHandler(res http.ResponseWriter, req *http.Request) {
    sessionOld, err := store.Get(req, "loginSession")

    fmt.Println("Session in logout")
    fmt.Println(sessionOld)
    if err = sessionOld.Save(req, res); err != nil {
        fmt.Println("Error saving session: %v", err)
    }
}

//handler for Session
func SessionHandler(res http.ResponseWriter, req *http.Request) {

    router.PathPrefix("/").Handler(http.FileServer(http.Dir("../static/")))
    session, _ := store.Get(req, "loginSession")

    fmt.Println("Session in SessionHandler")
    fmt.Println(session)


    if val, ok := session.Values["email"].(string); ok {
        // if val is a string
        switch val {
        case "": {
            http.Redirect(res, req, "html/login.html", http.StatusFound) }
        default:
            http.Redirect(res, req, "html/home.html", http.StatusFound)
        }
    } else {
        // if val is not a string type
        http.Redirect(res, req, "html/login.html", http.StatusFound)
    }
}

Может кто-нибудь сказать мне, что я делаю неправильно. Спасибо заранее.

4b9b3361

Ответ 1

Сначала: вы никогда не должны использовать md5 для хэш-паролей. Прочитайте эту статью о том, почему, а затем используйте Go пакет bcrypt, Вы также должны параметризовать ваши SQL-запросы, иначе вы открыты для катастрофических атак SQL-инъекций.

В любом случае: есть несколько проблем, которые вам необходимо решить здесь:

  • Ваши сеансы не являются "прилипающими", так как вы устанавливаете Path как /loginSession - поэтому, когда пользователь посещает любой другой путь (т.е. /), сеанс недействителен для этой области.

Вы должны настроить хранилище сеансов при инициализации программы и установить там параметры:

var store = sessions.NewCookieStore([]byte("something-very-secret"))

func init() {

   store.Options = &sessions.Options{
    Domain:   "localhost",
    Path:     "/",
    MaxAge:   3600 * 8, // 8 hours
    HttpOnly: true,
}

Причина, по которой вы можете установить более конкретный путь, заключается в том, что если вошедшие в систему пользователи всегда находятся на суб-маршруте, например /accounts. В вашем случае это не то, что происходит.

Я должен добавить, что вкладка Chrome "Ресурс" в веб-инспекторе (Ресурсы > Куки) невероятно полезна для отладки таких проблем, как вы можете видеть истечение срока действия cookie, путь и другие настройки.

  • Вы также проверяете session.Values["email"] == nil, который не работает. Пустая строка в Go просто "", и поскольку session.Values является map[string]interface{}, вам нужно type assert значение для строка:

то есть.

if val, ok := session.Values["email"].(string); ok {
      // if val is a string
      switch val {
             case "":
                 http.Redirect(res, req, "html/login.html", http.StatusFound)
             default:
                 http.Redirect(res, req, "html/home.html", http.StatusFound)
      }
    } else {
        // if val is not a string type
        http.Redirect(res, req, "html/login.html", http.StatusFound)
    }

Мы имеем дело с "не строковым" случаем, поэтому мы явно говорим о том, что программа должна делать, если сеанс не так, как мы ожидали (клиент изменил его, или более старая версия нашей программы использовала другой тип).

  • Вы не проверяете ошибки при сохранении сеансов.

    sessionNew.Save(req, res)
    

... должно быть:

    err := sessionNew.Save(req, res)
    if err != nil {
            // handle the error case
    }
  • Вы должны получить/проверить сеанс в SessionHandler перед, показывая статические файлы (вы делаете это очень обходным путем):

    func SessionHandler(res http.ResponseWriter, req *http.Request) {
        session, err := store.Get(req, "loginSession")
        if err != nil {
            // Handle the error
        }
    
        if session.Values["email"] == nil {
            http.Redirect(res, req, "html/login.html", http.StatusFound)
        } else {
           http.Redirect(res, req, "html/home.html", http.StatusFound)
        }
        // This shouldn't be here - router isn't scoped in this function! You should set this in your main() and wrap it with a function that checks for a valid session.
        router.PathPrefix("/").Handler(http.FileServer(http.Dir("../static/")))
    }
    

Ответ 2

Проблема заключается в том, что вы пишете ответ до вызова session.Save. Это предотвращает запись заголовков и, таким образом, ваш файл cookie отправляется клиенту.

В коде после session.Query вы вызываете Fprintf в ответ, как только этот код выполняется, вызов sessionNew.Save по существу ничего не делает. Удалите любой код, который записывается в ответ, и повторите попытку.

Я думаю, что сеанс инструментария gorilla должен возвращать ошибку при вызове "Сохранить", если ответ уже написан.

Ответ 3

Следуя цепочке комментариев, попробуйте удалить ограничение Domain из параметров сеанса или заменить его полным доменным именем, которое разрешает (например, с помощью /etc/hosts).

Это, кажется, ошибка в Chromium, где файлы cookie с явным доменом "localhost" не отправляются. Проблема, похоже, не представлена ​​в Firefox.

Мне удалось запустить демоверсию с помощью

store.Options = &sessions.Options{
    // Domain: "localhost",
    MaxAge:   3600 * 1, // 1 hour
    HttpOnly: true,
}

Ответ 4

Используйте серверную часть "FilesystemStore" вместо "CookieStore" , чтобы сохранить переменные сеанса. Другой альтернативой было бы обновление сеанса как контекстной переменной для запроса, то есть сохранение сеанса в контексте и разрешение браузера передавать его в каждом запросе, используя context.Set() из пакета gorilla/context.

Использование "CookieStore" тяжело для клиента, потому что по мере того, как объем информации, хранящейся в cookie, растет, больше информации передается по проводу для каждого запроса и ответа. Преимущество, которое он выполняет, заключается в том, что нет необходимости хранить информацию о сеансе на стороне сервера. Если это не является ограничением для хранения информации о сеансе на сервере, идеальным способом должно быть сохранение информации, связанной с регистрацией и аутентификацией, в хранилище сеансов "не-cookie" на стороне сервера и просто передача маркера клиенту. Сервер будет поддерживать карту данных токена и сеанса. "FilesystemStore" позволяет это сделать.

Хотя и "FilesystemStore" , и "CookieStore" реализуют интерфейс "Store", каждая из реализаций функций "Сохранить()" немного отличается. Исходный код для обеих функций CookieStore.Save() и FilesystemStore.Save() поможет нам понять, почему "CookieStore" не может сохранять информацию о сеансе. Метод FileystemStore Save(), кроме записи информации сеанса в заголовок ответа, также сохраняет информацию в файле сеанса на стороне сервера. В реализации "CookieStore" , если браузер не может отправить новый модифицированный файл cookie из ответа на следующий запрос, запрос может выйти из строя. В реализации "FileystemStore" токен, предоставляемый браузеру, остается неизменным. Информация о сеансе обновляется в файле и выбирается на основе запрашивающего токена, когда это требуется.