github.com/fastwego/offiaccount@v1.0.1/apis/oauth/oauth.go (about) 1 // Copyright 2020 FastWeGo 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package oauth 微信网页开发(oauth) 16 17 /* 18 网页授权流程分为四步: 19 20 1、引导用户进入授权页面同意授权,获取code 21 22 2、通过code换取网页授权access_token(与基础支持中的access_token不同) 23 24 3、如果需要,开发者可以刷新网页授权access_token,避免过期 25 26 4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制) 27 */ 28 package oauth 29 30 import ( 31 "encoding/json" 32 "fmt" 33 "io/ioutil" 34 "net/http" 35 "net/url" 36 37 "github.com/fastwego/offiaccount" 38 ) 39 40 var OauthAuthorizeServerUrl = "https://open.weixin.qq.com" 41 42 const ( 43 apiAuthorize = "/connect/oauth2/authorize" 44 apiAccessToken = "/sns/oauth2/access_token" 45 apiRefreshToken = "/sns/oauth2/refresh_token" 46 apiUserInfo = "/sns/userinfo" 47 apiAuth = "/sns/auth" 48 apiGetJSApiTicket = "/cgi-bin/ticket/getticket" 49 ) 50 51 const ( 52 ScopeSnsapiBase = "snsapi_base" 53 ScopeSnsapiUserinfo = "snsapi_userinfo" 54 ) 55 56 /* 57 获取 用户授权 跳转链接 58 59 以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面) 60 61 以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息 62 63 如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE 64 65 See: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 66 67 GET https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect 68 */ 69 func GetAuthorizeUrl(appid string, redirectUri string, scope string, state string) (authorizeUrl string) { 70 params := url.Values{} 71 params.Add("appid", appid) 72 params.Add("redirect_uri", redirectUri) 73 params.Add("response_type", "code") 74 params.Add("scope", scope) 75 params.Add("state", state) 76 return OauthAuthorizeServerUrl + apiAuthorize + "?" + params.Encode() 77 } 78 79 type OauthAccessToken struct { 80 AccessToken string `json:"access_token"` 81 ExpiresIn int `json:"expires_in"` 82 RefreshToken string `json:"refresh_token"` 83 Openid string `json:"openid"` 84 Scope string `json:"scope"` 85 } 86 87 /* 88 通过code换取网页授权access_token 89 90 注意:由于公众号的secret和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端 91 92 See: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 93 94 GET https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code 95 */ 96 func GetAccessToken(appid string, secret string, code string) (oauthAccessToken OauthAccessToken, err error) { 97 params := url.Values{} 98 params.Add("appid", appid) 99 params.Add("secret", secret) 100 params.Add("code", code) 101 params.Add("grant_type", "authorization_code") 102 103 uri := offiaccount.WXServerUrl + apiAccessToken + "?" + params.Encode() 104 response, err := http.Get(uri) 105 if err != nil { 106 return 107 } 108 109 defer response.Body.Close() 110 body, err := ioutil.ReadAll(response.Body) 111 if err != nil { 112 return 113 } 114 115 err = json.Unmarshal(body, &oauthAccessToken) 116 if err != nil { 117 err = fmt.Errorf("%s", string(body)) 118 return 119 } 120 121 return 122 } 123 124 /* 125 刷新access_token 126 127 由于access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新,refresh_token有效期为30天,当refresh_token失效之后,需要用户重新授权 128 129 See: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 130 131 POST https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN 132 */ 133 func RefreshToken(appid string, refresh_token string) (oauthAccessToken OauthAccessToken, err error) { 134 params := url.Values{} 135 params.Add("appid", appid) 136 params.Add("refresh_token", refresh_token) 137 params.Add("grant_type", "refresh_token") 138 139 uri := offiaccount.WXServerUrl + apiRefreshToken + "?" + params.Encode() 140 response, err := http.Get(uri) 141 if err != nil { 142 return 143 } 144 145 defer response.Body.Close() 146 body, err := ioutil.ReadAll(response.Body) 147 if err != nil { 148 return 149 } 150 151 err = json.Unmarshal(body, &oauthAccessToken) 152 if err != nil { 153 err = fmt.Errorf("%s", string(body)) 154 return 155 } 156 157 return 158 } 159 160 const ( 161 LANG_zh_CN = "zh_CN" 162 LANG_zh_TW = "zh_TW" 163 LANG_en = "en" 164 ) 165 166 type OauthUserInfo struct { 167 Openid string `json:"openid"` 168 Nickname string `json:"nickname"` 169 Sex int64 `json:"sex"` 170 Province string `json:"province"` 171 City string `json:"city"` 172 Country string `json:"country"` 173 Headimgurl string `json:"headimgurl"` 174 Privilege []string `json:"privilege"` 175 Unionid string `json:"unionid"` 176 } 177 178 /* 179 拉取用户信息 180 181 如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了 182 183 See: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 184 185 POST https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN 186 */ 187 func GetUserInfo(access_token string, openid string, lang string) (oauthUserInfo OauthUserInfo, err error) { 188 params := url.Values{} 189 params.Add("access_token", access_token) 190 params.Add("openid", openid) 191 params.Add("lang", lang) 192 193 uri := offiaccount.WXServerUrl + apiUserInfo + "?" + params.Encode() 194 response, err := http.Get(uri) 195 if err != nil { 196 return 197 } 198 199 defer response.Body.Close() 200 body, err := ioutil.ReadAll(response.Body) 201 if err != nil { 202 return 203 } 204 205 err = json.Unmarshal(body, &oauthUserInfo) 206 if err != nil { 207 err = fmt.Errorf("%s", string(body)) 208 return 209 } 210 211 return 212 } 213 214 /* 215 检验授权凭证(access_token)是否有效 216 217 218 219 See: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 220 221 GET https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID 222 */ 223 func Auth(access_token string, openid string) (isValid bool, err error) { 224 params := url.Values{} 225 params.Add("access_token", access_token) 226 params.Add("openid", openid) 227 228 uri := offiaccount.WXServerUrl + apiAuth + "?" + params.Encode() 229 response, err := http.Get(uri) 230 if err != nil { 231 return 232 } 233 234 defer response.Body.Close() 235 body, err := ioutil.ReadAll(response.Body) 236 if err != nil { 237 return 238 } 239 240 s := struct { 241 Errcode int `json:"errcode"` 242 Errmsg string `json:"errmsg"` 243 }{} 244 245 err = json.Unmarshal(body, &s) 246 if err != nil { 247 err = fmt.Errorf("%s", string(body)) 248 return 249 } 250 251 if s.Errcode == 0 { 252 isValid = true 253 } 254 255 return 256 } 257 258 /* 259 获取 jsapi_ticket 260 261 sapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 262 263 See: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62 264 265 GET https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi 266 */ 267 func GetJSApiTicket(ctx *offiaccount.OffiAccount) (jsapiTicket string, expiresIn int64, err error) { 268 269 jsapiTicketResp := struct { 270 Ticket string `json:"ticket"` 271 ExpiresIn int64 `json:"expires_in"` 272 }{} 273 resp, err := ctx.Client.HTTPGet(apiGetJSApiTicket + "?type=jsapi") 274 if err != nil { 275 return 276 } 277 278 err = json.Unmarshal(resp, &jsapiTicketResp) 279 if err != nil { 280 return 281 } 282 283 return jsapiTicketResp.Ticket, jsapiTicketResp.ExpiresIn, nil 284 }