oauth.go 6.9 KB

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