forked from aiyi/go-user
-
Notifications
You must be signed in to change notification settings - Fork 0
/
token.go
124 lines (104 loc) · 3.87 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
package token
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"github.com/chanxuehong/go-user/model"
"github.com/chanxuehong/go-user/securitykey"
)
const (
AuthTypeGuest = "guest" // 游客
AuthTypeEmailPassword = "email_password" // 邮箱+密码
AuthTypeEmailCheckCode = "email_checkcode" // 邮箱+校验码, 校验码推送到邮箱
AuthTypePhonePassword = "phone_password" // 手机+密码
AuthTypePhoneCheckCode = "phone_checkcode" // 手机+校验码, 校验码短信推送给手机
AuthTypeOAuthQQ = "oauth_qq" // QQ oauth
AuthTypeOAuthWechat = "oauth_wechat" // 微信 oauth
AuthTypeOAuthWeibo = "oauth_weibo" // 微博 oauth
)
func GetBindType(AuthType string) (typ model.BindType, err error) {
switch AuthType {
case AuthTypeEmailPassword, AuthTypeEmailCheckCode:
typ = model.BindTypeEmail
return
case AuthTypePhonePassword, AuthTypePhoneCheckCode:
typ = model.BindTypePhone
return
case AuthTypeOAuthQQ:
typ = model.BindTypeQQ
return
case AuthTypeOAuthWechat:
typ = model.BindTypeWechat
return
case AuthTypeOAuthWeibo:
typ = model.BindTypeWeibo
return
default:
err = errors.New("Invalid AuthType")
return
}
}
// 客户端访问 API 的令牌, 客户端和服务器交互的数据结构
type Token struct {
SessionId string `json:"sid"` // 服务器索引 Session 的 key
TokenId string `json:"token_id"` // token 的标识, 每次刷新 token 改变此值
AuthType string `json:"auth_type"` // token 的认证类型
ExpirationAccess int64 `json:"exp_access"` // 该 token 的过期时间; 0 表示永远有效
ExpirationRefresh int64 `json:"exp_refresh"` // 刷新 token 的截至时间, 固定值, 不会变化; 0 表示永远有效
Signatrue string `json:"-"` // 和客户端交互的 token 签名部分; 在 Token.Encode 或者 Token.Decode 才会获取到正确的值
}
// trim(url_base64(json(token))) + "." + hex(hmac-sha256(base64_str))
func (token *Token) Encode() ([]byte, error) {
const signatureLen = 64 // hmac-sha256
jsonBytes, err := json.Marshal(token)
if err != nil {
return nil, err
}
base64BytesLen := base64.URLEncoding.EncodedLen(len(jsonBytes))
buf := make([]byte, base64BytesLen+1+signatureLen)
base64Bytes := buf[:base64BytesLen]
base64.URLEncoding.Encode(base64Bytes, jsonBytes)
// 去掉 base64 编码尾部的 '='
base64Bytes = base64Trim(base64Bytes)
base64BytesLen = len(base64Bytes)
signatureOffset := base64BytesLen + 1
buf = buf[:signatureOffset+signatureLen]
buf[base64BytesLen] = '.'
signature := buf[signatureOffset:]
Hash := hmac.New(sha256.New, securitykey.Key)
Hash.Write(base64Bytes)
hex.Encode(signature, Hash.Sum(nil))
token.Signatrue = string(signature)
return buf, nil
}
var tokenBytesSplitSep = []byte{'.'}
// trim(url_base64(json(token))) + "." + hex(hmac-sha256(base64_str))
func (token *Token) Decode(tokenBytes []byte) error {
const signatureLen = 64 // hmac-sha256
bytesArray := bytes.Split(tokenBytes, tokenBytesSplitSep)
if len(bytesArray) < 2 {
return errors.New("invalid token bytes")
}
// 验证签名
signatrue := make([]byte, signatureLen)
Hash := hmac.New(sha256.New, securitykey.Key)
Hash.Write(bytesArray[0])
hex.Encode(signatrue, Hash.Sum(nil))
if !bytes.Equal(signatrue, bytesArray[1]) {
return errors.New("invalid token bytes, signature mismatch")
}
// 解码
temp := signatrue[:4] // signatrue 不再使用, 利用其空间
copy(temp, tokenBytes[len(bytesArray[0]):]) // 保护 tokenBytes
defer func() {
copy(tokenBytes[len(bytesArray[0]):], temp) // 恢复 tokenBytes
token.Signatrue = string(bytesArray[1])
}()
base64Bytes := base64Pad(bytesArray[0])
base64Decoder := base64.NewDecoder(base64.URLEncoding, bytes.NewReader(base64Bytes))
return json.NewDecoder(base64Decoder).Decode(token)
}