Переменные сеансов в 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)
}
}
Может кто-нибудь сказать мне, что я делаю неправильно. Спасибо заранее.
Ответы
Ответ 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)
}
Мы имеем дело с "не строковым" случаем, поэтому мы явно говорим о том, что программа должна делать, если сеанс не так, как мы ожидали (клиент изменил его, или более старая версия нашей программы использовала другой тип).
... должно быть:
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" токен, предоставляемый браузеру, остается неизменным. Информация о сеансе обновляется в файле и выбирается на основе запрашивающего токена, когда это требуется.