credentials.go 5.5 KB

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