You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-library/vendor/github.com/qiniu/go-sdk/v7/auth/credentials.go

235 lines
5.5 KiB

2 years ago
package auth
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"net/textproto"
"sort"
"strings"
api "github.com/qiniu/go-sdk/v7"
"github.com/qiniu/go-sdk/v7/conf"
)
const (
IAMKeyLen = 33
IAMKeyPrefix = "IAM-"
AuthorizationPrefixQiniu = "Qiniu "
AuthorizationPrefixQBox = "QBox "
2 years ago
)
11 months ago
// 七牛鉴权类用于生成Qbox, Qiniu, Upload签名
//
2 years ago
// AK/SK可以从 https://portal.qiniu.com/user/key 获取
type Credentials struct {
AccessKey string
SecretKey []byte
}
// 构建一个Credentials对象
func New(accessKey, secretKey string) *Credentials {
return &Credentials{accessKey, []byte(secretKey)}
}
// Sign 对数据进行签名,一般用于私有空间下载用途
func (ath *Credentials) Sign(data []byte) (token string) {
h := hmac.New(sha1.New, ath.SecretKey)
h.Write(data)
sign := base64.URLEncoding.EncodeToString(h.Sum(nil))
return fmt.Sprintf("%s:%s", ath.AccessKey, sign)
}
// SignToken 根据t的类型对请求进行签名并把token加入req中
func (ath *Credentials) AddToken(t TokenType, req *http.Request) error {
switch t {
case TokenQiniu:
token, sErr := ath.SignRequestV2(req)
if sErr != nil {
return sErr
}
req.Header.Add("Authorization", AuthorizationPrefixQiniu+token)
2 years ago
default:
token, err := ath.SignRequest(req)
if err != nil {
return err
}
req.Header.Add("Authorization", AuthorizationPrefixQBox+token)
2 years ago
}
return nil
}
// SignWithData 对数据进行签名,一般用于上传凭证的生成用途
func (ath *Credentials) SignWithData(b []byte) (token string) {
encodedData := base64.URLEncoding.EncodeToString(b)
sign := ath.Sign([]byte(encodedData))
return fmt.Sprintf("%s:%s", sign, encodedData)
}
// IsIAMKey 判断AccessKey是否为IAM的Key
func (ath *Credentials) IsIAMKey() bool {
return len(ath.AccessKey) == IAMKeyLen*4/3 &&
strings.HasPrefix(ath.AccessKey, IAMKeyPrefix)
}
func collectData(req *http.Request) (data []byte, err error) {
u := req.URL
s := u.Path
if u.RawQuery != "" {
s += "?"
s += u.RawQuery
}
s += "\n"
data = []byte(s)
if incBody(req) {
s2, rErr := api.BytesFromRequest(req)
if rErr != nil {
err = rErr
return
}
req.Body = ioutil.NopCloser(bytes.NewReader(s2))
data = append(data, s2...)
}
return
}
type (
xQiniuHeaderItem struct {
HeaderName string
HeaderValue string
}
xQiniuHeaders []xQiniuHeaderItem
)
func (headers xQiniuHeaders) Len() int {
return len(headers)
}
func (headers xQiniuHeaders) Less(i, j int) bool {
if headers[i].HeaderName < headers[j].HeaderName {
return true
} else if headers[i].HeaderName > headers[j].HeaderName {
return false
} else {
return headers[i].HeaderValue < headers[j].HeaderValue
}
}
func (headers xQiniuHeaders) Swap(i, j int) {
headers[i], headers[j] = headers[j], headers[i]
}
func collectDataV2(req *http.Request) (data []byte, err error) {
u := req.URL
//write method path?query
s := fmt.Sprintf("%s %s", req.Method, u.Path)
if u.RawQuery != "" {
s += "?"
s += u.RawQuery
}
//write host and post
s += "\nHost: " + req.Host + "\n"
//write content type
contentType := req.Header.Get("Content-Type")
if contentType == "" {
contentType = "application/x-www-form-urlencoded"
req.Header.Set("Content-Type", contentType)
}
s += fmt.Sprintf("Content-Type: %s\n", contentType)
xQiniuHeaders := make(xQiniuHeaders, 0, len(req.Header))
for headerName := range req.Header {
if len(headerName) > len("X-Qiniu-") && strings.HasPrefix(headerName, "X-Qiniu-") {
xQiniuHeaders = append(xQiniuHeaders, xQiniuHeaderItem{
HeaderName: textproto.CanonicalMIMEHeaderKey(headerName),
HeaderValue: req.Header.Get(headerName),
})
}
}
if len(xQiniuHeaders) > 0 {
sort.Sort(xQiniuHeaders)
for _, xQiniuHeader := range xQiniuHeaders {
s += fmt.Sprintf("%s: %s\n", xQiniuHeader.HeaderName, xQiniuHeader.HeaderValue)
}
}
s += "\n"
data = []byte(s)
//write body
if incBodyV2(req) {
s2, rErr := api.BytesFromRequest(req)
if rErr != nil {
err = rErr
return
}
req.Body = ioutil.NopCloser(bytes.NewReader(s2))
data = append(data, s2...)
}
return
}
// SignRequest 对数据进行签名,一般用于管理凭证的生成
func (ath *Credentials) SignRequest(req *http.Request) (token string, err error) {
data, err := collectData(req)
if err != nil {
return
}
token = ath.Sign(data)
return
}
// SignRequestV2 对数据进行签名,一般用于高级管理凭证的生成
func (ath *Credentials) SignRequestV2(req *http.Request) (token string, err error) {
data, err := collectDataV2(req)
if err != nil {
return
}
token = ath.Sign(data)
return
}
// 管理凭证生成时是否同时对request body进行签名
func incBody(req *http.Request) bool {
return req.Body != nil && req.Header.Get("Content-Type") == conf.CONTENT_TYPE_FORM
}
func incBodyV2(req *http.Request) bool {
contentType := req.Header.Get("Content-Type")
return req.Body != nil && (contentType == conf.CONTENT_TYPE_FORM || contentType == conf.CONTENT_TYPE_JSON)
}
// VerifyCallback 验证上传回调请求是否来自七牛
func (ath *Credentials) VerifyCallback(req *http.Request) (bool, error) {
auth := req.Header.Get("Authorization")
if auth == "" {
return false, nil
}
if strings.HasPrefix(auth, AuthorizationPrefixQiniu) {
token, err := ath.SignRequestV2(req)
if err != nil {
return false, err
}
return auth == AuthorizationPrefixQiniu+token, nil
} else {
2 years ago
token, err := ath.SignRequest(req)
if err != nil {
return false, err
}
return auth == AuthorizationPrefixQBox+token, nil
}
2 years ago
}