forked from xen0n/go-workwx
-
Notifications
You must be signed in to change notification settings - Fork 0
/
token.go
245 lines (214 loc) · 7.14 KB
/
token.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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
package workwx
import (
"context"
"sync"
"time"
"github.com/cenkalti/backoff/v4"
)
// ITokenProvider 是鉴权 token 的外部提供者需要实现的 interface。可用于官方所谓
// 使用“中控服务”集中提供、刷新 token 的场景。
//
// 不同类型的 tokens(如 access token、JSAPI token 等)都是这个 interface 提供,
// 实现方需要自行掌握 token 的类别,避免在 client 构造函数的选项中传入错误的种类。
type ITokenProvider interface {
// GetToken 取回一个 token。有可能被并发调用。
GetToken(context.Context) (string, error)
}
type tokenInfo struct {
token string
expiresIn time.Duration
}
type token struct {
mutex *sync.RWMutex
tokenInfo
lastRefresh time.Time
getTokenFunc func() (tokenInfo, error)
externalProvider ITokenProvider
}
func newToken(
externalProvider ITokenProvider,
refresher func() (tokenInfo, error),
) *token {
if externalProvider != nil {
return &token{
externalProvider: externalProvider,
}
}
return &token{
mutex: &sync.RWMutex{},
getTokenFunc: refresher,
}
}
func (t *token) usingExternalProvider() bool {
return t.externalProvider != nil
}
// getAccessToken 获取 access token
func (c *WorkwxApp) getAccessToken() (tokenInfo, error) {
get, err := c.execGetAccessToken(reqAccessToken{
CorpID: c.CorpID,
CorpSecret: c.CorpSecret,
})
if err != nil {
return tokenInfo{}, err
}
return tokenInfo{token: get.AccessToken, expiresIn: time.Duration(get.ExpiresInSecs)}, nil
}
// SpawnAccessTokenRefresher 启动该 app 的 access token 刷新 goroutine
//
// 如果使用了外部 token provider 提供 access token 则没有必要调用此方法:调用效果为空操作。
//
// NOTE: 该 goroutine 本身没有 keep-alive 逻辑,需要自助保活
func (c *WorkwxApp) SpawnAccessTokenRefresher() {
ctx := context.Background()
c.SpawnAccessTokenRefresherWithContext(ctx)
}
// SpawnAccessTokenRefresherWithContext 启动该 app 的 access token 刷新 goroutine
// 可以通过 context cancellation 停止此 goroutine
//
// 如果使用了外部 token provider 提供 access token 则没有必要调用此方法:调用效果为空操作。
//
// NOTE: 该 goroutine 本身没有 keep-alive 逻辑,需要自助保活
func (c *WorkwxApp) SpawnAccessTokenRefresherWithContext(ctx context.Context) {
if c.accessToken.usingExternalProvider() {
return
}
go c.accessToken.tokenRefresher(ctx)
}
// GetJSAPITicket 获取 JSAPI_ticket
func (c *WorkwxApp) GetJSAPITicket() (string, error) {
return c.jsapiTicket.getToken()
}
// getJSAPITicket 获取 JSAPI_ticket
func (c *WorkwxApp) getJSAPITicket() (tokenInfo, error) {
get, err := c.execGetJSAPITicket(reqJSAPITicket{})
if err != nil {
return tokenInfo{}, err
}
return tokenInfo{token: get.Ticket, expiresIn: time.Duration(get.ExpiresInSecs)}, nil
}
// SpawnJSAPITicketRefresher 启动该 app 的 JSAPI_ticket 刷新 goroutine
//
// 如果使用了外部 token provider 提供 JSAPI ticket 则没有必要调用此方法:调用效果为空操作。
//
// NOTE: 该 goroutine 本身没有 keep-alive 逻辑,需要自助保活
func (c *WorkwxApp) SpawnJSAPITicketRefresher() {
ctx := context.Background()
c.SpawnJSAPITicketRefresherWithContext(ctx)
}
// SpawnJSAPITicketRefresherWithContext 启动该 app 的 JSAPI_ticket 刷新 goroutine
// 可以通过 context cancellation 停止此 goroutine
//
// 如果使用了外部 token provider 提供 JSAPI ticket 则没有必要调用此方法:调用效果为空操作。
//
// NOTE: 该 goroutine 本身没有 keep-alive 逻辑,需要自助保活
func (c *WorkwxApp) SpawnJSAPITicketRefresherWithContext(ctx context.Context) {
if c.jsapiTicket.usingExternalProvider() {
return
}
go c.jsapiTicket.tokenRefresher(ctx)
}
// GetJSAPITicketAgentConfig 获取 JSAPI_ticket_agent_config
func (c *WorkwxApp) GetJSAPITicketAgentConfig() (string, error) {
return c.jsapiTicketAgentConfig.getToken()
}
// getJSAPITicketAgentConfig 获取 JSAPI_ticket_agent_config
func (c *WorkwxApp) getJSAPITicketAgentConfig() (tokenInfo, error) {
get, err := c.execGetJSAPITicketAgentConfig(reqJSAPITicketAgentConfig{})
if err != nil {
return tokenInfo{}, err
}
return tokenInfo{token: get.Ticket, expiresIn: time.Duration(get.ExpiresInSecs)}, nil
}
// SpawnJSAPITicketAgentConfigRefresher 启动该 app 的 JSAPI_ticket_agent_config 刷新 goroutine
//
// 如果使用了外部 token provider 提供 JSAPI ticket agent config 则没有必要调用此方法:调用效果为空操作。
//
// NOTE: 该 goroutine 本身没有 keep-alive 逻辑,需要自助保活
func (c *WorkwxApp) SpawnJSAPITicketAgentConfigRefresher() {
ctx := context.Background()
c.SpawnJSAPITicketAgentConfigRefresherWithContext(ctx)
}
// SpawnJSAPITicketAgentConfigRefresherWithContext 启动该 app 的 JSAPI_ticket_agent_config 刷新 goroutine
// 可以通过 context cancellation 停止此 goroutine
//
// 如果使用了外部 token provider 提供 JSAPI ticket agent config 则没有必要调用此方法:调用效果为空操作。
//
// NOTE: 该 goroutine 本身没有 keep-alive 逻辑,需要自助保活
func (c *WorkwxApp) SpawnJSAPITicketAgentConfigRefresherWithContext(ctx context.Context) {
if c.jsapiTicketAgentConfig.usingExternalProvider() {
return
}
go c.jsapiTicketAgentConfig.tokenRefresher(ctx)
}
func (t *token) getToken() (string, error) {
if t.externalProvider != nil {
tok, err := t.externalProvider.GetToken(context.TODO())
if err != nil {
return "", err
}
return tok, nil
}
// intensive mutex juggling action
t.mutex.RLock()
if t.token == "" {
t.mutex.RUnlock() // RWMutex doesn't like recursive locking
err := t.syncToken()
if err != nil {
return "", err
}
t.mutex.RLock()
}
tokenToUse := t.token
t.mutex.RUnlock()
return tokenToUse, nil
}
func (t *token) syncToken() error {
get, err := t.getTokenFunc()
if err != nil {
return err
}
t.mutex.Lock()
defer t.mutex.Unlock()
t.token = get.token
t.expiresIn = get.expiresIn * time.Second
t.lastRefresh = time.Now()
return nil
}
func (t *token) tokenRefresher(ctx context.Context) {
const refreshTimeWindow = 30 * time.Minute
const minRefreshDuration = 5 * time.Second
var waitDuration time.Duration
for {
select {
case <-time.After(waitDuration):
retryer := backoff.WithContext(backoff.NewExponentialBackOff(), ctx)
if err := backoff.Retry(t.syncToken, retryer); err != nil {
// TODO: logging
_ = err
}
waitUntilTime := t.lastRefresh.Add(t.expiresIn).Add(-refreshTimeWindow)
waitDuration = time.Until(waitUntilTime)
if waitDuration < minRefreshDuration {
waitDuration = minRefreshDuration
}
case <-ctx.Done():
return
}
}
}
// JSCode2Session 临时登录凭证校验
func (c *WorkwxApp) JSCode2Session(jscode string) (*JSCodeSession, error) {
resp, err := c.execJSCode2Session(reqJSCode2Session{JSCode: jscode})
if err != nil {
return nil, err
}
return &resp.JSCodeSession, nil
}
// AuthCode2UserInfo 获取访问用户身份
func (c *WorkwxApp) AuthCode2UserInfo(code string) (*AuthCodeUserInfo, error) {
resp, err := c.execAuthCode2UserInfo(reqAuthCode2UserInfo{Code: code})
if err != nil {
return nil, err
}
return &resp.AuthCodeUserInfo, nil
}