-
Notifications
You must be signed in to change notification settings - Fork 0
/
hub.go
183 lines (175 loc) · 5.24 KB
/
hub.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package sess
import (
"context"
xerr "github.com/goclub/error"
"github.com/google/uuid"
"net/http"
"time"
)
// Hub 利用 Store 生成 sessionID 和查找 Session
type Hub struct {
store Store
option HubOption
}
func NewHub(store Store, option HubOption) (hub *Hub, err error) {
// 默认 sesison ttl
if option.SessionTTL == 0 {
option.SessionTTL = time.Hour * 8
}
// http cookie name 默认值
if option.Cookie.MaxAge == 0 {
option.Cookie.MaxAge = int(option.SessionTTL.Seconds())
}
if option.Cookie.Name == "" {
option.Cookie.Name = "session_id"
}
// http header key 默认值
if option.Header.Key == "" {
option.Header.Key = "session"
}
if option.Cookie.Path == "" {
option.Cookie.Path = "/"
}
// 默认加密解密方式
if option.Security == nil {
option.Security = DefaultSecurity{}
}
switch option.Security.(type) {
case DefaultSecurity:
if len(option.SecureKey) != 32 {
return nil, xerr.New("goclub/sesison: NewHub(store, option) option.SecureKey length must be 32")
}
}
if store == nil {
return nil, xerr.New("goclub/sesison: NewHub(store, option) store can not be nil")
}
hub = &Hub{
store: store,
option: option,
}
return hub, nil
}
type HubOption struct {
// (必填) sessionID 与 storeKey 的加密解密秘钥 设置长度为 32 的 []byte
SecureKey []byte
// cookie 相关设置
Cookie HubOptionCookie
// sesison 过期时间,默认8小时
SessionTTL time.Duration
// header 相关设置
Header HubOptionHeader
// header 相关设置
// 加密方式,不填则为 goclub/sesion 默认 aes 加密
Security Security
// 当sessionID 解码为 storeKey 后在 store 中不存在时触发
// 用于监控系统排查恶意攻击或 sessionID 过期
// ctx 可以用 ctx.WithValue 传递 requestID 便于排查问题
OnStoreKeyDoesNotExist func(ctx context.Context, sessionID string, storeKey string)
// 当请求的 sessionID 为空字符串时触发
// 用于监控系统排查问题
// ctx 可以用 ctx.WithValue 传递 requestID 便于排查问题
OnRequestSessionIDIsEmptyString func(ctx context.Context)
}
type HubOptionCookie struct {
// Name 默认为session_id, 建议设置为 项目名 + "_session_id"
Name string
// Path 默认为 "/"
Path string
Domain string
// Path 默认为 int(HubOption{}.SessionTTL.Seconds())
MaxAge int
Secure bool
}
type HubOptionHeader struct {
// Key 建议设置为 session (若留空则为 session)
Key string
}
// NewSessionID
// 微信小程序和 app 场景下可能在登录成功时可能需要手动创建 SessionID
// 所以提供 NewSessionID 发放
func (hub Hub) NewSessionID(ctx context.Context) (sessionID string, err error) {
storeKey := uuid.New().String()
var sessionIDBytes []byte
sessionIDBytes, err = hub.option.Security.Encrypt([]byte(storeKey), hub.option.SecureKey)
if err != nil {
return
}
sessionID = string(sessionIDBytes)
err = hub.store.InitSession(ctx, storeKey, hub.option.SessionTTL)
if err != nil {
return
}
return sessionID, nil
}
func (hub Hub) GetSessionBySessionID(ctx context.Context, sessionID string) (session Session, sessionExpired bool, err error) {
session, has, err := hub.getSessionByReadWriter(ctx, sessionID, nil)
if err != nil {
return
}
// GetSessionBySessionID 的场景一般是微信小程序或 app
// 这种场景当 key 错误应当返回key已过期 ,便于调用方告知客户端需要重新登录
// 过期 = key 不存在
sessionExpired = (has == false)
return
}
func (hub Hub) getSessionByReadWriter(ctx context.Context, sessionID string, rw SessionHttpReadWriter) (session Session, has bool, err error) {
if rw == nil {
rw = EmptyHttpReadWirter{}
}
if sessionID == "" {
// session 为空时候返回 has = false
// 如果返回错误,会降低 goclub/session 的易用性
if hub.option.OnRequestSessionIDIsEmptyString != nil {
hub.option.OnRequestSessionIDIsEmptyString(ctx)
}
return Session{}, false, nil
}
var storeKeyBytes []byte
storeKeyBytes, err = hub.option.Security.Decrypt([]byte(sessionID), hub.option.SecureKey)
if err != nil {
return Session{}, false, err
}
storeKey := string(storeKeyBytes)
session = Session{
sessionID: sessionID,
storeKey: storeKey,
hub: hub,
rw: rw,
}
// 此处的验证可避免 key 过期或恶意猜测key进行攻击
has, err = session.existed(ctx)
if err != nil {
return
}
if has == false && hub.option.OnStoreKeyDoesNotExist != nil {
hub.option.OnStoreKeyDoesNotExist(ctx, sessionID, storeKey)
}
// 实现自动续期
remainingTTL, err := session.hub.store.StoreKeyRemainingTTL(ctx, session.storeKey)
if err != nil {
return
}
if remainingTTL < session.hub.option.SessionTTL/2 {
err = session.hub.store.RenewTTL(ctx, session.storeKey, session.hub.option.SessionTTL)
if err != nil {
return
}
}
return
}
func (hub Hub) GetSessionByCookie(ctx context.Context, writer http.ResponseWriter, request *http.Request) (Session, error) {
rw := CookieReadWriter{
Writer: writer,
Request: request,
}
s, err := hub.GetSessionByReadWriter(ctx, rw)
return s, err
}
func (hub Hub) GetSessionByHeader(ctx context.Context, writer http.ResponseWriter, header http.Header) (Session, error) {
rw := HeaderReadWriter{
Writer: writer,
Header: header,
}
s, err := hub.GetSessionByReadWriter(ctx, rw)
return s, err
}