verify.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package verify
  2. import (
  3. "crypto/md5"
  4. "encoding/hex"
  5. "net/url"
  6. "strings"
  7. "sync"
  8. "time"
  9. "go-common/library/ecode"
  10. "go-common/library/log"
  11. bm "go-common/library/net/http/blademaster"
  12. "go-common/library/net/metadata"
  13. xtime "go-common/library/time"
  14. )
  15. const (
  16. _secretURI = "/api/getsecret"
  17. )
  18. // Verify is is the verify model.
  19. type Verify struct {
  20. lock sync.RWMutex
  21. keys map[string]string
  22. secretURI string
  23. client *bm.Client
  24. }
  25. // Config is the verify config model.
  26. type Config struct {
  27. OpenServiceHost string
  28. HTTPClient *bm.ClientConfig
  29. }
  30. var _defaultConfig = &Config{
  31. OpenServiceHost: "http://open.bilibili.co",
  32. HTTPClient: &bm.ClientConfig{
  33. App: &bm.App{
  34. Key: "53e2fa226f5ad348",
  35. Secret: "3cf6bd1b0ff671021da5f424fea4b04a",
  36. },
  37. Dial: xtime.Duration(time.Millisecond * 100),
  38. Timeout: xtime.Duration(time.Millisecond * 300),
  39. KeepAlive: xtime.Duration(time.Second * 60),
  40. },
  41. }
  42. // New will create a verify middleware by given config.
  43. // panic on conf is nil.
  44. func New(conf *Config) *Verify {
  45. if conf == nil {
  46. conf = _defaultConfig
  47. }
  48. v := &Verify{
  49. keys: make(map[string]string),
  50. client: bm.NewClient(conf.HTTPClient),
  51. secretURI: conf.OpenServiceHost + _secretURI,
  52. }
  53. return v
  54. }
  55. func (v *Verify) verify(ctx *bm.Context) error {
  56. req := ctx.Request
  57. params := req.Form
  58. if req.Method == "POST" {
  59. // Give priority to sign in url query, otherwise check sign in post form.
  60. q := req.URL.Query()
  61. if q.Get("sign") != "" {
  62. params = q
  63. }
  64. }
  65. // check timestamp is not empty (TODO : Check if out of some seconds.., like 100s)
  66. if params.Get("ts") == "" {
  67. log.Error("ts is empty")
  68. return ecode.RequestErr
  69. }
  70. sign := params.Get("sign")
  71. params.Del("sign")
  72. defer params.Set("sign", sign)
  73. sappkey := params.Get("appkey")
  74. v.lock.RLock()
  75. secret, ok := v.keys[sappkey]
  76. v.lock.RUnlock()
  77. if !ok {
  78. fetched, err := v.fetchSecret(ctx, sappkey)
  79. if err != nil {
  80. return err
  81. }
  82. v.lock.Lock()
  83. v.keys[sappkey] = fetched
  84. v.lock.Unlock()
  85. secret = fetched
  86. }
  87. if hsign := Sign(params, sappkey, secret, true); hsign != sign {
  88. if hsign1 := Sign(params, sappkey, secret, false); hsign1 != sign {
  89. log.Error("Get sign: %s, expect %s", sign, hsign)
  90. return ecode.SignCheckErr
  91. }
  92. }
  93. return nil
  94. }
  95. // Verify will inject into handler func as verify required
  96. func (v *Verify) Verify(ctx *bm.Context) {
  97. if err := v.verify(ctx); err != nil {
  98. ctx.JSON(nil, err)
  99. ctx.Abort()
  100. return
  101. }
  102. }
  103. // VerifyUser is used to mark path as verify and mid required.
  104. func (v *Verify) VerifyUser(ctx *bm.Context) {
  105. if err := v.verify(ctx); err != nil {
  106. ctx.JSON(nil, err)
  107. ctx.Abort()
  108. return
  109. }
  110. var midReq struct {
  111. Mid int64 `form:"mid" validate:"required"`
  112. }
  113. if err := ctx.Bind(&midReq); err != nil {
  114. return
  115. }
  116. ctx.Set("mid", midReq.Mid)
  117. if md, ok := metadata.FromContext(ctx); ok {
  118. md[metadata.Mid] = midReq.Mid
  119. }
  120. }
  121. // Sign is used to sign form params by given condition.
  122. func Sign(params url.Values, appkey string, secret string, lower bool) string {
  123. data := params.Encode()
  124. if strings.IndexByte(data, '+') > -1 {
  125. data = strings.Replace(data, "+", "%20", -1)
  126. }
  127. if lower {
  128. data = strings.ToLower(data)
  129. }
  130. digest := md5.Sum([]byte(data + secret))
  131. return hex.EncodeToString(digest[:])
  132. }
  133. func (v *Verify) fetchSecret(ctx *bm.Context, appkey string) (string, error) {
  134. params := url.Values{}
  135. var resp struct {
  136. Code int `json:"code"`
  137. Data struct {
  138. AppSecret string `json:"app_secret"`
  139. } `json:"data"`
  140. }
  141. params.Set("sappkey", appkey)
  142. if err := v.client.Get(ctx, v.secretURI, metadata.String(ctx, metadata.RemoteIP), params, &resp); err != nil {
  143. return "", err
  144. }
  145. if resp.Code != 0 || resp.Data.AppSecret == "" {
  146. log.Error("Failed to fetch secret with request(%s, %s) code(%d)", v.secretURI, params.Encode(), resp.Code)
  147. if resp.Code != 0 {
  148. return "", ecode.Int(resp.Code)
  149. }
  150. return "", ecode.ServerErr
  151. }
  152. return resp.Data.AppSecret, nil
  153. }