package oauth import ( ctx2 "context" "encoding/json" "fmt" "net/http" "net/url" "github.com/silenceper/wechat/v2/officialaccount/context" "github.com/silenceper/wechat/v2/util" ) const ( redirectOauthURL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect" webAppRedirectOauthURL = "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect" accessTokenURL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code" refreshAccessTokenURL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s" userInfoURL = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=%s" checkAccessTokenURL = "https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s" ) // Oauth 保存用户授权信息 type Oauth struct { *context.Context } // NewOauth 实例化授权信息 func NewOauth(context *context.Context) *Oauth { auth := new(Oauth) auth.Context = context return auth } // GetRedirectURL 获取跳转的url地址 func (oauth *Oauth) GetRedirectURL(redirectURI, scope, state string) (string, error) { // url encode urlStr := url.QueryEscape(redirectURI) return fmt.Sprintf(redirectOauthURL, oauth.AppID, urlStr, scope, state), nil } // GetWebAppRedirectURL 获取网页应用跳转的url地址 func (oauth *Oauth) GetWebAppRedirectURL(redirectURI, scope, state string) (string, error) { urlStr := url.QueryEscape(redirectURI) return fmt.Sprintf(webAppRedirectOauthURL, oauth.AppID, urlStr, scope, state), nil } // Redirect 跳转到网页授权 func (oauth *Oauth) Redirect(writer http.ResponseWriter, req *http.Request, redirectURI, scope, state string) error { location, err := oauth.GetRedirectURL(redirectURI, scope, state) if err != nil { return err } http.Redirect(writer, req, location, http.StatusFound) return nil } // ResAccessToken 获取用户授权access_token的返回结果 type ResAccessToken struct { util.CommonError AccessToken string `json:"access_token"` ExpiresIn int64 `json:"expires_in"` RefreshToken string `json:"refresh_token"` OpenID string `json:"openid"` Scope string `json:"scope"` // IsSnapShotUser 是否为快照页模式虚拟账号,只有当用户是快照页模式虚拟账号时返回,值为1 // 公众号文档 https://developers.weixin.qq.com/community/minihome/doc/000c2c34068880629ced91a2f56001 IsSnapShotUser int `json:"is_snapshotuser"` // UnionID 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。 // 公众号文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842 UnionID string `json:"unionid"` } // GetUserInfoByCodeContext 通过网页授权的code 换取用户的信息 func (oauth *Oauth) GetUserInfoByCodeContext(ctx ctx2.Context, code string) (result UserInfo, err error) { var ( token ResAccessToken ) if token, err = oauth.GetUserAccessTokenContext(ctx, code); err != nil { return } return oauth.GetUserInfoContext(ctx, token.AccessToken, token.OpenID, "") } // GetUserAccessToken 通过网页授权的code 换取access_token(区别于context中的access_token) func (oauth *Oauth) GetUserAccessToken(code string) (result ResAccessToken, err error) { return oauth.GetUserAccessTokenContext(ctx2.Background(), code) } // GetUserAccessTokenContext 通过网页授权的code 换取access_token(区别于context中的access_token) with context func (oauth *Oauth) GetUserAccessTokenContext(ctx ctx2.Context, code string) (result ResAccessToken, err error) { urlStr := fmt.Sprintf(accessTokenURL, oauth.AppID, oauth.AppSecret, code) var response []byte response, err = util.HTTPGetContext(ctx, urlStr) if err != nil { return } err = json.Unmarshal(response, &result) if err != nil { return } if result.ErrCode != 0 { err = fmt.Errorf("GetUserAccessToken error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg) return } return } // RefreshAccessToken 刷新access_token func (oauth *Oauth) RefreshAccessToken(refreshToken string) (result ResAccessToken, err error) { return oauth.RefreshAccessTokenContext(ctx2.Background(), refreshToken) } // RefreshAccessTokenContext 刷新access_token with context func (oauth *Oauth) RefreshAccessTokenContext(ctx ctx2.Context, refreshToken string) (result ResAccessToken, err error) { urlStr := fmt.Sprintf(refreshAccessTokenURL, oauth.AppID, refreshToken) var response []byte response, err = util.HTTPGetContext(ctx, urlStr) if err != nil { return } err = json.Unmarshal(response, &result) if err != nil { return } if result.ErrCode != 0 { err = fmt.Errorf("GetUserAccessToken error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg) return } return } // CheckAccessToken 检验access_token是否有效 func (oauth *Oauth) CheckAccessToken(accessToken, openID string) (b bool, err error) { return oauth.CheckAccessTokenContext(ctx2.Background(), accessToken, openID) } // CheckAccessTokenContext 检验access_token是否有效 with context func (oauth *Oauth) CheckAccessTokenContext(ctx ctx2.Context, accessToken, openID string) (b bool, err error) { urlStr := fmt.Sprintf(checkAccessTokenURL, accessToken, openID) var response []byte response, err = util.HTTPGetContext(ctx, urlStr) if err != nil { return } var result util.CommonError err = json.Unmarshal(response, &result) if err != nil { return } if result.ErrCode != 0 { b = false return } b = true return } // UserInfo 用户授权获取到用户信息 type UserInfo struct { util.CommonError OpenID string `json:"openid"` Nickname string `json:"nickname"` Sex int32 `json:"sex"` Province string `json:"province"` City string `json:"city"` Country string `json:"country"` HeadImgURL string `json:"headimgurl"` Privilege []string `json:"privilege"` Unionid string `json:"unionid"` } // GetUserInfo 如果scope为 snsapi_userinfo 则可以通过此方法获取到用户基本信息 func (oauth *Oauth) GetUserInfo(accessToken, openID, lang string) (result UserInfo, err error) { return oauth.GetUserInfoContext(ctx2.Background(), accessToken, openID, lang) } // GetUserInfoContext 如果scope为 snsapi_userinfo 则可以通过此方法获取到用户基本信息 with context func (oauth *Oauth) GetUserInfoContext(ctx ctx2.Context, accessToken, openID, lang string) (result UserInfo, err error) { if lang == "" { lang = "zh_CN" } urlStr := fmt.Sprintf(userInfoURL, accessToken, openID, lang) var response []byte response, err = util.HTTPGetContext(ctx, urlStr) if err != nil { return } err = json.Unmarshal(response, &result) if err != nil { return } if result.ErrCode != 0 { err = fmt.Errorf("GetUserInfo error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg) return } return }