You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

aws_auth.go 5.8 kB

10 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package http
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/sha256"
  6. "encoding/hex"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "strings"
  11. "time"
  12. "github.com/aws/aws-sdk-go-v2/aws"
  13. v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
  14. "github.com/aws/aws-sdk-go-v2/credentials"
  15. "github.com/gin-gonic/gin"
  16. "gitlink.org.cn/cloudream/common/consts/errorcode"
  17. "gitlink.org.cn/cloudream/common/pkgs/logger"
  18. "gitlink.org.cn/cloudream/storage/client/internal/config"
  19. )
  20. const (
  21. AuthRegion = "any"
  22. AuthService = "jcs"
  23. AuthorizationHeader = "Authorization"
  24. )
  25. type AWSAuth struct {
  26. cred aws.Credentials
  27. signer *v4.Signer
  28. }
  29. func NewAWSAuth(accessKey string, secretKey string) (*AWSAuth, error) {
  30. prod := credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")
  31. cred, err := prod.Retrieve(context.TODO())
  32. if err != nil {
  33. return nil, err
  34. }
  35. return &AWSAuth{
  36. cred: cred,
  37. signer: v4.NewSigner(),
  38. }, nil
  39. }
  40. func (a *AWSAuth) Auth(c *gin.Context) {
  41. authorizationHeader := c.GetHeader(AuthorizationHeader)
  42. if authorizationHeader == "" {
  43. c.AbortWithStatusJSON(http.StatusBadRequest, Failed(errorcode.Unauthorized, "authorization header is missing"))
  44. return
  45. }
  46. _, headers, reqSig, err := parseAuthorizationHeader(authorizationHeader)
  47. if err != nil {
  48. c.AbortWithStatusJSON(http.StatusBadRequest, Failed(errorcode.Unauthorized, "invalid Authorization header format"))
  49. return
  50. }
  51. // 限制请求体大小
  52. rd := io.LimitReader(c.Request.Body, config.Cfg().MaxHTTPBodySize)
  53. body, err := io.ReadAll(rd)
  54. if err != nil {
  55. c.AbortWithStatusJSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "read request body failed"))
  56. return
  57. }
  58. payloadHash := sha256.Sum256(body)
  59. hexPayloadHash := hex.EncodeToString(payloadHash[:])
  60. // 构造验签用的请求
  61. verifyReq, err := http.NewRequest(c.Request.Method, c.Request.URL.String(), nil)
  62. if err != nil {
  63. c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.OperationFailed, err.Error()))
  64. return
  65. }
  66. for _, h := range headers {
  67. verifyReq.Header.Add(h, c.Request.Header.Get(h))
  68. }
  69. verifyReq.Host = c.Request.Host
  70. timestamp, err := time.Parse("20060102T150405Z", c.GetHeader("X-Amz-Date"))
  71. if err != nil {
  72. c.AbortWithStatusJSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "invalid X-Amz-Date header format"))
  73. return
  74. }
  75. signer := v4.NewSigner()
  76. err = signer.SignHTTP(context.TODO(), a.cred, verifyReq, hexPayloadHash, AuthService, AuthRegion, timestamp)
  77. if err != nil {
  78. logger.Warnf("sign request: %v", err)
  79. c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.OperationFailed, "sign request failed"))
  80. return
  81. }
  82. verifySig := a.getSignature(verifyReq)
  83. if !strings.EqualFold(verifySig, reqSig) {
  84. c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.Unauthorized, "signature mismatch"))
  85. return
  86. }
  87. c.Request.Body = io.NopCloser(bytes.NewReader(body))
  88. c.Next()
  89. }
  90. func (a *AWSAuth) AuthWithoutBody(c *gin.Context) {
  91. authorizationHeader := c.GetHeader(AuthorizationHeader)
  92. if authorizationHeader == "" {
  93. c.AbortWithStatusJSON(http.StatusBadRequest, Failed(errorcode.Unauthorized, "authorization header is missing"))
  94. return
  95. }
  96. _, headers, reqSig, err := parseAuthorizationHeader(authorizationHeader)
  97. if err != nil {
  98. c.AbortWithStatusJSON(http.StatusBadRequest, Failed(errorcode.Unauthorized, "invalid Authorization header format"))
  99. return
  100. }
  101. // 构造验签用的请求
  102. verifyReq, err := http.NewRequest(c.Request.Method, c.Request.URL.String(), nil)
  103. if err != nil {
  104. c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.OperationFailed, err.Error()))
  105. return
  106. }
  107. for _, h := range headers {
  108. verifyReq.Header.Add(h, c.Request.Header.Get(h))
  109. }
  110. verifyReq.Host = c.Request.Host
  111. timestamp, err := time.Parse("20060102T150405Z", c.GetHeader("X-Amz-Date"))
  112. if err != nil {
  113. c.AbortWithStatusJSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "invalid X-Amz-Date header format"))
  114. return
  115. }
  116. err = a.signer.SignHTTP(context.TODO(), a.cred, verifyReq, "", AuthService, AuthRegion, timestamp)
  117. if err != nil {
  118. logger.Warnf("sign request: %v", err)
  119. c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.OperationFailed, "sign request failed"))
  120. return
  121. }
  122. verifySig := a.getSignature(verifyReq)
  123. if strings.EqualFold(verifySig, reqSig) {
  124. c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.Unauthorized, "signature mismatch"))
  125. return
  126. }
  127. c.Next()
  128. }
  129. // 解析 Authorization 头部
  130. func parseAuthorizationHeader(authorizationHeader string) (string, []string, string, error) {
  131. if !strings.HasPrefix(authorizationHeader, "AWS4-HMAC-SHA256 ") {
  132. return "", nil, "", fmt.Errorf("invalid Authorization header format")
  133. }
  134. authorizationHeader = strings.TrimPrefix(authorizationHeader, "AWS4-HMAC-SHA256")
  135. parts := strings.Split(authorizationHeader, ",")
  136. if len(parts) != 3 {
  137. return "", nil, "", fmt.Errorf("invalid Authorization header format")
  138. }
  139. var credential, signedHeaders, signature string
  140. for _, part := range parts {
  141. part = strings.TrimSpace(part)
  142. if strings.HasPrefix(part, "Credential=") {
  143. credential = strings.TrimPrefix(part, "Credential=")
  144. }
  145. if strings.HasPrefix(part, "SignedHeaders=") {
  146. signedHeaders = strings.TrimPrefix(part, "SignedHeaders=")
  147. }
  148. if strings.HasPrefix(part, "Signature=") {
  149. signature = strings.TrimPrefix(part, "Signature=")
  150. }
  151. }
  152. if credential == "" || signedHeaders == "" || signature == "" {
  153. return "", nil, "", fmt.Errorf("missing necessary parts in Authorization header")
  154. }
  155. headers := strings.Split(signedHeaders, ";")
  156. return credential, headers, signature, nil
  157. }
  158. func (a *AWSAuth) getSignature(req *http.Request) string {
  159. auth := req.Header.Get(AuthorizationHeader)
  160. idx := strings.Index(auth, "Signature=")
  161. if idx == -1 {
  162. return ""
  163. }
  164. return auth[idx+len("Signature="):]
  165. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。