Возможности
-
Автоматическая загрузка и сохранение данных сессии через middleware
-
Выбор из 19 серверных хранилищ для сессий, включая:
- Базы данных: PostgreSQL, MySQL, MSSQL, SQLite
- Кэш-системы: Redis
- Поддержка пользовательских хранилищ
-
Расширенные функции:
- Несколько сессий на один запрос
- “Flash”-сообщения (одноразовые уведомления)
- Регенерация токенов сессии
- Таймауты: по бездействию и абсолютные
- Функция “запомнить меня”
-
Гибкость:
- Простота расширения и кастомизации
- Передача токенов сессии через HTTP-заголовки или тела запросов/ответов
-
Производительность:
- Оптимизированная архитектура
- Меньший размер, выше скорость и меньше потребление памяти по сравнению с gorilla/sessions
Установка
Этот пакет требует Go версии 1.12 или новее.
go get github.com/alexedwards/scs/v2
Примечание: Если вы используете традиционный механизм GOPATH для управления зависимостями (вместо модулей), вам нужно использовать go get
и импортировать github.com/alexedwards/scs
без суффикса v2
.
Рекомендуется использовать версионные релизы. Код в ветке tip
может содержать экспериментальные функции, которые могут измениться.
Базовое использование
SCS реализует механизм управления сессиями в соответствии с рекомендациями по безопасности OWASP. Данные сессии хранятся на сервере, а случайно сгенерированный уникальный токен сессии (или ID сессии) передается клиенту и обратно через cookie сессии.
package main
import (
"io"
"net/http"
"time"
"github.com/alexedwards/scs/v2"
)
var sessionManager *scs.SessionManager
func main() {
// Инициализация нового менеджера сессий и настройка времени жизни сессии
sessionManager = scs.New()
sessionManager.Lifetime = 24 * time.Hour
mux := http.NewServeMux()
mux.HandleFunc("/put", putHandler)
mux.HandleFunc("/get", getHandler)
// Оберните ваши обработчики middleware LoadAndSave()
http.ListenAndServe(":4000", sessionManager.LoadAndSave(mux))
}
func putHandler(w http.ResponseWriter, r *http.Request) {
// Сохранение нового ключа и значения в данных сессии
sessionManager.Put(r.Context(), "message", "Hello from a session!")
}
func getHandler(w http.ResponseWriter, r *http.Request) {
// Использование вспомогательной функции GetString для получения строкового значения
// по ключу. Возвращается нулевое значение, если ключ не существует.
msg := sessionManager.GetString(r.Context(), "message")
io.WriteString(w, msg)
}
Пример работы с curl:
$ curl -i --cookie-jar cj --cookie cj localhost:4000/put
HTTP/1.1 200 OK
Cache-Control: no-cache="Set-Cookie"
Set-Cookie: session=lHqcPNiQp_5diPxumzOklsSdE-MJ7zyU6kjch1Ee0UM; Path=/; Expires=Sat, 27 Apr 2019 10:28:20 GMT; Max-Age=86400; HttpOnly; SameSite=Lax
Vary: Cookie
Date: Fri, 26 Apr 2019 10:28:19 GMT
Content-Length: 0
$ curl -i --cookie-jar cj --cookie cj localhost:4000/get
HTTP/1.1 200 OK
Date: Fri, 26 Apr 2019 10:28:24 GMT
Content-Length: 21
Content-Type: text/plain; charset=utf-8
Hello from a session!
Настройка поведения сессии
Поведение сессии можно настроить через поля SessionManager:
sessionManager = scs.New()
sessionManager.Lifetime = 3 * time.Hour // Общее время жизни сессии
sessionManager.IdleTimeout = 20 * time.Minute // Таймаут бездействия
sessionManager.Cookie.Name = "session_id" // Имя cookie
sessionManager.Cookie.Domain = "example.com" // Домен cookie
sessionManager.Cookie.HttpOnly = true // Доступ только через HTTP
sessionManager.Cookie.Path = "/example/" // Путь cookie
sessionManager.Cookie.Persist = true // Сохранение после закрытия браузера
sessionManager.Cookie.SameSite = http.SameSiteStrictMode // Политика SameSite
sessionManager.Cookie.Secure = true // Только HTTPS
sessionManager.Cookie.Partitioned = true // Partitioned cookies
Документация по всем доступным настройкам и их значениям по умолчанию доступна [здесь](ссылка на документацию).
Работа с данными сессии
Установка и получение данных
Данные можно устанавливать с помощью метода Put()
и получать с помощью Get()
. Для распространённых типов данных предусмотрены вспомогательные методы, такие как GetString()
, GetInt()
и GetBytes()
. Полный список вспомогательных методов доступен в документации.
Одноразовое получение данных
Метод Pop()
(и соответствующие вспомогательные методы для распространённых типов данных) работает как одноразовый Get()
- извлекает данные и сразу удаляет их из сессии. Это полезно для реализации функционала “flash”-сообщений, которые показываются пользователю только один раз.
Дополнительные функции
Exists()
- возвращаетbool
, указывающий на наличие ключа в данных сессииKeys()
- возвращает отсортированный срез всех ключей в данных сессии
Удаление данных
Отдельные элементы можно удалить с помощью метода Remove()
. Для полной очистки данных сессии используйте метод Destroy()
. После вызова Destroy()
любые последующие операции в рамках того же цикла запроса приведут к созданию новой сессии - с новым токеном и временем жизни.
Хранение пользовательских типов
SCS использует кодировку gob
для хранения данных сессии. Пользовательские типы должны быть зарегистрированы в пакете encoding/gob
. Поля структур пользовательских типов должны быть экспортируемыми (начинаться с заглавной буквы), чтобы быть видимыми для пакета encoding/gob
. Пример работы можно посмотреть [здесь](ссылка на пример).
Загрузка и сохранение сессий
Стандартное использование
Большинство приложений используют middleware LoadAndSave()
. Этот middleware:
- Загружает и сохраняет данные сессии в хранилище
- Управляет передачей токена сессии клиенту через cookie
Кастомизация поведения
Если требуется изменить стандартное поведение (например, передавать токен сессии через HTTP-заголовки или устанавливать распределённую блокировку токена сессии на время запроса), можно создать собственный middleware, используя код LoadAndSave()
в качестве шаблона. Пример доступен [здесь](ссылка на пример).
Точный контроль
Для более точного управления можно загружать и сохранять сессии непосредственно в обработчиках запросов или в любом другом месте приложения. Пример такого подхода приведён [здесь](ссылка на пример).
Настройка хранилища сессий
Хранилище по умолчанию
По умолчанию SCS использует in-memory хранилище для данных сессии. Это:
- Удобно (не требует настройки)
- Очень быстро
- Но все данные теряются при остановке или перезапуске приложения
Это решение подходит для:
- Приложений, где допустима потеря данных в обмен на высокую производительность
- Прототипирования
- Тестирования
Продукционные решения
Для большинства рабочих приложений рекомендуется использовать постоянные хранилища, такие как:
- PostgreSQL
- MySQL
- Другие поддерживаемые системы (список ниже)
Полный список поддерживаемых хранилищ сессий с инструкциями по использованию доступен в таблице (ссылки в оригинальной документации).
Поддерживаемые хранилища сессий
Вот полный список доступных хранилищ сессий в SCS с их характеристиками:
Пакет | Бэкенд | Встроенный | In-Memory | Мультипроцессорный |
---|---|---|---|---|
badgerstore |
BadgerDB | Да | Нет | Нет |
boltstore |
BBolt | Да | Нет | Нет |
bunstore |
Bun ORM (PostgreSQL/MySQL/MSSQL/SQLite) | Нет | Нет | Да |
buntdbstore |
BuntDB | Да | Да | Нет |
cockroachdbstore |
CockroachDB | Нет | Нет | Да |
consulstore |
Consul | Нет | Да | Да |
etcdstore |
Etcd | Нет | Нет | Да |
firestore |
Google Cloud Firestore | Нет | ? | Да |
gormstore |
GORM ORM (PostgreSQL/MySQL/SQLite/MSSQL/TiDB) | Нет | Нет | Да |
leveldbstore |
LevelDB | Да | Нет | Нет |
memstore |
In-memory (по умолчанию) | Да | Да | Нет |
mongodbstore |
MongoDB | Нет | Нет | Да |
mssqlstore |
Microsoft SQL Server | Нет | Нет | Да |
mysqlstore |
MySQL | Нет | Нет | Да |
pgxstore |
PostgreSQL (драйвер pgx) | Нет | Нет | Да |
postgresstore |
PostgreSQL (драйвер pq) | Нет | Нет | Да |
redisstore |
Redis | Нет | Да | Да |
sqlite3store |
SQLite3 (CGO-драйвер mattn/go-sqlite3) | Да | Нет | Да |
Ключевые характеристики:
- Встроенный - Не требует отдельного сервера/процесса
- In-Memory - Хранит данные в оперативной памяти
- Мультипроцессорный - Поддерживает работу в распределённой среде
Пользовательские хранилища
SCS также поддерживает создание собственных хранилищ сессий. Подробная информация доступна [здесь](ссылка на документацию).
Выбор хранилища зависит от требований вашего приложения:
- Для тестирования/разработки -
memstore
илиbuntdbstore
- Для продакшена -
postgresstore
,mysqlstore
илиredisstore
- Для встраиваемых решений -
badgerstore
илиboltstore
Использование пользовательских хранилищ сессий
Базовый интерфейс хранилища
Интерфейс scs.Store
определяет контракт для пользовательских хранилищ сессий. Любой объект, реализующий этот интерфейс, может быть установлен как хранилище при настройке сессии.
type Store interface {
// Delete удаляет токен сессии и соответствующие данные из хранилища.
// Если токен не существует, Delete должен завершиться без ошибки.
Delete(token string) (err error)
// Find возвращает данные для указанного токена сессии.
// Если токен не найден или истек, возвращает found=false.
// Для повреждённых токенов также возвращает found=false.
// Ошибка возвращается только для системных сбоев.
Find(token string) (b []byte, found bool, err error)
// Commit добавляет или обновляет данные сессии с указанным сроком действия.
Commit(token string, b []byte, expiry time.Time) (err error)
}
type IterableStore interface {
// All возвращает map со всеми активными (не истекшими) сессиями.
// Ключ - токен сессии, значение - данные сессии.
// Если активных сессий нет, возвращает пустую (не nil) map.
All() (map[string][]byte, error)
}
Интерфейс с поддержкой контекста
scs.CtxStore
расширяет базовый интерфейс, добавляя методы с поддержкой context.Context
:
type CtxStore interface {
Store
// Версии методов с контекстом
DeleteCtx(ctx context.Context, token string) (err error)
FindCtx(ctx context.Context, token string) (b []byte, found bool, err error)
CommitCtx(ctx context.Context, token string, b []byte, expiry time.Time) (err error)
}
type IterableCtxStore interface {
// AllCtx - версия All с поддержкой контекста
AllCtx(ctx context.Context) (map[string][]byte, error)
}
Защита от фиксации сессии
Для предотвращения атак фиксации сессии следует обновлять токен сессии при любом изменении уровня привилегий. Обычно это делается при входе/выходе пользователя:
func loginHandler(w http.ResponseWriter, r *http.Request) {
userID := 123
// 1. Сначала обновляем токен сессии
err := sessionManager.RenewToken(r.Context())
if err != nil {
http.Error(w, err.Error(), 500)
return
}
// 2. Затем изменяем уровень привилегий
sessionManager.Put(r.Context(), "userID", userID)
}
Важные моменты:
RenewToken()
должен вызываться ДО изменения данных сессии- При ошибке обновления токена следует прервать операцию
- Такой же подход применяется при выходе пользователя
Этот механизм гарантирует, что после аутентификации сессия получает новый токен, делая недействительным любой ранее перехваченный злоумышленником токен.
Расширенные возможности SCS
Несколько сессий в одном запросе
SCS позволяет использовать несколько параллельных сессий в рамках одного запроса с разными:
- Временем жизни
- Хранилищами данных
Пример реализации доступен [здесь](ссылка на пример).
Перебор всех сессий
Для работы со всеми активными сессиями SCS предоставляет метод Iterate()
, который принимает функцию-обработчик:
err := sessionManager.Iterate(r.Context(), func(ctx context.Context) error {
userID := sessionManager.GetInt(ctx, "userID")
if userID == 4 {
// Удаляем сессию для пользователя с ID=4
return sessionManager.Destroy(ctx)
}
return nil
})
if err != nil {
log.Fatal(err)
}
Ключевые особенности:
- Обработчик получает контекст каждой сессии
- Можно выполнять любые операции с сессией
- Возврат ошибки прерывает процесс итерации
Потоковая передача ответов (Flushing)
Поддержка потоковой передачи реализована через http.NewResponseController
(доступно в Go ≥1.20):
func flushingHandler(w http.ResponseWriter, r *http.Request) {
sessionManager.Put(r.Context(), "message", "Hello from flushing handler!")
rc := http.NewResponseController(w)
for i := 0; i < 5; i++ {
fmt.Fprintf(w, "Write %d\n", i)
err := rc.Flush()
if err != nil {
log.Println(err)
return
}
time.Sleep(time.Second)
}
}
Важно:
- Стандартная реализация middleware
LoadAndSave()
не поддерживает интерфейсhttp.Flusher
- Потоковая передача работает только в Go версии 1.20 и выше
Полный рабочий пример можно найти [здесь](ссылка на пример).
Совместимость с фреймворками
Возможны проблемы при интеграции с фреймворками, которые не передают контекст запроса через стандартные middleware:
- Для Echo рекомендуется использовать пакет echo-scs-session
- Fiber и другие нестандартные фреймворки могут требовать дополнительной адаптации
Вклад в разработку
Приветствуются:
- Исправления ошибок
- Улучшения документации
Для новых функций или изменений поведения:
- Создайте issue для обсуждения
- После согласования - отправляйте PR
Для новых реализаций хранилищ:
- Как правило, не добавляются в основной репозиторий
- Можно создать отдельный репозиторий и добавить ссылку в README через PR