golang高性能可定制HTTP会话管理插件库sessions的使用
Golang高性能可定制HTTP会话管理插件库sessions的使用
简介
这是一个非常简单、高性能且高度可定制的Go HTTP服务器会话服务库。默认情况下,该服务将会话存储在redis中,并通过cookie将会话传输给客户端。但这些都可以轻松自定义。
快速开始
var sesh *sessions.Service
// 发布一个新会话并将其写入ResponseWriter
userSession, err := sesh.IssueUserSession("fakeUserID", "{\"foo\":\"bar\"}", w)
if err != nil {
log.Printf("Err issuing user session: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
...
// 从请求中获取有效的用户会话指针。nil指针表示没有或无效的会话
userSession, err := sesh.GetUserSession(r)
if err != nil {
log.Printf("Err fetching user session: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// nil会话指针表示401未授权
if userSession == nil {
http.Error(w, "Unathorized", http.StatusUnauthorized)
return
}
...
// 延长会话过期时间。注意会话过期需要手动延长
if err := sesh.ExtendUserSession(userSession, r, w); err != nil {
log.Printf("Err extending user session: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
...
// 使用户会话无效,从redis中删除它并使ResponseWriter上的cookie过期
if err := sesh.ClearUserSession(userSession, w); err != nil {
log.Printf("Err clearing user session: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
性能
基准测试需要运行redis-server。设置REDIS_URL
环境变量,否则基准测试会查找":6379"。
$ (cd benchmark && go test -bench=.)
setting up benchmark tests
BenchmarkBaseServer-2 20000 72479 ns/op
BenchmarkValidSession-2 10000 151650 ns/op
PASS
shutting down benchmark tests
ok github.com/adam-hanna/sessions/benchmark 3.727s
设计
默认情况下,服务将会话存储在redis中,并将哈希的sessionID通过cookie传输给客户端。但可以通过创建实现接口的自定义结构轻松定制。
会话服务的一般流程如下:
- 创建存储、认证和传输服务
- 用户登录后调用
IssueUserSession
函数 - 使用
GetUserSession
函数检查请求中是否包含有效会话 - 用户注销时调用
ClearUserSession
函数
API
user.Session
type Session struct {
ID string
UserID string
ExpiresAt time.Time
JSON string
}
Session是用于存储会话数据的结构体。JSON字段允许您设置任何自定义信息。
IssueUserSession
func (s *Service) IssueUserSession(userID string, json string, w http.ResponseWriter) (*user.Session, error)
IssueUserSession授予一个新的用户会话,将该会话信息写入存储,并在http.ResponseWriter上写入会话。
ClearUserSession
func (s *Service) ClearUserSession(userSession *user.Session, w http.ResponseWriter) error
ClearUserSession用于从存储中删除用户会话,并清除ResponseWriter上的cookie。
GetUserSession
func (s *Service) GetUserSession(r *http.Request) (*user.Session, error)
GetUserSession从请求中包含的哈希sessionID返回用户会话。此方法仅返回有效会话。
ExtendUserSession
func (s *Service) ExtendUserSession(userSession *user.Session, r *http.Request, w http.ResponseWriter) error
ExtendUserSession按Options.ExpirationDuration延长会话的ExpiresAt。
完整示例
package main
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"io"
"log"
"net/http"
"time"
"github.com/adam-hanna/sessions"
"github.com/adam-hanna/sessions/auth"
"github.com/adam-hanna/sessions/store"
"github.com/adam-hanna/sessions/transport"
)
// SessionJSON 用于编组和解组自定义会话json信息
type SessionJSON struct {
CSRF string `json:"csrf"`
}
var sesh *sessions.Service
var issueSession = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
csrf, err := generateKey()
if err != nil {
log.Printf("Err generating csrf: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
myJSON := SessionJSON{
CSRF: csrf,
}
JSONBytes, err := json.Marshal(myJSON)
if err != nil {
log.Printf("Err marhsalling json: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
userSession, err := sesh.IssueUserSession("fakeUserID", string(JSONBytes[:]), w)
if err != nil {
log.Printf("Err issuing user session: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
log.Printf("In issue; user's session: %v\n", userSession)
// 注意: 我们在cookie中设置csrf,但在请求头中查找它
csrfCookie := http.Cookie{
Name: "csrf",
Value: csrf,
Expires: userSession.ExpiresAt,
Path: "/",
HttpOnly: false,
Secure: false, // 注意: 在开发中不能使用安全cookie
}
http.SetCookie(w, &csrfCookie)
w.WriteHeader(http.StatusOK)
})
var requiresSession = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userSession, err := sesh.GetUserSession(r)
if err != nil {
log.Printf("Err fetching user session: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// nil会话指针表示401未授权
if userSession == nil {
http.Error(w, "Unathorized", http.StatusUnauthorized)
return
}
log.Printf("In require; user session expiration before extension: %v\n", userSession.ExpiresAt.UTC())
myJSON := SessionJSON{}
if err := json.Unmarshal([]byte(userSession.JSON), &myJSON); err != nil {
log.Printf("Err unmarshalling json: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("In require; user's custom json: %v\n", myJSON)
// 注意: 我们在cookie中设置csrf,但在请求头中查找它
csrf := r.Header.Get("X-CSRF-Token")
if csrf != myJSON.CSRF {
log.Printf("Unauthorized! CSRF token doesn't match user session")
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 注意会话过期需要手动延长
if err = sesh.ExtendUserSession(userSession, r, w); err != nil {
log.Printf("Err extending user session: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
log.Printf("In require; users session expiration after extension: %v\n", userSession.ExpiresAt.UTC())
// 也需要延长csrf cookie
csrfCookie := http.Cookie{
Name: "csrf",
Value: csrf,
Expires: userSession.ExpiresAt,
Path: "/",
HttpOnly: false,
Secure: false, // 注意: 在开发中不能使用安全cookie
}
http.SetCookie(w, &csrfCookie)
w.WriteHeader(http.StatusOK)
})
var clearSession = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userSession, err := sesh.GetUserSession(r)
if err != nil {
log.Printf("Err fetching user session: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// nil会话指针表示401未授权
if userSession == nil {
http.Error(w, "Unathorized", http.StatusUnauthorized)
return
}
log.Printf("In clear; session: %v\n", userSession)
myJSON := SessionJSON{}
if err := json.Unmarshal([]byte(userSession.JSON), &myJSON); err != nil {
log.Printf("Err unmarshalling json: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
log.Printf("In require; user's custom json: %v\n", myJSON)
// 注意: 我们在cookie中设置csrf,但在请求头中查找它
csrf := r.Header.Get("X-CSRF-Token")
if csrf != myJSON.CSRF {
log.Printf("Unauthorized! CSRF token doesn't match user session")
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err = sesh.ClearUserSession(userSession, w); err != nil {
log.Printf("Err clearing user session: %v\n", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// 也需要清除csrf cookie
aLongTimeAgo := time.Now().Add(-1000 * time.Hour)
csrfCookie := http.Cookie{
Name: "csrf",
Value: "",
Expires: aLongTimeAgo,
Path: "/",
HttpOnly: false,
Secure: false, // 注意: 在开发中不能使用安全cookie
}
http.SetCookie(w, &csrfCookie)
w.WriteHeader(http.StatusOK)
})
func main() {
seshStore := store.New(store.Options{})
// 例如 `$ openssl rand -base64 64`
seshAuth, err := auth.New(auth.Options{
Key: []byte("DOZDgBdMhGLImnk0BGYgOUI+h1n7U+OdxcZPctMbeFCsuAom2aFU4JPV4Qj11hbcb5yaM4WDuNP/3B7b+BnFhw=="),
})
if err != nil {
log.Fatal(err)
}
seshTransport := transport.New(transport.Options{
HTTPOnly: true,
Secure: false, // 注意: 在开发中不能使用安全cookie!
})
sesh = sessions.New(seshStore, seshAuth, seshTransport, sessions.Options{})
http.HandleFunc("/issue", issueSession)
http.HandleFunc("/require", requiresSession)
http.HandleFunc("/clear", clearSession) // 也需要有效的会话
log.Println("Listening on localhost:3000")
log.Fatal(http.ListenAndServe("127.0.0.1:3000", nil))
}
// 感谢
// https://astaxie.gitbooks.io/build-web-application-with-golang/en/06.2.html#unique-session-ids
func generateKey() (string, error) {
b := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
许可证
MIT许可证
更多关于golang高性能可定制HTTP会话管理插件库sessions的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang高性能可定制HTTP会话管理插件库sessions的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang高性能可定制HTTP会话管理库sessions使用指南
sessions
是Golang中一个流行的HTTP会话管理库,它提供了高性能、可定制的会话管理功能。下面我将详细介绍如何使用这个库。
基本安装
首先安装sessions库:
go get github.com/gorilla/sessions
基本使用示例
package main
import (
"fmt"
"net/http"
"github.com/gorilla/sessions"
)
// 初始化cookie存储
var store = sessions.NewCookieStore([]byte("your-secret-key"))
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
// 获取会话
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 设置会话值
session.Values["foo"] = "bar"
session.Values[42] = true
// 保存会话
err = session.Save(r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 读取会话值
foo := session.Values["foo"]
fmt.Fprintf(w, "Session value: %v", foo)
}
高级配置
1. 自定义存储后端
sessions
支持多种存储后端,包括Cookie、文件系统、Redis等。
使用Redis存储
import (
"github.com/gorilla/sessions"
"github.com/boj/redistore"
)
func newRedisStore() *redistore.RediStore {
store, err := redistore.NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
if err != nil {
panic(err)
}
return store
}
var store = newRedisStore()
2. 会话选项配置
var store = sessions.NewCookieStore(
[]byte("your-secret-key"),
[]byte("another-secret-key-for-encryption"),
)
// 配置会话选项
store.Options = &sessions.Options{
Path: "/",
MaxAge: 86400 * 30, // 30天
HttpOnly: true,
Secure: true, // 仅在HTTPS下传输
SameSite: http.SameSiteLaxMode,
}
3. 闪存消息(Flash Messages)
闪存消息是一种特殊的会话消息,它只显示一次然后自动删除。
func flashHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
// 添加闪存消息
session.AddFlash("This is a flash message!")
// 保存会话
session.Save(r, w)
// 在下一个请求中获取闪存消息
flashes := session.Flashes()
for _, flash := range flashes {
fmt.Fprintf(w, "Flash: %v", flash)
}
}
性能优化建议
- 使用Redis存储:对于高流量网站,使用Redis等外部存储比Cookie存储性能更好
- 减少会话数据大小:只存储必要的数据
- 适当设置会话过期时间:避免会话无限期存在
- 使用会话池:对于频繁创建销毁会话的场景
安全最佳实践
- 总是使用HTTPS并设置
Secure
标志 - 使用
HttpOnly
标志防止XSS攻击 - 定期更换密钥
- 避免在会话中存储敏感信息
完整示例
package main
import (
"fmt"
"net/http"
"github.com/gorilla/sessions"
)
var store = sessions.NewCookieStore([]byte("super-secret-key"))
func loginHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "auth-session")
// 模拟用户认证
if r.FormValue("username") == "admin" && r.FormValue("password") == "password" {
session.Values["authenticated"] = true
session.Save(r, w)
http.Redirect(w, r, "/dashboard", http.StatusFound)
} else {
session.AddFlash("Invalid credentials")
session.Save(r, w)
http.Redirect(w, r, "/login", http.StatusFound)
}
}
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "auth-session")
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
fmt.Fprintln(w, "Welcome to the dashboard!")
}
func logoutHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "auth-session")
// 清除会话
session.Values["authenticated"] = false
session.Options.MaxAge = -1 // 立即过期
session.Save(r, w)
http.Redirect(w, r, "/login", http.StatusFound)
}
func main() {
http.HandleFunc("/login", loginHandler)
http.HandleFunc("/dashboard", dashboardHandler)
http.HandleFunc("/logout", logoutHandler)
http.ListenAndServe(":8080", nil)
}
sessions
库提供了灵活而强大的会话管理功能,通过合理配置可以满足大多数Web应用的会话管理需求。