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  }