@
前言
微信網頁登錄授權、APP登錄授權、JS-SDK接口調用
溫馨提示:實踐某項功能請至少讀完准備工作,可以避免很多的坑
官方文檔
H5網頁授權
APP授權
https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Development_Guide.html
小程序授權
[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)
准備工作
-
測試:
-
開發階段可用此工具獲取域名
-
申請測試賬號
測試賬號擁有微信幾乎所有接口的能力,可以先通過測試賬號提前驗證
-
獲取測試號信息
-
賬號驗證(接口配置)
填寫接口配置信息,此信息需要你有自己的服務器資源,填寫的URL需要正確響應微信發送的Token驗證,請閱讀消息接口使用指南。
字段 說明 URL 開發者用來接收微信消息和事件的接口URL(可以外網訪問自己服務器IP:端口號也可以) TOKEN 自己隨意填寫,用作生成簽名(該Token會和接口URL中包含的Token進行比對,從而驗證安全性) 驗證接口實現:
@ApiOperation("微信公眾號認證入口") @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步中你設置的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"; } }
此時的URL可以設置為 :http://IP:端口號/wechat/api/wxServerValdation
也可以填寫為域名+/wechat/api/wxServerValdation
驗證字段說明
參數 描述 signature 微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。 timestamp 時間戳 nonce 隨機數 echostr 隨機字符串 開發者通過檢驗signature對請求進行校驗(下面有校驗方式)。若確認此次GET請求來自微信服務器,請原樣返回echostr參數內容,則接入生效,成為開發者成功,否則接入失敗。加密/校驗流程如下:
1)將token、timestamp、nonce三個參數進行字典序排序
2)將三個參數字符串拼接成一個字符串進行sha1加密
3)開發者獲得加密后的字符串可與signature對比,標識該請求來源於微信
驗證工具:
-
JS接口安全域名
設置JS接口安全域后,通過關注該測試號,開發者即可在該域名下調用微信開放的JS接口,請閱讀微信JSSDK開發文檔。
請注意,這里填寫的是域名(是一個字符串),而不是URL,因此請勿加 http:// 等協議頭;
-
微信根據此域名檢查回調地址域名
請注意,這里填寫的是域名(是一個字符串),而不是URL,因此請勿加 http:// 等協議頭;
-
-
正式:
-
申請賬號
-
正式的微信賬號分為訂閱號、服務號、企業號,認證都需要300元,但是使用授權和接口功能必須認證。
-
但是訂閱號不能直接獲取到用戶的OpenID,所以盡量申請一個服務號。(這點微信也是不會提醒用戶的,但是當你看了一下測試賬號是訂閱號,就直接申請了一個訂閱號結果就不言而喻了,接口處就會提醒用戶,只有服務號才能使用授權接口。)
-
不過訂閱號也是有一個漏洞的,如果你是在公眾號內使用,是可以迂回獲取到用戶信OpenID的。可以參考下面的文章:https://blog.csdn.net/vbirdbest/article/details/51217478
-
-
賬號認證
-
接口配置
接口配置主要有一下三點,其中IP白名單要加入所有訪問微信接口服務器的IP
-
基本配置
-
服務器配置
-
功能設置
-
-
流程講解
-
總體流程
接入微信公眾平台開發,開發者需要按照如下步驟完成:
1、填寫服務器配置
2、驗證服務器地址的有效性
3、依據接口文檔實現業務邏輯
-
網頁授權
-
功能
網頁授權獲取用戶基本信息:通過該接口,可以獲取用戶的基本信息(獲取用戶的OpenID是無需用戶同意的,獲取用戶的基本信息則需用戶同意)
-
注意
-
關於網頁授權access_token和普通access_token的區別
1、微信網頁授權是通過OAuth2.0機制實現的,在用戶授權給公眾號后,公眾號可以獲取到一個網頁授權特有的接口調用憑證(網頁授權access_token),通過網頁授權access_token可以進行授權后接口調用,如獲取用戶基本信息;
2、其他微信接口,需要通過基礎支持中的“獲取access_token”接口來獲取到的普通access_token調用。
-
-
流程
網頁授權流程分為四步:
1、引導用戶進入授權頁面同意授權,獲取code(拉起授權頁面)
2、通過code換取網頁授權access_token(與基礎支持中的access_token不同)
3、如果需要,開發者可以刷新網頁授權access_token,避免過期
4、通過網頁授權access_token和openid獲取用戶基本信息(支持UnionID機制)
5 附:檢驗授權憑證(access_token)是否有效
-
-
APP授權
- 第三方發起微信授權登錄請求,微信用戶允許授權第三方應用后,微信會拉起應用或重定向到第三方網站,並且帶上授權臨時票據 code 參數;
- 通過 code 參數加上 AppID 和 AppSecret 等,通過 API 換取 access_token;
- 通過 access_token 進行接口調用,獲取用戶基本數據資源或幫助用戶實現基本操作。
-
小程序授權
-
調用 wx.login() 獲取 臨時登錄憑證code ,並回傳到開發者服務器。
-
調用 auth.code2Session 接口,換取 用戶唯一標識 OpenID 、 用戶在微信開放平台帳號下的唯一標識UnionID(若當前小程序已綁定到微信開放平台帳號) 和 會話密鑰 session_key。
-
之后開發者服務器可以根據用戶標識來生成自定義登錄態,用於后續業務邏輯中前后端交互時識別用戶身份
-
-
JS-SDK接口調用
-
功能
微信JS-SDK:是開發者在網頁上通過JavaScript代碼使用微信原生功能的工具包,開發者可以使用它在網頁上錄制和播放微信語音、監聽微信分享、上傳手機本地圖片、拍照等許多能力。
-
注意
這塊可能會出現前端調用失敗的問題,或者分享的時候微信不能分享的問題,這個很有可能時版本的問題,請先倒回之前的版本,再進行測試。
- 簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。
- 簽名用的url必須是調用JS接口頁面的完整URL。
-
流程
-
獲取Access token
官方接口測試工具
https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?t=index&type=基礎支持&form=獲取access_token接口 /token
-
獲得jsapi_ticket
(有效期7200秒,開發者必須在自己的服務全局緩存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
-
生成JS-SDK權限驗證的簽名
-
-
功能實現
-
屬性配置
-
yml 可以把地址也存放到配置里
#微信開放平台賬號 wechat: #服務號 mp: appId: secret: token: aesKey:
-
WxMpConfig 配置類
@Component @ConfigurationProperties("wechat.mp") public class WxMpConfig { /** * 設置微信公眾號的appid */ public static String appId; /** * 設置微信公眾號的app secret */ public static String secret; /** * 設置微信公眾號的token */ public static String token; /** * js 回調地址 */ public static String jsUrl; /** * 微信認證路徑 */ public static String url; /** * 微信認證路徑 */ 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"; /** * 網頁授權 獲取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); //向微信發送get請求 log.info("requestUrl: {}", requestUrl); JSONObject callBack = doGet(requestUrl); 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) { // 返回結果轉化為JSON對象 final String result = EntityUtils.toString(entity, "UTF-8"); jsonObject = JSON.parseObject(result); } return jsonObject; } }
-
-
授權
這里只詳細介紹網頁授權,其他方式大同小異,官方文檔可以查看
-
獲取 code (拉起授權頁面)
APP 與 H5 獲取 code 的區別:h5 是前端通過后台拿到授權 url,然后前端請求該 url 得到 code 再請求后台;APP 則是前端配合使用微信開放平台提供的 SDK 進行授權登錄請求,用戶同意授權后得到 code 再去請求后台;
獲取code請求路徑
獲取code 需要先設置REDIRECT_URI 回調路徑
如果用戶同意授權,頁面將跳轉至 redirect_uri/?code=CODE&state=STATE。
code說明 : code作為換取access_token的票據,每次用戶授權帶上的code將不一樣,code只能使用一次,5分鍾未被使用自動過期。
-
獲取access_token
獲取code后,請求以下鏈接獲取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
參數 是否必須 說明 appid 是 公眾號的唯一標識 secret 是 公眾號的appsecret code 是 填寫第一步獲取的code參數 grant_type 是 填寫為authorization_code -
通過openID + access_token獲取用戶信息
需要用緩存存儲access_token
https://api.weixin.qq.com/sns/userinfo?access_token=" + ACCESS_TOKEN + "&openid=" + OPEN_ID + "&lang=zh_CN";
-
access_token過期刷新
檢查token是否失效
-
@ApiOperation("網頁拉起微信授權頁面,返回微信校驗信息")
@GetMapping("/auth")
public R<String> callBack(@ApiParam(value = "*code",required = true)String code) throws IOException {
/**
* 獲取到授權標志code(用戶同意微信授權之后產生的code)
*/
log.info("進入微信回調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("授權結果{}",jsonObject);
return R.ok(jsonObject.getString("openid"));
}
@ApiOperation("拉取微信用戶信息,每天獲取token的次數受限,需要將accessToken緩存(目前不需要獲取用戶信息)")
@GetMapping("/userInfo")
public R<WxUserVO> getWeiChatUserInfo(@RequestParam("code")String openid,
@RequestParam("accessToken")String accessToken,
@RequestParam("refreshToken")String refreshToken) throws IOException {
/**
* 校驗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("校驗信息-----{}",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("用戶數據-----{}", userInfo);
return R.ok(wxUserVO);
}
-
JS-SDK生成signature
@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、沒有 證明已經過期 重新獲取
* 獲取jsapiTicket
* @return jsapiTicket
*/
private String getJsapiTicket(){
String jsapiTicket = null;
try{
//根據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數據{}", 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);
//向微信發送get請求
JSONObject callBack = WxAuthUtil.doGet(requestUrl);
log.info("訪問獲取jsApiTicket數據{}", 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;
}
錯誤處理
-
invalid signature簽名錯誤。建議按如下順序檢查:
- 確認簽名算法正確,可用http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 頁面工具進行校驗。
- 確認config中nonceStr(js中駝峰標准大寫S), timestamp與用以簽名中的對應noncestr, timestamp一致。
- 確認url是頁面完整的url(請在當前頁面alert(location.href.split('#')[0])確認),包括'http(s)😕/'部分,以及'?'后面的GET參數部分,但不包括'#'hash后面的部分。
- 確認 config 中的 appid 與用來獲取 jsapi_ticket 的 appid 一致。
- 確保一定緩存access_token和jsapi_ticket
- 確保你獲取用來簽名的url是動態獲取的,動態頁面可參見實例代碼中php的實現方式。如果是html的靜態頁面在前端通過ajax將url傳到后台簽名,前端需要用js獲取當前頁面除去'#'hash部分的鏈接(可用location.href.split('#')[0]獲取,而且需要encodeURIComponent),因為頁面一旦分享,微信客戶端會在你的鏈接末尾加入其它參數,如果不是動態獲取當前鏈接,將導致分享后的頁面簽名失敗。