微信网页授权之使用完整服务解决方案

目录

微信网页授权能力调整造成的问题

能力调整的内容和理由

原有运行方案

is_snapshotuser字段 

改造原有方案

如何复现测试场景

小结


微信网页授权能力调整造成的问题

依附于第三方的开发,做为开发者经常会遇到第三方进行规范和开发的调整,如开发腾讯微信的相关应用。我所经历的如小程序隐私政策调整、信息备案调整、微信授权获取个人信息限制调整等。

最近我们的一些项目因为微信页面授权能力的调整出现了一些问题,对于新用户未经授权前,微信开发团队给出的输出是快照页,该页内所获取的openId等均为虚拟账号数据,并在屏幕下方非常不明显的显示“使用完整服务”,如下图所示:

此图即是微信给出的授权提示,也是我们折中的解决方案,图中所示的提示框源自己于我们通过携带的参数反馈给用户的提示,以引导用户点击下方的“使用完整服务”链接,并进行授权。

能力调整的内容和理由

微信团队给出的解释是当开发者在网页中在不规范使用发起 snsapi_userinfo 网页授权时,微信将默认打开网页快照页模式进行基础浏览。

微信网页授权规范

  1. 授权流程需引导清晰、准确:在申请获取用户信息的弹窗出现前,应该清晰、准确地告知用户获取信息的范围及获取信息的目的;
  2. 必要场景申请:在必须获取用户信息时才申请,而不是用户尚未了解服务前就强制弹窗。如使用医院挂号时才需要获取用户信息;
  3. 不强制登录:提供游客模式,供用户了解网页提供的基础服务,不强制用户允许网页获取用户信息后才能使用网页服务。

常见的微信网页授权不规范使用案例

  1. 强制登录:在用户打开网页时立即要求用户授权,用户拒绝后无法使用网页提供的服务;
  2. 违规收集个人信息:未在网页提前告知使用个人信息的目的、方式和范围;
  3. 非必要收集:非必要获取用户信息的网页,如文章、视频等,要求用户在浏览内容前登录;
  4. 差别对待微信用户:同样的网页在浏览器内可以无需登录直接访问,在微信内却要求用户先登录才可访问。

原有运行方案

微信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 数据库字段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>  /// 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空  /// </summary>  public string headimgurl{set { _headimgUrl = value; }get { return _headimgUrl; }}/// <summary>  /// 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)其实这个格式称不上JSON,只是个单纯数组  /// </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"))  {  //可能发生错误  }  //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数据包:

因此可能通过判断 is_snapshotuser 字段是否为1,判断是否快照页模式

改造原有方案

主要是增加 string is_snapshotuser = "0" 和后续对JSON返回值的判断,并返回回调的url并携带此参数。示例代码如下:

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 数据库字段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>  /// 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空  /// </summary>  public string headimgurl{set { _headimgUrl = value; }get { return _headimgUrl; }}/// <summary>  /// 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)其实这个格式称不上JSON,只是个单纯数组  /// </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"))  {  //可能发生错误  }  //Response.Write(returnText);  return returnText;  }  }

这样可以在业务页面,如上述代码中的index.aspx进行如下判断:

if (Request.QueryString["is_snapshotuser"] == "1")
{Layer.open("使用前微信要求您的授权,请点击下方使用完整服务后继续...", "'确定'", "info");return;
}

如何复现测试场景

已经授权的用户,如果想测试重新授权的场景,请打开微信,依如下步骤进行设置:

 

 

 

 

小结

以上示例是一种较小改动的解决方案,个人比较习惯于应用程序稳定性第一的思路。如果已经使用新规则设计方案则可仅供参考。

另外在此介绍一下关于网页授权的两种scope的区别:
1、以snsapi_base为scope发起的网页授权,可以直接获取进入页面的用户的openid,且是静默授权并自动跳转到业务页面。
2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。需要用户手动同意,无须关注,就可在授权后获取该用户的基本信息。

以上是个人的一些观点和解决方案,感谢阅读,并提出指正。

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/452305.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线程池,定时器以及阻塞队列(生产者/消费者模型)

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录专栏&#xff1a;线程池,定时器以及阻塞队列(生产者/消费者模型) &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 实现线程池,定时器以及阻塞队列,生产者/消费者模型 线程池线程池…

