目錄
微信網頁授權能力調整造成的問題
能力調整的內容和理由
原有運行方案
is_snapshotuser字段?
改造原有方案
如何復現(xiàn)測試場景
小結
微信網頁授權能力調整造成的問題
依附于第三方的開發(fā),做為開發(fā)者經常會遇到第三方進行規(guī)范和開發(fā)的調整,如開發(fā)騰訊微信的相關應用。我所經歷的如小程序隱私政策調整、信息備案調整、微信授權獲取個人信息限制調整等。
最近我們的一些項目因為微信頁面授權能力的調整出現(xiàn)了一些問題,對于新用戶未經授權前,微信開發(fā)團隊給出的輸出是快照頁,該頁內所獲取的openId等均為虛擬賬號數(shù)據,并在屏幕下方非常不明顯的顯示“使用完整服務”,如下圖所示:
此圖即是微信給出的授權提示,也是我們折中的解決方案,圖中所示的提示框源自己于我們通過攜帶的參數(shù)反饋給用戶的提示,以引導用戶點擊下方的“使用完整服務”鏈接,并進行授權。
能力調整的內容和理由
微信團隊給出的解釋是當開發(fā)者在網頁中在不規(guī)范使用發(fā)起 snsapi_userinfo 網頁授權時,微信將默認打開網頁快照頁模式進行基礎瀏覽。
微信網頁授權規(guī)范
- 授權流程需引導清晰、準確:在申請獲取用戶信息的彈窗出現(xiàn)前,應該清晰、準確地告知用戶獲取信息的范圍及獲取信息的目的;
- 必要場景申請:在必須獲取用戶信息時才申請,而不是用戶尚未了解服務前就強制彈窗。如使用醫(yī)院掛號時才需要獲取用戶信息;
- 不強制登錄:提供游客模式,供用戶了解網頁提供的基礎服務,不強制用戶允許網頁獲取用戶信息后才能使用網頁服務。
常見的微信網頁授權不規(guī)范使用案例
- 強制登錄:在用戶打開網頁時立即要求用戶授權,用戶拒絕后無法使用網頁提供的服務;
- 違規(guī)收集個人信息:未在網頁提前告知使用個人信息的目的、方式和范圍;
- 非必要收集:非必要獲取用戶信息的網頁,如文章、視頻等,要求用戶在瀏覽內容前登錄;
- 差別對待微信用戶:同樣的網頁在瀏覽器內可以無需登錄直接訪問,在微信內卻要求用戶先登錄才可訪問。
原有運行方案
微信OA2授權訪問地址如下(示例url為C#字符串):
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx7964497eb8bad783&redirect_uri=https%3A//www.leadihr.com/weixin/oa2.aspx%3F&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect&connect_redirect = 1
重定向接收地址 OA2.ASPX程序?(C#版本)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml;
using System.Collections;
using System.Net;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using LitJson;
using System.Data;
using System.Data.SqlClient;
using CosysJaneCommonAPI;
using System.Web.Script.Serialization;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
public partial class oa2 : System.Web.UI.Page
{
string Appid = "";
string appsecret = "";
string domain = "";
public class OAuth_Token
{
public OAuth_Token()
{
}
//access_token 網頁授權接口調用憑證,注意:此access_token與基礎支持的access_token不同
//expires_in access_token接口調用憑證超時時間,單位(秒)
//refresh_token 用戶刷新access_token
//openid 用戶唯一標識,請注意,在未關注公眾號時,用戶訪問公眾號的網頁,也會產生一個用戶和公眾號唯一的OpenID
//scope 用戶授權的作用域,使用逗號(,)分隔
public string access_token { get; set; }
public string expires_in { get; set; }
public string refresh_token { get; set; }
public string openid { get; set; }
public string scope { get; set; }
}
public class OAuthUser
{
public OAuthUser()
{ }
#region 數(shù)據庫字段
private string _openID;
private string _searchText;
private string _unionid;
private string _nickname;
private string _sex;
private string _province;
private string _city;
private string _country;
private string _headimgUrl;
// private string _privilege;
#endregion
#region 字段屬性
/// <summary>
/// 用戶的唯一標識
/// </summary>
public string openid
{
set { _openID = value; }
get { return _openID; }
}
public string SearchText
{
set { _searchText = value; }
get { return _searchText; }
}
/// <summary>
/// 用戶昵稱
/// </summary>
public string nickname
{
set { _nickname = value; }
get { return _nickname; }
}
public string unionid
{
set { _unionid = value; }
get { return _unionid; }
}
/// <summary>
/// 用戶的性別,值為1時是男性,值為2時是女性,值為0時是未知
/// </summary>
public string sex
{
set { _sex = value; }
get { return _sex; }
}
/// <summary>
/// 用戶個人資料填寫的省份
/// </summary>
public string province
{
set { _province = value; }
get { return _province; }
}
/// <summary>
/// 普通用戶個人資料填寫的城市
/// </summary>
public string city
{
set { _city = value; }
get { return _city; }
}
/// <summary>
/// 國家,如中國為CN
/// </summary>
public string country
{
set { _country = value; }
get { return _country; }
}
/// <summary>
/// 用戶頭像,最后一個數(shù)值代表正方形頭像大?。ㄓ?、46、64、96、132數(shù)值可選,0代表640*640正方形頭像),用戶沒有頭像時該項為空
/// </summary>
public string headimgurl
{
set { _headimgUrl = value; }
get { return _headimgUrl; }
}
/// <summary>
/// 用戶特權信息,json 數(shù)組,如微信沃卡用戶為(chinaunicom)其實這個格式稱不上JSON,只是個單純數(shù)組
/// </summary>
//public string privilege
//{
// set { _privilege = value; }
// get { return _privilege; }
//}
#endregion
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if (!string.IsNullOrEmpty(Request.QueryString["code"]))
{
string Code = Request.QueryString["code"].ToString();
string State = Request.QueryString["state"].ToString();
//獲得Token
OAuth_Token Model = Get_token(Code);
OAuthUser OAuthUser_Model = Get_UserInfo(Model.access_token, Model.openid);
string content=Model.access_token+ "用戶OPENID:" + OAuthUser_Model.openid + "<br>用戶昵稱:" + OAuthUser_Model.nickname + "<br>性別:" + OAuthUser_Model.sex + "<br>所在省:" + OAuthUser_Model.province + "<br>所在市:" + OAuthUser_Model.city + "<br>所在國家:" + OAuthUser_Model.country + "<br>頭像地址:" + OAuthUser_Model.headimgurl + "<br>用戶特權信息:";
Response.Redirect("https://x.x.com/index.aspx?&oid=" + OAuthUser_Model.openid);
}
}
}
public class JsonHelper
{
/// <summary>
/// 生成Json格式
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static string GetJson<T>(T obj)
{
DataContractJsonSerializer json = new DataContractJsonSerializer(obj.GetType());
using (MemoryStream stream = new MemoryStream())
{
json.WriteObject(stream, obj);
string szJson = Encoding.UTF8.GetString(stream.ToArray()); return szJson;
}
}
/// <summary>
/// 獲取Json的Model
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="szJson"></param>
/// <returns></returns>
public static T ParseFromJson<T>(string szJson)
{
T obj = Activator.CreateInstance<T>();
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(szJson)))
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
return (T)serializer.ReadObject(ms);
}
}
}
//獲得Token
protected OAuth_Token Get_token(string Code)
{
string Str = GetJson("https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + Appid + "&secret=" + appsecret + "&code=" + Code + "&grant_type=authorization_code");
OAuth_Token Oauth_Token_Model = JsonHelper.ParseFromJson<OAuth_Token>(Str);
return Oauth_Token_Model;
}
//刷新Token
protected OAuth_Token refresh_token(string REFRESH_TOKEN)
{
string Str = GetJson("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + Appid + "&grant_type=refresh_token&refresh_token=" + REFRESH_TOKEN);
OAuth_Token Oauth_Token_Model = JsonHelper.ParseFromJson<OAuth_Token>(Str);
return Oauth_Token_Model;
}
//獲得用戶信息
protected OAuthUser Get_UserInfo(string REFRESH_TOKEN, string OPENID)
{
// Response.Write("獲得用戶信息REFRESH_TOKEN:" + REFRESH_TOKEN + "||OPENID:" + OPENID);
string Str = GetJson("https://api.weixin.qq.com/sns/userinfo?access_token=" + REFRESH_TOKEN + "&openid=" + OPENID + "&lang=zh_CN");
OAuthUser OAuthUser_Model = JsonHelper.ParseFromJson<OAuthUser>(Str);
return OAuthUser_Model;
}
protected string GetJson(string url)
{
WebClient wc = new WebClient();
wc.Credentials = CredentialCache.DefaultCredentials;
wc.Encoding = Encoding.UTF8;
string returnText = "";
try
{
returnText = wc.DownloadString(url);
}
catch (Exception e)
{
Response.Write(e.Message);
Response.End();
}
if (returnText.Contains("errcode"))
{
//可能發(fā)生錯誤
}
//Response.Write(returnText);
return returnText;
}
}
is_snapshotuser字段?
通過code換取網頁授權access_token
請求方法是獲取code后,請求以下鏈接獲取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
正確時會返回具有如下圖所示的JSON數(shù)據包:
因此可能通過判斷 is_snapshotuser 字段是否為1,判斷是否快照頁模式
改造原有方案
主要是增加?string is_snapshotuser = "0" 和后續(xù)對JSON返回值的判斷,并返回回調的url并攜帶此參數(shù)。示例代碼如下:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml;
using System.Collections;
using System.Net;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using LitJson;
using System.Data;
using System.Data.SqlClient;
using CosysJaneCommonAPI;
using System.Web.Script.Serialization;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
public partial class oa2 : System.Web.UI.Page
{
string Appid = "";
string appsecret = "";
string domain = "";
string is_snapshotuser = "0";
public class OAuth_Token
{
public OAuth_Token()
{
}
//access_token 網頁授權接口調用憑證,注意:此access_token與基礎支持的access_token不同
//expires_in access_token接口調用憑證超時時間,單位(秒)
//refresh_token 用戶刷新access_token
//openid 用戶唯一標識,請注意,在未關注公眾號時,用戶訪問公眾號的網頁,也會產生一個用戶和公眾號唯一的OpenID
//scope 用戶授權的作用域,使用逗號(,)分隔
public string access_token { get; set; }
public string expires_in { get; set; }
public string refresh_token { get; set; }
public string openid { get; set; }
public string scope { get; set; }
}
public class OAuthUser
{
public OAuthUser()
{ }
#region 數(shù)據庫字段
private string _openID;
private string _searchText;
private string _unionid;
private string _nickname;
private string _sex;
private string _province;
private string _city;
private string _country;
private string _headimgUrl;
// private string _privilege;
#endregion
#region 字段屬性
/// <summary>
/// 用戶的唯一標識
/// </summary>
public string openid
{
set { _openID = value; }
get { return _openID; }
}
public string SearchText
{
set { _searchText = value; }
get { return _searchText; }
}
/// <summary>
/// 用戶昵稱
/// </summary>
public string nickname
{
set { _nickname = value; }
get { return _nickname; }
}
public string unionid
{
set { _unionid = value; }
get { return _unionid; }
}
/// <summary>
/// 用戶的性別,值為1時是男性,值為2時是女性,值為0時是未知
/// </summary>
public string sex
{
set { _sex = value; }
get { return _sex; }
}
/// <summary>
/// 用戶個人資料填寫的省份
/// </summary>
public string province
{
set { _province = value; }
get { return _province; }
}
/// <summary>
/// 普通用戶個人資料填寫的城市
/// </summary>
public string city
{
set { _city = value; }
get { return _city; }
}
/// <summary>
/// 國家,如中國為CN
/// </summary>
public string country
{
set { _country = value; }
get { return _country; }
}
/// <summary>
/// 用戶頭像,最后一個數(shù)值代表正方形頭像大?。ㄓ?、46、64、96、132數(shù)值可選,0代表640*640正方形頭像),用戶沒有頭像時該項為空
/// </summary>
public string headimgurl
{
set { _headimgUrl = value; }
get { return _headimgUrl; }
}
/// <summary>
/// 用戶特權信息,json 數(shù)組,如微信沃卡用戶為(chinaunicom)其實這個格式稱不上JSON,只是個單純數(shù)組
/// </summary>
//public string privilege
//{
// set { _privilege = value; }
// get { return _privilege; }
//}
#endregion
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if (!string.IsNullOrEmpty(Request.QueryString["code"]))
{
string Code = Request.QueryString["code"].ToString();
string State = Request.QueryString["state"].ToString();
//獲得Token
OAuth_Token Model = Get_token(Code);
OAuthUser OAuthUser_Model = Get_UserInfo(Model.access_token, Model.openid);
string content=Model.access_token+ "用戶OPENID:" + OAuthUser_Model.openid + "<br>用戶昵稱:" + OAuthUser_Model.nickname + "<br>性別:" + OAuthUser_Model.sex + "<br>所在省:" + OAuthUser_Model.province + "<br>所在市:" + OAuthUser_Model.city + "<br>所在國家:" + OAuthUser_Model.country + "<br>頭像地址:" + OAuthUser_Model.headimgurl + "<br>用戶特權信息:";
Response.Redirect("https://x.x.com/index.aspx?&oid=" + OAuthUser_Model.openid+"&is_snapshotuser="+is_snapshotuser);
}
}
}
public class JsonHelper
{
/// <summary>
/// 生成Json格式
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static string GetJson<T>(T obj)
{
DataContractJsonSerializer json = new DataContractJsonSerializer(obj.GetType());
using (MemoryStream stream = new MemoryStream())
{
json.WriteObject(stream, obj);
string szJson = Encoding.UTF8.GetString(stream.ToArray()); return szJson;
}
}
/// <summary>
/// 獲取Json的Model
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="szJson"></param>
/// <returns></returns>
public static T ParseFromJson<T>(string szJson)
{
T obj = Activator.CreateInstance<T>();
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(szJson)))
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
return (T)serializer.ReadObject(ms);
}
}
}
//獲得Token
protected OAuth_Token Get_token(string Code)
{
string Str = GetJson("https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + Appid + "&secret=" + appsecret + "&code=" + Code + "&grant_type=authorization_code");
if (Str.IndexOf("\"is_snapshotuser\":1") != -1||Str.IndexOf("\"is_snapshotuser\": 1")!=-1)
{
is_snapshotuser = "1";
}
OAuth_Token Oauth_Token_Model = JsonHelper.ParseFromJson<OAuth_Token>(Str);
return Oauth_Token_Model;
}
//刷新Token
protected OAuth_Token refresh_token(string REFRESH_TOKEN)
{
string Str = GetJson("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + Appid + "&grant_type=refresh_token&refresh_token=" + REFRESH_TOKEN);
OAuth_Token Oauth_Token_Model = JsonHelper.ParseFromJson<OAuth_Token>(Str);
return Oauth_Token_Model;
}
//獲得用戶信息
protected OAuthUser Get_UserInfo(string REFRESH_TOKEN, string OPENID)
{
// Response.Write("獲得用戶信息REFRESH_TOKEN:" + REFRESH_TOKEN + "||OPENID:" + OPENID);
string Str = GetJson("https://api.weixin.qq.com/sns/userinfo?access_token=" + REFRESH_TOKEN + "&openid=" + OPENID + "&lang=zh_CN");
OAuthUser OAuthUser_Model = JsonHelper.ParseFromJson<OAuthUser>(Str);
return OAuthUser_Model;
}
protected string GetJson(string url)
{
WebClient wc = new WebClient();
wc.Credentials = CredentialCache.DefaultCredentials;
wc.Encoding = Encoding.UTF8;
string returnText = "";
try
{
returnText = wc.DownloadString(url);
}
catch (Exception e)
{
Response.Write(e.Message);
Response.End();
}
if (returnText.Contains("errcode"))
{
//可能發(fā)生錯誤
}
//Response.Write(returnText);
return returnText;
}
}
這樣可以在業(yè)務頁面,如上述代碼中的index.aspx進行如下判斷:
if (Request.QueryString["is_snapshotuser"] == "1")
{
Layer.open("使用前微信要求您的授權,請點擊下方使用完整服務后繼續(xù)...", "'確定'", "info");
return;
}
如何復現(xiàn)測試場景
已經授權的用戶,如果想測試重新授權的場景,請打開微信,依如下步驟進行設置:
?文章來源地址http://www.zghlxwxcb.cn/news/detail-830072.html
?文章來源:http://www.zghlxwxcb.cn/news/detail-830072.html
?
?
小結
以上示例是一種較小改動的解決方案,個人比較習慣于應用程序穩(wěn)定性第一的思路。如果已經使用新規(guī)則設計方案則可僅供參考。
另外在此介紹一下關于網頁授權的兩種scope的區(qū)別:
1、以snsapi_base為scope發(fā)起的網頁授權,可以直接獲取進入頁面的用戶的openid,且是靜默授權并自動跳轉到業(yè)務頁面。
2、以snsapi_userinfo為scope發(fā)起的網頁授權,是用來獲取用戶的基本信息的。需要用戶手動同意,無須關注,就可在授權后獲取該用戶的基本信息。
以上是個人的一些觀點和解決方案,感謝閱讀,并提出指正。
?
?
?
到了這里,關于微信網頁授權之使用完整服務解決方案的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!