開(kāi)發(fā)者第一次把“獲取微信用戶信息”寫進(jìn)技術(shù)方案時(shí),幾乎都會(huì)經(jīng)歷這樣的落差:在微信開(kāi)發(fā)者工具里跑通的授權(quán)流程,到了真機(jī)或?qū)徍谁h(huán)節(jié)卻直接返回 userinfoundefined,或者根本彈不出授權(quán)窗。這不是代碼寫錯(cuò)了,而是對(duì)微信權(quán)限體系的作用時(shí)機(jī)和回收機(jī)制沒(méi)有對(duì)齊。

微信授權(quán)的分層模型:你真正需要什么

所有微信用戶授權(quán)最終都落在“拿到什么信息”和“用什么方式拿”這兩個(gè)維度上。根據(jù)這兩個(gè)維度,可以把授權(quán)拆成三層:

  1. 靜默授權(quán):只換取 openidunionid,不彈窗,不感知身份變化。
  2. 用戶主動(dòng)授權(quán):彈窗請(qǐng)求用戶同意,拿到昵稱、頭像等基本信息。
  3. 手機(jī)號(hào)授權(quán):通過(guò)專用組件觸發(fā),拿到加密的手機(jī)號(hào)碼。

后續(xù)所有操作路徑、接口選型和錯(cuò)誤處理都圍繞這三層展開(kāi)。先明確你的業(yè)務(wù)場(chǎng)景卡在哪一層,能直接決定用哪套接口組合。

實(shí)現(xiàn)路徑:從發(fā)起調(diào)起到拿到有效數(shù)據(jù)

1. 靜默授權(quán)(只拿 openidunionid

靜默授權(quán)依賴微信 OAuth2.0 協(xié)議的基礎(chǔ)碼交換流程。適用場(chǎng)景是用戶還未感知登錄態(tài)時(shí),先完成身份標(biāo)識(shí)的靜默綁定,例如進(jìn)入頁(yè)面前自動(dòng)建立會(huì)話。

公眾號(hào)內(nèi)網(wǎng)頁(yè)實(shí)現(xiàn)步驟

  • 引導(dǎo)用戶訪問(wèn)一個(gè) 302 跳轉(zhuǎn)鏈接:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=YOUR_APPID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect

這里 scope=snsapi_base 是關(guān)鍵。它不會(huì)彈出授權(quán)頁(yè),用戶無(wú)感知,但只返回 code,無(wú)法換取用戶詳細(xì)信息。

  • 你的回調(diào)地址收到 code 后,服務(wù)器端用以下接口換取 access_tokenopenid
GET https://api.weixin.qq.com/sns/oauth2/access_token?appid=YOUR_APPID&secret=YOUR_APPSECRET&code=CODE&grant_type=authorization_code

返回?cái)?shù)據(jù)示例:

{
  "access_token": "ACCESS_TOKEN",
  "expires_in": 7200,
  "refresh_token": "REFRESH_TOKEN",
  "openid": "OPENID",
  "scope": "snsapi_base"
}

如果同一主體下有多個(gè)應(yīng)用并開(kāi)通了開(kāi)放平臺(tái),可以用 access_tokenopenid 進(jìn)一步獲取 unionid,實(shí)現(xiàn)跨應(yīng)用用戶統(tǒng)一標(biāo)識(shí)。

小程序靜默獲取

小程序沒(méi)有真正的“靜默授權(quán)彈窗”概念,但 wx.login 配合后端 code2Session 可以拿到 openidunionid(條件同公眾號(hào)),全程無(wú)用戶交互。

wx.login({
  success: (res) => {
    // 將 res.code 發(fā)送到你們的服務(wù)端
    // 服務(wù)器調(diào)用 https://api.weixin.qq.com/sns/jscode2session
  }
})

只要用戶沒(méi)有在小程序內(nèi)刪除該小程序,openid 對(duì)當(dāng)前小程序保持不變。注意code2Session 本身不返回任何用戶信息(昵稱、頭像),這和公眾號(hào)靜默授權(quán)一致。

2. 用戶主動(dòng)授權(quán)(獲取昵稱、頭像等基本信息)

