Переменные сессий в 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)
    }
}

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

4 ответа

Сначала: никогда не используйте 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") невероятно полезна для устранения подобных проблем, поскольку вы можете видеть срок действия файлов cookie, путь и другие параметры.

  • Вы также проверяете session.Values["email"] == nil, который не работает. Пустая строка в Go просто "", и потому что session.Values это map[string]interface{} вам нужно набрать 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/")))
    }
    

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

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

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

В моем случае проблема была в Пути. Я знаю, что вопрос не в этом, но этот пост появляется первым, когда вы выполняете поиск в Google. Итак, я начал сеанс следующим образом:

/usuario/login

Таким образом, путь был установлен в / usuario, а затем, когда я сделал еще один запрос от /файл cookie не был установлен, поскольку / не совпадает с / usuario

Я исправил это, указав Путь, я знаю, что это должно быть очевидно, но мне потребовалось несколько часов, чтобы это осознать. Так:

&sessions.Options{
        MaxAge:   60 * 60 * 24,
        HttpOnly: true,
        Path:     "/", // <-- This is very important
    }

Дополнительная информация об общих файлах cookie: https://developer.mozilla.org/es/docs/Web/HTTP/Cookies.

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

Это похоже на ошибку в Chromium, когда куки с явным доменом localhost не отправляются. Кажется, проблема не возникает в Firefox.

Я смог заставить ваше демо работать, используя

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

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

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

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

Другие вопросы по тегам