github.com/qiniu/go-sdk/v7@v7.27.0/auth/credentials.go (about) 1 package auth 2 3 import ( 4 "context" 5 "crypto/hmac" 6 "crypto/sha1" 7 "encoding/base64" 8 "fmt" 9 "net/http" 10 "net/textproto" 11 "sort" 12 "strings" 13 14 api "github.com/qiniu/go-sdk/v7" 15 "github.com/qiniu/go-sdk/v7/conf" 16 internal_io "github.com/qiniu/go-sdk/v7/internal/io" 17 "github.com/qiniu/go-sdk/v7/storagev2/defaults" 18 ) 19 20 const ( 21 IAMKeyLen = 33 22 IAMKeyPrefix = "IAM-" 23 24 AuthorizationPrefixQiniu = "Qiniu " 25 AuthorizationPrefixQBox = "QBox " 26 ) 27 28 // 七牛鉴权类,用于生成Qbox, Qiniu, Upload签名 29 // 30 // AK/SK可以从 https://portal.qiniu.com/user/key 获取 31 type Credentials struct { 32 AccessKey string 33 SecretKey []byte 34 } 35 36 // New 构建一个Credentials对象 37 func New(accessKey, secretKey string) *Credentials { 38 return &Credentials{accessKey, []byte(secretKey)} 39 } 40 41 // Default 构建默认的 Credentials 对象 42 func Default() *Credentials { 43 accessKey, secretKey, err := defaults.Credentials() 44 if err == nil && accessKey != "" && secretKey != "" { 45 return New(accessKey, secretKey) 46 } 47 return nil 48 } 49 50 // Sign 对数据进行签名,一般用于私有空间下载用途 51 func (ath *Credentials) Sign(data []byte) (token string) { 52 var ( 53 accessKey string 54 secretKey []byte 55 ) 56 if ath == nil { 57 if cred := Default(); cred != nil { 58 accessKey = cred.AccessKey 59 secretKey = cred.SecretKey 60 } 61 } else { 62 accessKey, secretKey = ath.AccessKey, ath.SecretKey 63 } 64 h := hmac.New(sha1.New, secretKey) 65 h.Write(data) 66 67 sign := base64.URLEncoding.EncodeToString(h.Sum(nil)) 68 return fmt.Sprintf("%s:%s", accessKey, sign) 69 } 70 71 // SignToken 根据t的类型对请求进行签名,并把token加入req中 72 func (ath *Credentials) AddToken(t TokenType, req *http.Request) error { 73 switch t { 74 case TokenQiniu: 75 token, sErr := ath.SignRequestV2(req) 76 if sErr != nil { 77 return sErr 78 } 79 req.Header.Add("Authorization", AuthorizationPrefixQiniu+token) 80 default: 81 token, err := ath.SignRequest(req) 82 if err != nil { 83 return err 84 } 85 req.Header.Add("Authorization", AuthorizationPrefixQBox+token) 86 } 87 return nil 88 } 89 90 // SignWithData 对数据进行签名,一般用于上传凭证的生成用途 91 func (ath *Credentials) SignWithData(b []byte) (token string) { 92 encodedData := base64.URLEncoding.EncodeToString(b) 93 sign := ath.Sign([]byte(encodedData)) 94 return fmt.Sprintf("%s:%s", sign, encodedData) 95 } 96 97 // IsIAMKey 判断AccessKey是否为IAM的Key 98 func (ath *Credentials) IsIAMKey() bool { 99 var accessKey string 100 if ath == nil { 101 if cred := Default(); cred != nil { 102 accessKey = cred.AccessKey 103 } 104 } else { 105 accessKey = ath.AccessKey 106 } 107 return len(accessKey) == IAMKeyLen*4/3 && 108 strings.HasPrefix(accessKey, IAMKeyPrefix) 109 } 110 111 func collectData(req *http.Request) (data []byte, err error) { 112 u := req.URL 113 s := u.Path 114 if u.RawQuery != "" { 115 s += "?" 116 s += u.RawQuery 117 } 118 s += "\n" 119 120 data = []byte(s) 121 if incBody(req) { 122 s2, rErr := api.BytesFromRequest(req) 123 if rErr != nil { 124 err = rErr 125 return 126 } 127 req.Body = internal_io.NewBytesNopCloser(s2) 128 data = append(data, s2...) 129 } 130 return 131 } 132 133 type ( 134 xQiniuHeaderItem struct { 135 HeaderName string 136 HeaderValue string 137 } 138 xQiniuHeaders []xQiniuHeaderItem 139 ) 140 141 func (headers xQiniuHeaders) Len() int { 142 return len(headers) 143 } 144 145 func (headers xQiniuHeaders) Less(i, j int) bool { 146 if headers[i].HeaderName < headers[j].HeaderName { 147 return true 148 } else if headers[i].HeaderName > headers[j].HeaderName { 149 return false 150 } else { 151 return headers[i].HeaderValue < headers[j].HeaderValue 152 } 153 } 154 155 func (headers xQiniuHeaders) Swap(i, j int) { 156 headers[i], headers[j] = headers[j], headers[i] 157 } 158 159 func collectDataV2(req *http.Request) (data []byte, err error) { 160 u := req.URL 161 162 // write method path?query 163 s := fmt.Sprintf("%s %s", req.Method, u.Path) 164 if u.RawQuery != "" { 165 s += "?" 166 s += u.RawQuery 167 } 168 169 // write host and post 170 s += "\nHost: " + req.Host + "\n" 171 172 // write content type 173 contentType := req.Header.Get("Content-Type") 174 if contentType == "" { 175 contentType = "application/x-www-form-urlencoded" 176 req.Header.Set("Content-Type", contentType) 177 } 178 s += fmt.Sprintf("Content-Type: %s\n", contentType) 179 180 xQiniuHeaders := make(xQiniuHeaders, 0, len(req.Header)) 181 for headerName := range req.Header { 182 if len(headerName) > len("X-Qiniu-") && strings.HasPrefix(headerName, "X-Qiniu-") { 183 xQiniuHeaders = append(xQiniuHeaders, xQiniuHeaderItem{ 184 HeaderName: textproto.CanonicalMIMEHeaderKey(headerName), 185 HeaderValue: req.Header.Get(headerName), 186 }) 187 } 188 } 189 190 if len(xQiniuHeaders) > 0 { 191 sort.Sort(xQiniuHeaders) 192 for _, xQiniuHeader := range xQiniuHeaders { 193 s += fmt.Sprintf("%s: %s\n", xQiniuHeader.HeaderName, xQiniuHeader.HeaderValue) 194 } 195 } 196 s += "\n" 197 198 data = []byte(s) 199 // write body 200 if incBodyV2(req) { 201 s2, rErr := api.BytesFromRequest(req) 202 if rErr != nil { 203 err = rErr 204 return 205 } 206 req.Body = internal_io.NewBytesNopCloser(s2) 207 data = append(data, s2...) 208 } 209 return 210 } 211 212 // SignRequest 对数据进行签名,一般用于管理凭证的生成 213 func (ath *Credentials) SignRequest(req *http.Request) (token string, err error) { 214 data, err := collectData(req) 215 if err != nil { 216 return 217 } 218 token = ath.Sign(data) 219 return 220 } 221 222 // SignRequestV2 对数据进行签名,一般用于高级管理凭证的生成 223 func (ath *Credentials) SignRequestV2(req *http.Request) (token string, err error) { 224 data, err := collectDataV2(req) 225 if err != nil { 226 return 227 } 228 token = ath.Sign(data) 229 return 230 } 231 232 // 管理凭证生成时,是否同时对request body进行签名 233 func incBody(req *http.Request) bool { 234 return req.Body != nil && req.Header.Get("Content-Type") == conf.CONTENT_TYPE_FORM 235 } 236 237 func incBodyV2(req *http.Request) bool { 238 contentType := req.Header.Get("Content-Type") 239 return req.Body != nil && (contentType == conf.CONTENT_TYPE_FORM || contentType == conf.CONTENT_TYPE_JSON) 240 } 241 242 // VerifyCallback 验证上传回调请求是否来自七牛 243 func (ath *Credentials) VerifyCallback(req *http.Request) (bool, error) { 244 auth := req.Header.Get("Authorization") 245 if auth == "" { 246 return false, nil 247 } 248 249 if strings.HasPrefix(auth, AuthorizationPrefixQiniu) { 250 token, err := ath.SignRequestV2(req) 251 if err != nil { 252 return false, err 253 } 254 return auth == AuthorizationPrefixQiniu+token, nil 255 } else { 256 257 token, err := ath.SignRequest(req) 258 if err != nil { 259 return false, err 260 } 261 return auth == AuthorizationPrefixQBox+token, nil 262 } 263 } 264 265 // Get 实现 CredentialsProvider 接口 266 func (c *Credentials) Get(ctx context.Context) (*Credentials, error) { 267 return c, nil 268 }