Linux------进程优先级与进程切换

目录 一、进程优先级 二、优先级与权限的区别 三、优先级的查看 四、进程优先级修改 五、进程切换 六、linux2.6内核调度队列与调度原理 一、进程优先级 首先我们得知道一个进程总是需要排队的&#xff0c;他一会在运行队列中排队等待运行&#xff0c;一会在设备的等待队…

【AcWing第141场周赛】AcWing 5464. 客人数量(A题)

文章目录 一、题目1、原题链接2、题目描述 二、解题报告1、思路分析2、时间复杂度3、代码详解 一、题目 1、原题链接 5464. 客人数量 2、题目描述 二、解题报告 1、思路分析 按题意模拟&#xff0c;累加求和即可&#xff0c;最终输出结果。 2、时间复杂度 时间复杂度为O(n…

git命令远程仓库推送本地项目报错了,解决方案

如果你在使用git命令上传本地项目到远程仓库遇到了如下错误&#xff1a; Updates were rejected because the tip of your current branch is behind。n 别慌&#xff0c;肯定是你的远程仓库里面有原始文件&#xff0c;需要你提前进行一下合并操作&#xff0c;然后才能使用pu…

ES高可用架构涉及常用功能整理

ES高可用架构涉及常用功能整理 1. es的高可用系统架构和相关组件2. es的核心参数2.1 常规配置2.2 特殊优化配置2.2.1 数据分片按ip打散2.2.2 数据分片机架感知2.2.3 强制要求数据分片机架感知2.2.4 写入线程池优化2.2.5 分片balance优化2.2.6 限流控制器优化 3. es常用命令3.1 …

用Python Tkinter打造的精彩连连看小游戏【附源码】

文章目录 连连看小游戏&#xff1a;用Python Tkinter打造的精彩游戏体验游戏简介技术背景MainWindow类:职责:方法:Point类: 主执行部分:完整代码&#xff1a;总结&#xff1a; 连连看小游戏&#xff1a;用Python Tkinter打造的精彩游戏体验 在丰富多彩的游戏世界中&#xff0c…

【C++】C++入门 — 类和对象初步介绍

类和对象 1 类的作用域2 类的实例化3 类对象模型4 this指针介绍&#xff1a;特性&#xff1a; Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;下一篇文章见&#xff01;&#xff01;&#xff01; 1 类的作用域 类定义了一个新的作用域&#xff0c;类的…

超级实用!Android Studio的10大神器插件,让你的开发效率翻倍!

超级实用&#xff01;Android Studio的10大神器插件&#xff0c;让你的开发效率翻倍&#xff01; Android Studio是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;专为Android应用程序开发而设计。它提供了丰富的工具和功能&#xff0c;使开发者能够轻松…

vulhub中Adminer远程文件读取漏洞复现(CVE-2021-43008)

Adminer是一个PHP编写的开源数据库管理工具&#xff0c;支持MySQL、MariaDB、PostgreSQL、SQLite、MS SQL、Oracle、Elasticsearch、MongoDB等数据库。 在其版本1.12.0到4.6.2之间存在一处因为MySQL LOAD DATA LOCAL导致的文件读取漏洞。 参考链接&#xff1a; https://gith…

Jenkins(本地Windows上搭建)上传 Pipeline构建前端项目并将生成dist文件夹上传至指定服务器

下载安装jdk https://www.oracle.com/cn/java/technologies/downloads/#jdk21-windows 下载jenkins window版 双击安装 https://www.jenkins.io/download/thank-you-downloading-windows-installer-stable/ 网页输入 http://localhost:8088/ 输入密码、设置账号、安装推…

Openresty+Lua+Redis实现高性能缓存

一、背景 当我们的程序需要提供较高的并发访问时&#xff0c;往往需要在程序中引入缓存技术&#xff0c;通常都是使用Redis作为缓存&#xff0c;但是要再更进一步提升性能的话&#xff0c;就需要尽可能的减少请求的链路长度&#xff0c;比如可以将访问Redis缓存从Tomcat服务器…