2021 年 4 月起微信調(diào)整了策略,小程序無(wú)法直接通過(guò) wx.getUserProfile 繼續(xù)獲取真實(shí)昵稱頭像,而是需要用戶顯式在特定組件內(nèi)填寫選擇。公眾號(hào)網(wǎng)頁(yè)端仍可通過(guò) snsapi_userinfo 彈窗獲取,但同樣受到用戶可拒絕或取消的限制。

公眾號(hào)網(wǎng)頁(yè)授權(quán)(完整信息)

將地址中的 scope 改為 snsapi_userinfo

https://open.weixin.qq.com/connect/oauth2/authorize?appid=YOUR_APPID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

用戶首次訪問(wèn)會(huì)看到明確的授權(quán)頁(yè)面,同意后回調(diào)得到 code,后續(xù)換取 access_tokenopenid 的接口不變。接著用 access_tokenopenid 拉取用戶信息:

GET https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

返回的 nickname、headimgurl 等字段僅在用戶授權(quán)后有效。如果用戶拒絕,code 換取階段就會(huì)返回錯(cuò)誤碼 -4,你的服務(wù)端需要處理這個(gè)拒絕分支,而不是假設(shè)一定能拿到信息。

小程序獲取用戶信息(新版方式)

小程序端獲取頭像昵稱推薦使用頭像填寫按鈕 <button open-type="chooseAvatar"> 和昵稱輸入框 <input type="nickname">。用戶必須主動(dòng)點(diǎn)擊選擇才能填充,程序無(wú)法靜默讀取。

<button open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
  <image src="{{avatarUrl}}" mode="aspectFill"/>
</button>
<input type="nickname" placeholder="請(qǐng)輸入昵稱" bindchange="onNicknameChange"/>
onChooseAvatar(e) {
  const { avatarUrl } = e.detail
  // 此時(shí) avatarUrl 是臨時(shí)路徑,需上傳到自己的服務(wù)器
}
onNicknameChange(e) {
  const nickname = e.detail.value
}

這徹底改變了獲取用戶信息的交互模型:信息歸用戶主動(dòng)提交,開(kāi)發(fā)者不再能通過(guò)一次彈窗自動(dòng)化獲取。如果你維護(hù)的老項(xiàng)目還在使用 wx.getUserProfile,需要盡快遷移,因?yàn)樵摻涌谝驯还俜较拗撇⒖赡芡耆厥铡?/p>

3. 手機(jī)號(hào)授權(quán)

手機(jī)號(hào)授權(quán)獨(dú)立于上述兩類授權(quán),它依賴微信客戶端內(nèi)專門的加密信道到微信后臺(tái),始終需要用戶點(diǎn)擊觸發(fā),不可程序化喚起。

小程序手機(jī)號(hào)獲取

使用 <button open-type="getPhoneNumber"> 觸發(fā),bindgetphonenumber 回調(diào)中拿到 encryptedDataiv,傳到你們的服務(wù)器后調(diào)用 https://api.weixin.qq.com/wxa/business/getuserphonenumber 解密。

<button open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">授權(quán)手機(jī)號(hào)</button>
onGetPhoneNumber(e) {
  if (e.detail.code) {
    // 新版接口:返回動(dòng)態(tài) code,需要后端調(diào)用接口換取手機(jī)號(hào)
    wx.request({
      url: 'YOUR_BACKEND/getPhone',
      data: { code: e.detail.code }
    })
  }
}

后端接口示例(使用微信 openapi):

// Node.js 示例,調(diào)用微信接口
const response = await axios.post(
  'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=YOUR_ACCESS_TOKEN',
  { code: receivedCode }
)
// response.data.phone_info.phoneNumber 即為手機(jī)號(hào)(無(wú)區(qū)號(hào))

這里需要的是小程序全局 access_token(不是 OAuth 的 access_token),必須由后端定時(shí)維護(hù)并緩存。另外,手機(jī)號(hào)授權(quán)每次都會(huì)扣減該小程序的“實(shí)時(shí)驗(yàn)證次數(shù)”,免費(fèi)額度有限,發(fā)布前務(wù)必在微信公眾平臺(tái)確認(rèn)額度。

公眾號(hào)網(wǎng)頁(yè)手機(jī)號(hào)獲取

