微信網(wǎng)頁登錄授權(quán)、APP登錄授權(quán)、JS-SDK接口調(diào)用
時(shí)間:2022-08-06 21:18:01 | 來源:網(wǎng)站運(yùn)營
時(shí)間:2022-08-06 21:18:01 來源:網(wǎng)站運(yùn)營
@TOC
前言
微信網(wǎng)頁登錄授權(quán)、APP登錄授權(quán)、JS-SDK接口調(diào)用
溫馨提示:實(shí)踐某項(xiàng)功能請至少讀完準(zhǔn)備工作,可以避免很多的坑官方文檔
H5網(wǎng)頁授權(quán)
https://developers.weixin.qq.com
APP授權(quán)
https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Development_Guide.html
小程序授權(quán)
[https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html)
JS-SDK說明文檔
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.htmlhttps://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html)
準(zhǔn)備工作
- 測試:
- natapp 內(nèi)網(wǎng)穿透工具
開發(fā)階段可用此工具獲取域名
- 申請測試賬號
測試賬號擁有微信幾乎所有接口的能力,可以先通過測試賬號提前驗(yàn)證
- 賬號驗(yàn)證(接口配置)
填寫接口配置信息,此信息需要你有自己的服務(wù)器資源,填寫的URL需要正確響應(yīng)微信發(fā)送的Token驗(yàn)證,請閱讀消息接口使用指南。
驗(yàn)證接口實(shí)現(xiàn):
@ApiOperation("微信公眾號認(rèn)證入口")
@GetMapping(value = "/wechat/api/wxServerValdation")
public String wxServerValdation(String signature, String timestamp, String nonce, String echostr){
if (Objects.isNull(signature)|| Objects.isNull(timestamp) || Objects.isNull(nonce) || Objects.isNull(echostr)){
return "fail";
}
ArrayList<String> list= new ArrayList<>();
list.add(nonce);
list.add(timestamp);
//這是第5步中你設(shè)置的Token
list.add(WxMpConfig.token);
Collections.sort(list);
String sha1Singnature = DigestUtils.sha1Hex(list.get(0)+list.get(1)+list.get(2));
if (sha1Singnature.equals(signature)){
return echostr;
}else {
return "fail";
}
}
此時(shí)的URL可以設(shè)置為 :http://IP:端口號/wechat/api/wxServerValdation
也可以填寫為域名+/wechat/api/wxServerValdation
驗(yàn)證字段說明
開發(fā)者通過檢驗(yàn)signature對請求進(jìn)行校驗(yàn)(下面有校驗(yàn)方式)。若確認(rèn)此次GET請求來自微信服務(wù)器,請?jiān)瓨臃祷豦chostr參數(shù)內(nèi)容,則接入生效,成為開發(fā)者成功,否則接入失敗。加密/校驗(yàn)流程如下:
1)將token、timestamp、nonce三個(gè)參數(shù)進(jìn)行字典序排序
2)將三個(gè)參數(shù)字符串拼接成一個(gè)字符串進(jìn)行sha1加密
3)開發(fā)者獲得加密后的字符串可與signature對比,標(biāo)識(shí)該請求來源于微信
驗(yàn)證工具:
http://mp.weixin.qq.com/debug/
- JS接口安全域名
設(shè)置JS接口安全域后,通過關(guān)注該測試號,開發(fā)者即可在該域名下調(diào)用微信開放的JS接口,請閱讀微信JSSDK開發(fā)文檔。
請注意,這里填寫的是域名(是一個(gè)字符串),而不是URL,因此請勿加 http:// 等協(xié)議頭;
- 網(wǎng)頁授權(quán)獲取用戶基本信息
微信根據(jù)此域名檢查回調(diào)地址域名
請注意,這里填寫的是域名(是一個(gè)字符串),而不是URL,因此請勿加 http:// 等協(xié)議頭;
- 正式:
- 申請賬號
- 正式的微信賬號分為訂閱號、服務(wù)號、企業(yè)號,認(rèn)證都需要300元,但是使用授權(quán)和接口功能必須認(rèn)證。
- 但是訂閱號不能直接獲取到用戶的OpenID,所以盡量申請一個(gè)服務(wù)號。(這點(diǎn)微信也是不會(huì)提醒用戶的,但是當(dāng)你看了一下測試賬號是訂閱號,就直接申請了一個(gè)訂閱號結(jié)果就不言而喻了,接口處就會(huì)提醒用戶,只有服務(wù)號才能使用授權(quán)接口。)
- 不過訂閱號也是有一個(gè)漏洞的,如果你是在公眾號內(nèi)使用,是可以迂回獲取到用戶信OpenID的。可以參考下面的文章:https://blog.csdn.net/vbirdbest/article/details/51217478
- 接口配置
接口配置主要有一下三點(diǎn),其中IP白名單要加入所有訪問微信接口服務(wù)器的IP
流程講解
- 總體流程
接入微信公眾平臺(tái)開發(fā),開發(fā)者需要按照如下步驟完成:
1、填寫服務(wù)器配置
2、驗(yàn)證服務(wù)器地址的有效性
3、依據(jù)接口文檔實(shí)現(xiàn)業(yè)務(wù)邏輯
- 網(wǎng)頁授權(quán)
- 功能
網(wǎng)頁授權(quán)獲取用戶基本信息:通過該接口,可以獲取用戶的基本信息(獲取用戶的OpenID是無需用戶同意的,獲取用戶的基本信息則需用戶同意)
- 注意
- 關(guān)于網(wǎng)頁授權(quán)access_token和普通access_token的區(qū)別
1、微信網(wǎng)頁授權(quán)是通過OAuth2.0機(jī)制實(shí)現(xiàn)的,在用戶授權(quán)給公眾號后,公眾號可以獲取到一個(gè)網(wǎng)頁授權(quán)特有的接口調(diào)用憑證(網(wǎng)頁授權(quán)access_token),通過網(wǎng)頁授權(quán)access_token可以進(jìn)行授權(quán)后接口調(diào)用,如獲取用戶基本信息;
2、其他微信接口,需要通過基礎(chǔ)支持中的“獲取access_token”接口來獲取到的普通access_token調(diào)用。
- 流程
網(wǎng)頁授權(quán)流程分為四步:
1、引導(dǎo)用戶進(jìn)入授權(quán)頁面同意授權(quán),獲取code(拉起授權(quán)頁面)
2、通過code換取網(wǎng)頁授權(quán)access_token(與基礎(chǔ)支持中的access_token不同)
3、如果需要,開發(fā)者可以刷新網(wǎng)頁授權(quán)access_token,避免過期
4、通過網(wǎng)頁授權(quán)access_token和openid獲取用戶基本信息(支持UnionID機(jī)制)
5 附:檢驗(yàn)授權(quán)憑證(access_token)是否有效
- APP授權(quán)
- 第三方發(fā)起微信授權(quán)登錄請求,微信用戶允許授權(quán)第三方應(yīng)用后,微信會(huì)拉起應(yīng)用或重定向到第三方網(wǎng)站,并且?guī)鲜跈?quán)臨時(shí)票據(jù) code 參數(shù);
- 通過 code 參數(shù)加上 AppID 和 AppSecret 等,通過 API 換取 access_token;
- 通過 access_token 進(jìn)行接口調(diào)用,獲取用戶基本數(shù)據(jù)資源或幫助用戶實(shí)現(xiàn)基本操作。
- 小程序授權(quán)
- 調(diào)用 wx.login() 獲取 臨時(shí)登錄憑證code ,并回傳到開發(fā)者服務(wù)器。
- 調(diào)用 auth.code2Session 接口,換取 用戶唯一標(biāo)識(shí) OpenID 、 用戶在微信開放平臺(tái)帳號下的唯一標(biāo)識(shí)UnionID(若當(dāng)前小程序已綁定到微信開放平臺(tái)帳號) 和 會(huì)話密鑰 session_key。
- 之后開發(fā)者服務(wù)器可以根據(jù)用戶標(biāo)識(shí)來生成自定義登錄態(tài),用于后續(xù)業(yè)務(wù)邏輯中前后端交互時(shí)識(shí)別用戶身份
- JS-SDK接口調(diào)用
- 功能
微信JS-SDK:是開發(fā)者在網(wǎng)頁上通過JavaScript代碼使用微信原生功能的工具包,開發(fā)者可以使用它在網(wǎng)頁上錄制和播放微信語音、監(jiān)聽微信分享、上傳手機(jī)本地圖片、拍照等許多能力。
- 注意
這塊可能會(huì)出現(xiàn)前端調(diào)用失敗的問題,或者分享的時(shí)候微信不能分享的問題,這個(gè)很有可能時(shí)版本的問題,請先倒回之前的版本,再進(jìn)行測試。
- 簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。
- 簽名用的url必須是調(diào)用JS接口頁面的完整URL。
- 流程
- 獲取Access token
官方接口測試工具
https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?t=index&type=%E5%9F%BA%E7%A1%80%E6%94%AF%E6%8C%81&form=%E8%8E%B7%E5%8F%96access_token%E6%8E%A5%E5%8F%A3%20/token
- 獲得jsapi_ticket
(有效期7200秒,開發(fā)者必須在自己的服務(wù)全局緩存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
- 生成JS-SDK權(quán)限驗(yàn)證的簽名
微信 JS 接口簽名校驗(yàn)工具
功能實(shí)現(xiàn)
- 屬性配置
- yml 可以把地址也存放到配置里
#微信開放平臺(tái)賬號
wechat:
#服務(wù)號
mp:
appId:
secret:
token:
aesKey:
- WxMpConfig 配置類
@Component
@ConfigurationProperties("wechat.mp")
public class WxMpConfig {
/**
* 設(shè)置微信公眾號的appid
*/
public static String appId;
/**
* 設(shè)置微信公眾號的app secret
*/
public static String secret;
/**
* 設(shè)置微信公眾號的token
*/
public static String token;
/**
* js 回調(diào)地址
*/
public static String jsUrl;
/**
* 微信認(rèn)證路徑
*/
public static String url;
/**
* 微信認(rèn)證路徑
*/
public static String aesKey;
public void setAppId(String appId) {
WxMpConfig.appId = appId;
}
public void setSecret(String secret) {
WxMpConfig.secret = secret;
}
public void setToken(String token) {
WxMpConfig.token = token;
}
public void setJsUrl(String jsUrl) {
WxMpConfig.jsUrl = jsUrl;
}
public void setUrl(String url) {
WxMpConfig.url = url;
}
public void setAesKey(String aesKey) {
WxMpConfig.aesKey = aesKey;
}
}
- WxAuthUtil
@Slf4j
public class WxAuthUtil {
/**
* APPID
*/
public final static String APP_ID = "APP_ID";
/**
* SECRET
*/
public final static String SECRET = "SECRET";
public final static String CODE = "CODE";
public final static String OPEN_ID = "OPEN_ID";
public final static String ACCESS_TOKEN = "ACCESS_TOKEN";
public final static String REFRESH_TOKEN = "REFRESH_TOKEN";
public final static String WX_ACCESS_TOKEN = "WX_ACCESS_TOKEN";
public final static String WX_JSAPI_TICKET = "WX_JSAPI_TICKET";
/**
* 公用獲取access_token訪問地址
*/
public final static String JS_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
/**
* 獲取jsApiTicket訪問地址
*/
public final static String JS_API_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
/**
* 網(wǎng)頁授權(quán) 獲取access_token訪問地址
*/
public final static String H5_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APP_ID&secret=SECRET&code=CODE&grant_type=authorization_code";
/**
* 檢查access_token是否失效地址
*/
public final static String CHECK_H5_URL = "https://api.weixin.qq.com/sns/auth?access_token="+ACCESS_TOKEN+"&openid="+OPEN_ID;
/**
* 刷新access_token地址
*/
public final static String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + OPEN_ID
+ "&grant_type=refresh_token&refresh_token=" + REFRESH_TOKEN;
/**
* 拉取用戶信息
*/
public final static String USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=" + ACCESS_TOKEN + "&openid=" + OPEN_ID
+ "&lang=zh_CN";
/**
* 獲取accessToken
* @return Map
* @throws IOException
*/
public static JSONObject getAccessToken() throws IOException {
String requestUrl = JS_ACCESS_TOKEN_URL.replace("APPID", WxMpConfig.appId).replace("APPSECRET",WxMpConfig.secret);
//向微信發(fā)送get請求
http://log.info("requestUrl: {}", requestUrl);
JSONObject callBack = doGet(requestUrl);
http://log.info("獲取accessToken:{}", callBack);
return callBack;
}
/**
* 微信請求
* @param url
* @return Object
* @throws IOException
*/
public static JSONObject doGet(String url) throws IOException {
JSONObject jsonObject = null;
HttpClient client = HttpClientBuilder.create().build();
final HttpGet httpGet = new HttpGet(url);
HttpResponse response = client.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
// 返回結(jié)果轉(zhuǎn)化為JSON對象
final String result = EntityUtils.toString(entity, "UTF-8");
jsonObject = JSON.parseObject(result);
}
return jsonObject;
}
}
- 授權(quán)
這里只詳細(xì)介紹網(wǎng)頁授權(quán),其他方式大同小異,官方文檔可以查看
APP 與 H5 獲取 code 的區(qū)別:h5 是前端通過后臺(tái)拿到授權(quán) url,然后前端請求該 url 得到 code 再請求后臺(tái);APP 則是前端配合使用微信開放平臺(tái)提供的 SDK 進(jìn)行授權(quán)登錄請求,用戶同意授權(quán)后得到 code 再去請求后臺(tái);
獲取code請求路徑https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx6fb2b539e0663d3b&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_re獲取code 需要先設(shè)置REDIRECT_URI 回調(diào)路徑
如果用戶同意授權(quán),頁面將跳轉(zhuǎn)至 redirect_uri/?code=CODE&state=STATE。
code說明 : code作為換取access_token的票據(jù),每次用戶授權(quán)帶上的code將不一樣,code只能使用一次,5分鐘未被使用自動(dòng)過期。
獲取code后,請求以下鏈接獲取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code- 通過openID + access_token獲取用戶信息
需要用緩存存儲(chǔ)access_token
https://api.weixin.qq.com/sns/userinfo?access_token=" + ACCESS_TOKEN + "&openid=" + OPEN_ID + "&lang=zh_CN";
檢查token是否失效
@ApiOperation("網(wǎng)頁拉起微信授權(quán)頁面,返回微信校驗(yàn)信息") @GetMapping("/auth") public R<String> callBack(@ApiParam(value = "*code",required = true)String code) throws IOException { /** * 獲取到授權(quán)標(biāo)志code(用戶同意微信授權(quán)之后產(chǎn)生的code) */ log.info("進(jìn)入微信回調(diào)code={}",code); String url = WxAuthUtil.H5_ACCESS_TOKEN_URL. replace(WxAuthUtil.APP_ID,WxMpConfig.appId). replace(WxAuthUtil.SECRET, WxMpConfig.secret). replace(WxAuthUtil.CODE,code); JSONObject jsonObject = WxAuthUtil.doGet(url); log.info("授權(quán)結(jié)果{}",jsonObject); return R.ok(jsonObject.getString("openid")); } @ApiOperation("拉取微信用戶信息,每天獲取token的次數(shù)受限,需要將accessToken緩存(目前不需要獲取用戶信息)") @GetMapping("/userInfo") public R<WxUserVO> getWeiChatUserInfo(@RequestParam("code")String openid, @RequestParam("accessToken")String accessToken, @RequestParam("refreshToken")String refreshToken) throws IOException { /** * 校驗(yàn)access_token是否失效 */ String checkoutUrl = WxAuthUtil.CHECK_H5_URL. replace(WxAuthUtil.ACCESS_TOKEN, accessToken). replace(WxAuthUtil.OPEN_ID,openid); JSONObject checkoutInfo = WxAuthUtil.doGet(checkoutUrl); log.info("校驗(yàn)信息-----{}",checkoutInfo.toString()); if (!"0".equals(checkoutInfo.getString("errcode"))) { // 刷新access_token String refreshTokenUrl = WxAuthUtil.REFRESH_TOKEN_URL .replace(WxAuthUtil.APP_ID,WxMpConfig.appId) .replace(WxAuthUtil.REFRESH_TOKEN, refreshToken); JSONObject refreshInfo = WxAuthUtil.doGet(refreshTokenUrl); System.out.println(refreshInfo.toString()); accessToken = refreshInfo.getString("access_token"); } /** * 使用access_token拉取用戶信息 */ String infoUrl = WxAuthUtil.USER_INFO_URL .replace(WxAuthUtil.ACCESS_TOKEN, accessToken) .replace(WxAuthUtil.OPEN_ID, openid); JSONObject userInfo = WxAuthUtil.doGet(infoUrl); WxUserVO wxUserVO = JSONObject.parseObject(String.valueOf(userInfo), WxUserVO.class); log.info("用戶數(shù)據(jù)-----{}", userInfo); return R.ok(wxUserVO); }
@ApiOperation("生成微信JS-SDK簽名") @PostMapping( "/signature") public R<Map<String, String>> makeWxSignature(@RequestBody @Validated WxH5VO wxH5VO){ //獲取jsapiTicket String jsapiTicket = getJsapiTicket(); Map<String, String> ret = new HashMap<>(6); String nonceStr = RandomUtils.getRandomStr(); long timestamp = System.currentTimeMillis()/1000L; //加密 String signature = SHA1.genWithAmple( "jsapi_ticket=" + jsapiTicket, "noncestr=" + nonceStr, "timestamp=" + timestamp, "url=" + wxH5VO.getUrl()); ret.put("url", wxH5VO.getUrl()); ret.put("jsapi_ticket", jsapiTicket); ret.put("nonceStr", nonceStr); ret.put("timestamp", Long.toString(timestamp)); ret.put("signature", signature); ret.put("appId", WxMpConfig.appId); return R.ok(ret); } /** * 1、緩存中獲取 jsApiTicket * 2、沒有 已過期 重新獲取 * 2、獲取緩存中的access_token * 3、沒有 證明已經(jīng)過期 重新獲取 * 獲取jsapiTicket * @return jsapiTicket */ private String getJsapiTicket(){ String jsapiTicket = null; try{ //根據(jù)access_token 獲取jsapiTicket jsapiTicket = redisService.getCacheObject(WxAuthUtil.WX_JSAPI_TICKET); if(Objects.isNull(jsapiTicket)){ //獲取access_token String accessToken = redisService.getCacheObject(WxAuthUtil.WX_ACCESS_TOKEN); if(Objects.isNull(accessToken)){ JSONObject accessTokenMap = WxAuthUtil.getAccessToken(); accessToken = accessTokenMap.getString("access_token"); log.info("訪問獲取access_token數(shù)據(jù){}", accessTokenMap); redisService.setCacheObject(WxAuthUtil.WX_ACCESS_TOKEN, accessToken, accessTokenMap.getLong("expires_in"), TimeUnit.SECONDS); } String requestUrl = WxAuthUtil.JS_API_TICKET_URL.replace("ACCESS_TOKEN", accessToken); //向微信發(fā)送get請求 JSONObject callBack = WxAuthUtil.doGet(requestUrl); log.info("訪問獲取jsApiTicket數(shù)據(jù){}", callBack); jsapiTicket = callBack.getString("ticket"); redisService.setCacheObject(WxAuthUtil.WX_JSAPI_TICKET, jsapiTicket,callBack.getLong("expires_in"), TimeUnit.SECONDS); } log.info("獲取jsApiTicket:{}", jsapiTicket); }catch (IOException e){ log.info("獲取簽名異常:" + e.getMessage()); } return jsapiTicket; }
錯(cuò)誤處理
?
官方常見錯(cuò)誤及解決方法- invalid signature簽名錯(cuò)誤。建議按如下順序檢查:
- 確認(rèn)簽名算法正確,可用http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 頁面工具進(jìn)行校驗(yàn)。
- 確認(rèn)config中nonceStr(js中駝峰標(biāo)準(zhǔn)大寫S), timestamp與用以簽名中的對應(yīng)noncestr, timestamp一致。
- 確認(rèn)url是頁面完整的url(請?jiān)诋?dāng)前頁面alert(location.href.split('#')[0])確認(rèn)),包括'http(s)://'部分,以及'?'后面的GET參數(shù)部分,但不包括'#'hash后面的部分。
- 確認(rèn) config 中的 appid 與用來獲取 jsapi_ticket 的 appid 一致。
- 確保一定緩存access_token和jsapi_ticket
- 確保你獲取用來簽名的url是動(dòng)態(tài)獲取的,動(dòng)態(tài)頁面可參見實(shí)例代碼中php的實(shí)現(xiàn)方式。如果是html的靜態(tài)頁面在前端通過ajax將url傳到后臺(tái)簽名,前端需要用js獲取當(dāng)前頁面除去'#'hash部分的鏈接(可用location.href.split('#')[0]獲取,而且需要encodeURIComponent),因?yàn)轫撁嬉坏┓窒恚⑿趴蛻舳藭?huì)在你的鏈接末尾加入其它參數(shù),如果不是動(dòng)態(tài)獲取當(dāng)前鏈接,將導(dǎo)致分享后的頁面簽名失敗。
關(guān)鍵詞:授權(quán),調(diào)用