開發背景
當使用者在微信客戶端中訪問第三方網頁,公眾號可以透過微信網頁授權機制,來獲取使用者基本資訊,進而實現業務邏輯。我們一般透過使用者網頁授權來無感實現使用者登入,並獲取使用者的微信資訊。
注意:使用者管理類介面中的“獲取使用者基本資訊介面”,是在使用者和公眾號產生訊息互動或關注後事件推送後,才能根據使用者OpenID來獲取使用者基本資訊。這個介面,包括其他微信介面,都是需要該使用者(即openid)關注了公眾號後,才能呼叫成功的。
開發前配置
需要先到公眾平臺官網中的「設定與開發」-「功能設定」-「網頁授權域名」的配置選項中,修改授權回撥域名。請注意,這裡填寫的是域名(是一個字串),而不是URL,因此請勿加 http:// 等協議頭;
網頁授權的兩種scope的區別說明
- 以snsapi_base為scope發起的網頁授權,是用來獲取進入頁面的使用者的openid的,並且是靜默授權並自動跳轉到回撥頁的。使用者感知的就是直接進入了回撥頁(往往是業務頁面)
- 以snsapi_userinfo為scope發起的網頁授權,是用來獲取使用者的基本資訊的。但這種授權需要使用者手動同意,並且由於使用者同意過,所以無須關注,就可在授權後獲取該使用者的基本資訊。
第一步、使用者同意授權獲取code
在確保微信公眾賬號擁有授權作用域(scope引數)的許可權的前提下(已認證服務號,預設擁有scope引數中的snsapi_base和snsapi_userinfo 許可權),引導關注者開啟如下頁面:
若提示“該連結無法訪問”,請檢查引數是否填寫錯誤,是否擁有scope引數對應的授權作用域許可權。跳轉回撥redirect_uri,應當使用https連結來確保授權code的安全性,並且是在微信公眾號後臺配置的網頁授權域名的訪問地址。
- https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
請求引數:
引數 | 是否必須 | 說明 |
---|---|---|
appid | 是 | 公眾號的唯一標識 |
redirect_uri | 是 | 授權後重定向的回撥連結地址, 請使用 urlEncode 對連結進行處理 |
response_type | 是 | 返回型別,請填寫code |
scope | 是 | 應用授權作用域,snsapi_base (不彈出授權頁面,直接跳轉,只能獲取使用者openid),snsapi_userinfo (彈出授權頁面,可透過openid拿到暱稱、性別、所在地。並且, 即使在未關注的情況下,只要使用者授權,也能獲取其資訊 ) |
state | 否 | 重定向後會帶上state引數,開發者可以填寫a-zA-Z0-9的引數值,最多128位元組 |
#wechat_redirect | 是 | 無論直接開啟還是做頁面302重定向時候,必須帶此引數 |
forcePopup | 否 | 強制此次授權需要使用者彈窗確認;預設為false;需要注意的是,若使用者命中了特殊場景下的靜默授權邏輯,則此引數不生效 |
第二步、透過code換取網頁授權access_token
首先請注意,這裡透過code換取的是一個特殊的網頁授權access_token,與基礎支援中的access_token(該access_token用於呼叫其他介面)不同。公眾號可透過下述介面來獲取網頁授權access_token。如果網頁授權的作用域為snsapi_base,則本步驟中獲取到網頁授權access_token的同時,也獲取到了openid,snsapi_base式的網頁授權流程即到此為止。
- 獲取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 |
返回引數:
引數 | 描述 |
---|---|
access_token | 網頁授權介面呼叫憑證,注意:此access_token與基礎支援的access_token不同 |
expires_in | access_token介面呼叫憑證超時時間,單位(秒) |
refresh_token | 使用者重新整理access_token |
openid | 使用者唯一標識,請注意,在未關注公眾號時,使用者訪問公眾號的網頁,也會產生一個使用者和公眾號唯一的OpenID |
scope | 使用者授權的作用域,使用逗號(,)分隔 |
is_snapshotuser | 是否為快照頁模式虛擬賬號,只有當使用者是快照頁模式虛擬賬號時返回,值為1 |
unionid | 使用者統一標識(針對一個微信開放平臺賬號下的應用,同一使用者的 unionid 是唯一的),只有當scope為"snsapi_userinfo"時返回 |
正確時返回的JSON資料包如下:
{ "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE", "is_snapshotuser": 1, "unionid": "UNIONID" }
錯誤時微信會返回JSON資料包如下(示例為Code無效錯誤):
{"errcode":40029,"errmsg":"invalid code"}
請求示例程式碼:
public class WeChatLogin : Controller
{
/// <summary>
/// 獲取微信網頁授權access_token
/// </summary>
/// <param name="state">自定義引數</param>
/// <param name="code">透過使用者授權後得到的code</param>
/// <returns></returns>
public async Task<Response> GetWeChatAccessToken(string state, string code)
{
string appId = "YourAppId";
string appSecret = "YourAppSecret";
string requestUrl = $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={appId}&secret={appSecret}&code={code}&grant_type=authorization_code";
using (var httpClient = new HttpClient())
{
var httpRequest = new HttpRequestMessage(HttpMethod.Get, requestUrl);
using (var response = await httpClient.SendAsync(httpRequest))
{
if (response.IsSuccessStatusCode)
{
var responseString = await response.Content.ReadAsStringAsync();
var responseData = JsonConvert.DeserializeObject<WeChatTokenResponse>(responseString);
return new Response
{
Code = 1,
Message = responseData.AccessToken
};
}
else
{
var errorResponseString = await response.Content.ReadAsStringAsync();
var errorData = JsonConvert.DeserializeObject<ErrorResponse>(errorResponseString);
return new Response
{
Code = 0,
Message = $"Failed to get access token: {errorData.ErrMsg}"
};
}
}
}
}
}
public class WeChatTokenResponse
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
[JsonProperty("openid")]
public string OpenId { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
[JsonProperty("is_snapshotuser")]
public int IsSnapshotUser { get; set; }
[JsonProperty("unionid")]
public string UnionId { get; set; }
}
public class ErrorResponse
{
[JsonProperty("errcode")]
public int ErrCode { get; set; }
[JsonProperty("errmsg")]
public string ErrMsg { get; set; }
}
第三步、獲取使用者資訊(需scope為 snsapi_userinfo)
如果網頁授權作用域為snsapi_userinfo,則此時開發者可以透過access_token和openid拉取使用者資訊了。
- 請求方法:https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
請求引數:
引數 | 描述 |
---|---|
access_token | 網頁授權介面呼叫憑證,注意:此access_token與基礎支援的access_token不同 |
openid | 使用者的唯一標識 |
lang | 返回國家地區語言版本,zh_CN 簡體,zh_TW 繁體,en 英語 |
返回引數:
引數 | 描述 |
---|---|
openid | 使用者的唯一標識 |
nickname | 使用者暱稱 |
sex | 使用者的性別,值為1時是男性,值為2時是女性,值為0時是未知 |
province | 使用者個人資料填寫的省份 |
city | 普通使用者個人資料填寫的城市 |
country | 國家,如中國為CN |
headimgurl | 使用者頭像,最後一個數值代表正方形頭像大小(有0、46、64、96、132數值可選,0代表640*640正方形頭像),使用者沒有頭像時該項為空。若使用者更換頭像,原有頭像URL將失效。 |
privilege | 使用者特權資訊,json 陣列,如微信沃卡使用者為(chinaunicom) |
unionid | 只有在使用者將公眾號繫結到微信開放平臺賬號後,才會出現該欄位。 |
正確時返回的JSON資料包如下:
{ "openid": "OPENID", "nickname": NICKNAME, "sex": 1, "province":"PROVINCE", "city":"CITY", "country":"COUNTRY", "headimgurl":"https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46", "privilege":[ "PRIVILEGE1" "PRIVILEGE2" ], "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL" }
錯誤時微信會返回JSON資料包如下(示例為openid無效):
{"errcode":40003,"errmsg":" invalid openid "}
請求示例程式碼:
public class WeChatLogin : Controller
{
/// <summary>
/// 使用者資訊獲取
/// </summary>
/// <param name="accessToken"> 網頁授權介面呼叫憑證,注意:此access_token與基礎支援的access_token不同</param>
/// <param name="openId">使用者的唯一標識</param>
/// <returns></returns>
public async Task<Response> GetWeChatUserInfo(string accessToken, string openId)
{
string requestUrl = $"https://api.weixin.qq.com/sns/userinfo?access_token={accessToken}&openid={openId}&lang=zh_CN";
using (var httpClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
var response = await httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseString = await response.Content.ReadAsStringAsync();
var responseData = JsonConvert.DeserializeObject<WeChatUserInfoResponse>(responseString);
return new Response
{
Code = 1,
Message = $"Nickname: {responseData.Nickname}, Province: {responseData.Province}, City: {responseData.City}"
};
}
else
{
var errorResponseString = await response.Content.ReadAsStringAsync();
var errorData = JsonConvert.DeserializeObject<ErrorResponse>(errorResponseString);
return new Response
{
Code = 0,
Message = $"Failed to get user info: {errorData.ErrMsg}"
};
}
}
}
public class WeChatUserInfoResponse
{
[JsonProperty("openid")]
public string OpenId { get; set; }
[JsonProperty("nickname")]
public string Nickname { get; set; }
[JsonProperty("sex")]
public int Sex { get; set; }
[JsonProperty("province")]
public string Province { get; set; }
[JsonProperty("city")]
public string City { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
[JsonProperty("headimgurl")]
public string HeadImgUrl { get; set; }
[JsonProperty("privilege")]
public List<string> Privilege { get; set; }
[JsonProperty("unionid")]
public string UnionId { get; set; }
}
public class ErrorResponse
{
[JsonProperty("errcode")]
public int ErrCode { get; set; }
[JsonProperty("errmsg")]
public string ErrMsg { get; set; }
}
}
DotNetGuide技術社群交流群
- DotNetGuide技術社群是一個面向.NET開發者的開源技術社群,旨在為開發者們提供全面的C#/.NET/.NET Core相關學習資料、技術分享和諮詢、專案推薦、招聘資訊和解決問題的平臺。
- 在這個社群中,開發者們可以分享自己的技術文章、專案經驗、遇到的疑難技術問題以及解決方案,並且還有機會結識志同道合的開發者。
- 我們致力於構建一個積極向上、和諧友善的.NET技術交流平臺,為廣大.NET開發者帶來更多的價值和成長機會。
.NET微信網頁開發相關文章教程
參考文章
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html