公眾號(hào)網(wǎng)頁(yè)無(wú)法直接獲取手機(jī)號(hào)??尚械穆肪€是通過(guò)微信 JS-SDK 的方式,在已認(rèn)證的公眾號(hào)下引導(dǎo)用戶跳轉(zhuǎn)到小程序完成手機(jī)號(hào)授權(quán)再回傳,或使用網(wǎng)頁(yè)授權(quán)后讓用戶自行填寫手機(jī)號(hào),再走短信驗(yàn)證確??尚?。切勿嘗試通過(guò) userinfo 接口獲取手機(jī)號(hào),該字段早已被移除。

實(shí)現(xiàn)中必須校準(zhǔn)的三個(gè)邊界條件

用戶拒絕授權(quán)的狀態(tài)要持續(xù)保存

用戶在某次授權(quán)中選擇“拒絕”后,微信會(huì)緩存該狀態(tài)。短時(shí)間內(nèi)重復(fù)調(diào)用 scope=snsapi_userinfo 的授權(quán)鏈接不會(huì)再?gòu)棾鍪跈?quán)窗,直接返回拒絕結(jié)果。你的服務(wù)端需要記錄這個(gè)拒絕狀態(tài),并在前端通過(guò)提醒引導(dǎo)用戶進(jìn)入微信“設(shè)置-隱私-授權(quán)管理”中手動(dòng)恢復(fù),而不是無(wú)意義地反復(fù)拉取。

openid 的隔離性容易被忽略

同一個(gè)微信用戶在不同公眾號(hào)、不同小程序之間 openid 完全不同。只有將所有應(yīng)用綁定到同一開(kāi)放平臺(tái),才能通過(guò) unionid 打通。設(shè)計(jì)之初就要把開(kāi)放平臺(tái)綁定步驟列入上線 checklist。很多跨端數(shù)據(jù)斷裂的線上故障,追溯到最后都是“開(kāi)放平臺(tái)沒(méi)綁”這一條。

回調(diào)域名的安全模型

OAuth 流程中 redirect_uri 必須經(jīng)過(guò)微信公眾號(hào)后臺(tái)的“網(wǎng)頁(yè)授權(quán)域名”配置,且該域名的根目錄下需要放置一個(gè) MP_verify 文件。如果你的服務(wù)部署在多環(huán)境下(開(kāi)發(fā)、預(yù)發(fā)、生產(chǎn)),需要分別為每個(gè)環(huán)境配置獨(dú)立的域名或使用轉(zhuǎn)發(fā)規(guī)則,但域名本身不能包含動(dòng)態(tài)參數(shù),也不要嘗試用 localhost 或 IP 地址,這都會(huì)直接導(dǎo)致授權(quán)回調(diào)失敗。

行動(dòng)建議

把你當(dāng)前的業(yè)務(wù)需求映射到三層授權(quán)模型上。如果只需要用戶唯一標(biāo)識(shí),優(yōu)先使用靜默授權(quán),接口依賴少,失敗路徑短。如果需要展示昵稱頭像,小程序端請(qǐng)直接采用新版組件交互,公眾號(hào)網(wǎng)頁(yè)端仍可用 snsapi_userinfo 但做好拒絕處理。手機(jī)號(hào)授權(quán)務(wù)必獨(dú)立設(shè)計(jì)交互,不要讓它在關(guān)鍵商業(yè)流程中成為唯一的用戶標(biāo)識(shí)來(lái)源,因?yàn)橛脩敉耆赡懿皇跈?quán)手機(jī)號(hào),你必須有降級(jí)身份驗(yàn)證手段。

接下來(lái)可以檢查兩點(diǎn):一是你的服務(wù)端是否統(tǒng)一了 openid/unionid 的持久化格式和索引,避免不同模塊各自請(qǐng)求微信接口造成限頻;二是在所有調(diào)用微信接口的模塊中加入風(fēng)控響應(yīng)處理,尤其是 40029(code 無(wú)效)、40163(code 已被使用)和 -1(系統(tǒng)繁忙),這些錯(cuò)誤必須與業(yè)務(wù)錯(cuò)誤區(qū)分開(kāi)來(lái),并在用戶端給出可理解的提示。

← 上一篇 花了幾萬(wàn)塊做的微信商城只逛不買?你可能漏掉了這組核心功能 下一篇 → 微信公眾號(hào)菜單不是導(dǎo)航欄:重新理解菜單的入口價(jià)值與規(guī)劃邏輯