oauth.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. package oauth
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "github.com/silenceper/wechat/v2/officialaccount/context"
  8. "github.com/silenceper/wechat/v2/util"
  9. )
  10. const (
  11. redirectOauthURL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect"
  12. webAppRedirectOauthURL = "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect"
  13. accessTokenURL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"
  14. refreshAccessTokenURL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s"
  15. userInfoURL = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=%s"
  16. checkAccessTokenURL = "https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s"
  17. )
  18. // Oauth 保存用户授权信息
  19. type Oauth struct {
  20. *context.Context
  21. }
  22. // NewOauth 实例化授权信息
  23. func NewOauth(context *context.Context) *Oauth {
  24. auth := new(Oauth)
  25. auth.Context = context
  26. return auth
  27. }
  28. // GetRedirectURL 获取跳转的url地址
  29. func (oauth *Oauth) GetRedirectURL(redirectURI, scope, state string) (string, error) {
  30. // url encode
  31. urlStr := url.QueryEscape(redirectURI)
  32. return fmt.Sprintf(redirectOauthURL, oauth.AppID, urlStr, scope, state), nil
  33. }
  34. // GetWebAppRedirectURL 获取网页应用跳转的url地址
  35. func (oauth *Oauth) GetWebAppRedirectURL(redirectURI, scope, state string) (string, error) {
  36. urlStr := url.QueryEscape(redirectURI)
  37. return fmt.Sprintf(webAppRedirectOauthURL, oauth.AppID, urlStr, scope, state), nil
  38. }
  39. // Redirect 跳转到网页授权
  40. func (oauth *Oauth) Redirect(writer http.ResponseWriter, req *http.Request, redirectURI, scope, state string) error {
  41. location, err := oauth.GetRedirectURL(redirectURI, scope, state)
  42. if err != nil {
  43. return err
  44. }
  45. http.Redirect(writer, req, location, http.StatusFound)
  46. return nil
  47. }
  48. // ResAccessToken 获取用户授权access_token的返回结果
  49. type ResAccessToken struct {
  50. util.CommonError
  51. AccessToken string `json:"access_token"`
  52. ExpiresIn int64 `json:"expires_in"`
  53. RefreshToken string `json:"refresh_token"`
  54. OpenID string `json:"openid"`
  55. Scope string `json:"scope"`
  56. // IsSnapShotUser 是否为快照页模式虚拟账号,只有当用户是快照页模式虚拟账号时返回,值为1
  57. // 公众号文档 https://developers.weixin.qq.com/community/minihome/doc/000c2c34068880629ced91a2f56001
  58. IsSnapShotUser int `json:"is_snapshotuser"`
  59. // UnionID 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
  60. // 公众号文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
  61. UnionID string `json:"unionid"`
  62. }
  63. // GetUserAccessToken 通过网页授权的code 换取access_token(区别于context中的access_token)
  64. func (oauth *Oauth) GetUserAccessToken(code string) (result ResAccessToken, err error) {
  65. urlStr := fmt.Sprintf(accessTokenURL, oauth.AppID, oauth.AppSecret, code)
  66. var response []byte
  67. response, err = util.HTTPGet(urlStr)
  68. if err != nil {
  69. return
  70. }
  71. err = json.Unmarshal(response, &result)
  72. if err != nil {
  73. return
  74. }
  75. if result.ErrCode != 0 {
  76. err = fmt.Errorf("GetUserAccessToken error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
  77. return
  78. }
  79. return
  80. }
  81. // RefreshAccessToken 刷新access_token
  82. func (oauth *Oauth) RefreshAccessToken(refreshToken string) (result ResAccessToken, err error) {
  83. urlStr := fmt.Sprintf(refreshAccessTokenURL, oauth.AppID, refreshToken)
  84. var response []byte
  85. response, err = util.HTTPGet(urlStr)
  86. if err != nil {
  87. return
  88. }
  89. err = json.Unmarshal(response, &result)
  90. if err != nil {
  91. return
  92. }
  93. if result.ErrCode != 0 {
  94. err = fmt.Errorf("GetUserAccessToken error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
  95. return
  96. }
  97. return
  98. }
  99. // CheckAccessToken 检验access_token是否有效
  100. func (oauth *Oauth) CheckAccessToken(accessToken, openID string) (b bool, err error) {
  101. urlStr := fmt.Sprintf(checkAccessTokenURL, accessToken, openID)
  102. var response []byte
  103. response, err = util.HTTPGet(urlStr)
  104. if err != nil {
  105. return
  106. }
  107. var result util.CommonError
  108. err = json.Unmarshal(response, &result)
  109. if err != nil {
  110. return
  111. }
  112. if result.ErrCode != 0 {
  113. b = false
  114. return
  115. }
  116. b = true
  117. return
  118. }
  119. // UserInfo 用户授权获取到用户信息
  120. type UserInfo struct {
  121. util.CommonError
  122. OpenID string `json:"openid"`
  123. Nickname string `json:"nickname"`
  124. Sex int32 `json:"sex"`
  125. Province string `json:"province"`
  126. City string `json:"city"`
  127. Country string `json:"country"`
  128. HeadImgURL string `json:"headimgurl"`
  129. Privilege []string `json:"privilege"`
  130. Unionid string `json:"unionid"`
  131. }
  132. // GetUserInfo 如果scope为 snsapi_userinfo 则可以通过此方法获取到用户基本信息
  133. func (oauth *Oauth) GetUserInfo(accessToken, openID, lang string) (result UserInfo, err error) {
  134. if lang == "" {
  135. lang = "zh_CN"
  136. }
  137. urlStr := fmt.Sprintf(userInfoURL, accessToken, openID, lang)
  138. var response []byte
  139. response, err = util.HTTPGet(urlStr)
  140. if err != nil {
  141. return
  142. }
  143. err = json.Unmarshal(response, &result)
  144. if err != nil {
  145. return
  146. }
  147. if result.ErrCode != 0 {
  148. err = fmt.Errorf("GetUserInfo error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
  149. return
  150. }
  151. return
  152. }