第一篇:理论
如果一个用户并未关注某个公众号,只是在微信内打开了公众号web服务器上的某个网页,要如何获取用户的openid以及用户的微信信息,以便实现业务逻辑呢?本篇讲述网页授权OAuth获取微信用户的关键信息。先来了解一个概念:
网页授权
如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。
微信网页授权能力调整公告
为进一步规范能力使用,保障用户合法权益,平台将对微信网页授权能力进行调整。 当开发者在网页中在不规范使用发起 snsapi_userinfo 网页授权时,微信将默认打开网页快照页模式进行基础浏览。 能力调整将于 2022 年 7 月 12 日 24 时生效。详情点击查看原公告《微信网页授权能力调整公告》。
关于网页授权回调域名的说明
- 在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
- 授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com 无法进行OAuth2.0鉴权
关于网页授权的两种 scope 的区别说明
- 以snsapi_base为 scope 发起的网页授权,是用来获取进入页面的用户的 openid 的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)
- 以snsapi_userinfo为 scope 发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。
- 用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户 OpenID 来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。
关于网页授权access_token和普通access_token的区别
- 微信网页授权是通过OAuth2.0机制实现的,在用户授权给公众号后,公众号可以获取到一个网页授权特有的接口调用凭证(网页授权access_token),通过网页授权access_token可以进行授权后接口调用,如获取用户基本信息;
- 其他微信接口,需要通过基础支持中的“获取access_token”接口来获取到的普通access_token调用。
关于特殊场景下的静默授权
- 上面已经提到,对于以snsapi_base为 scope 的网页授权,就静默授权的,用户无感知;
- 对于已关注公众号的用户,如果用户从公众号的会话或者自定义菜单进入本公众号的网页授权页,即使是 scope 为snsapi_userinfo,也是静默授权,用户无感知。
开发指南
网页授权流程分为四步:
- 引导用户进入授权页面同意授权,获取code
- 通过 code 换取网页授权access_token(与基础支持中的access_token不同)
- 如果需要,开发者可以刷新网页授权access_token,避免过期
- 通过网页授权access_token和 openid 获取用户基本信息(支持 UnionID 机制)
第一步:用户同意授权,获取code
在确保微信公众账号拥有授权作用域(scope参数)的权限的前提下(已认证服务号,默认拥有 scope 参数中的snsapi_base和snsapi_userinfo 权限),引导关注者打开如下页面:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
若提示“该链接无法访问”,请检查参数是否填写错误,是否拥有 scope 参数对应的授权作用域权限。
尤其注意:由于授权操作安全等级较高,所以在发起授权请求时,微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无法正常访问
参考链接(请在微信客户端中打开此链接体验):
scope为snsapi_base:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect
scope为snsapi_userinfo:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
第二步:通过 code 换取网页授权access_token
首先请注意,这里通过 code 换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。公众号可通过下述接口来获取网页授权access_token。如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。
尤其注意:由于公众号的 secret 和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。后续刷新access_token、通过access_token获取用户信息等步骤,也必须从服务器发起。
请求方法
获取 code 后,请求以下链接获取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
第三步:刷新access_token(如果需要)
由于access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新,refresh_token有效期为30天,当refresh_token失效之后,需要用户重新授权。
请求方法
获取第二步的refresh_token后,请求以下链接获取access_token:
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
第四步:拉取用户信息(需 scope 为 snsapi_userinfo)
如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和 openid 拉取用户信息了。
请求方法
http:GET(请使用 https 协议):
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
当一切正常时,返回的用户信息字段如下:
第二篇:实战
一、先准备一个专用类,直接上源码:
/*
*类名:QinMingWeixinOAuth
*归属:QinMing.WeixinOAuth命名空间
*用途:通过网页OAuth方式获取用户openid和access_token,进而可以获取微信用户的头像、昵称等信息
*作者:
*日期:8
*/
using System;
using System.Web;
using System.Net;
using System.IO;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using QinMing.Config;
namespace QinMing.WeixinOAuth
{
public class QinMingWeixinOAuth : System.Web.UI.Page
{
/// <summary>
/// 获取CODE,后期用于获取微信用户openid,静默,不需要用户确认
/// </summary>
public string GetCodeBase(string appid, string redirect_url)
{
var state = "QinMing" + DateTime.Now.Millisecond;
string RedirectUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?"
+ "appid=" + appid
+ "&redirect_uri=" + redirect_url
+ "&response_type=code&scope=snsapi_base"
+ "&state=" + state + "#wechat_redirect";
return RedirectUrl;
}
/// <summary>
/// 用CODE换取openid,适用于获取用户openid,在scope=snsapi_base使用
/// </summary>
public string GetOpenId(string code)
{
string appid = QinMingConfig.Weixin_AppId;
string secret = QinMingConfig.Weixin_AppSecret;
string strResult="";
string openid="";
string strurl="https://api.weixin.qq.com/sns/oauth2/access_token?appid="
+ appid + "&secret="
+ secret + "&code="
+ code + "&grant_type=authorization_code";
try
{
HttpWebRequest myReq = (HttpWebRequest)HttpWebRequest.Create(strurl);
HttpWebResponse HttpWResp = (HttpWebResponse)myReq.GetResponse();
Stream myStream = HttpWResp.GetResponseStream();
StreamReader sr = new StreamReader(myStream, Encoding.UTF8);
StringBuilder strBuilder = new StringBuilder();
while (-1 != sr.Peek())
{
strBuilder.Append(sr.ReadLine());
}
strResult = strBuilder.ToString();
JObject obj = (JObject)JsonConvert.DeserializeObject(HttpUtility.UrlDecode(strResult));
openid = obj["openid"].ToString().Replace("\"", "");
return openid;
}
catch
{
strResult = "err";
return strResult;
}
}
/// <summary>
/// 获取CODE,后期用于获取微信用户信息,需要用户确认;与上面GetCodeBase的差异只是scope=snsapi_userinfo部分
/// </summary>
public string GetCodeUserInfo(string appid, string redirect_url)
{
var state = "QinMing" + DateTime.Now.Millisecond;
string RedirectUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?"
+ "appid=" + appid
+ "&redirect_uri=" + redirect_url
+ "&response_type=code&scope=snsapi_userinfo"
+ "&state=" + state + "#wechat_redirect";
return RedirectUrl;
}
/// <summary>
/// 用CODE换取openid和AccessToken,适用于获取用户信息,在scope=snsapi_userinfo使用,切忌只能用在服务器端使用,不能传递到客户端,高风险
/// </summary>
public string GetOpenIdAndAccessToken(string code)
{
string appid = QinMingConfig.Weixin_AppId;
string secret = QinMingConfig.Weixin_AppSecret;
string strResult="";
string openid="";
string access_token="";
string strurl="https://api.weixin.qq.com/sns/oauth2/access_token?appid="
+ appid + "&secret="
+ secret + "&code="
+ code + "&grant_type=authorization_code";
try
{
HttpWebRequest myReq = (HttpWebRequest)HttpWebRequest.Create(strurl);
HttpWebResponse HttpWResp = (HttpWebResponse)myReq.GetResponse();
Stream myStream = HttpWResp.GetResponseStream();
StreamReader sr = new StreamReader(myStream, Encoding.UTF8);
StringBuilder strBuilder = new StringBuilder();
while (-1 != sr.Peek())
{
strBuilder.Append(sr.ReadLine());
}
strResult = strBuilder.ToString();
JObject obj = (JObject)JsonConvert.DeserializeObject(HttpUtility.UrlDecode(strResult));
openid = obj["openid"].ToString().Replace("\"", "");
access_token = obj["access_token"].ToString().Replace("\"", "");
return openid + "|" + access_token;
}
catch
{
strResult = "err";
return strResult;
}
}
/// <summary>
/// 获取用户信息,包含头像和昵称等
/// </summary>
public JObject GetUserInfo(string open_id, string access_token)
{
string strResult = "";
string strurl="https://api.weixin.qq.com/sns/userinfo?access_token=" + access_token + "&openid=" + open_id + "&lang=zh_CN";
try
{
HttpWebRequest myReq = (HttpWebRequest)HttpWebRequest.Create(strurl);
HttpWebResponse HttpWResp = (HttpWebResponse)myReq.GetResponse();
Stream myStream = HttpWResp.GetResponseStream();
StreamReader sr = new StreamReader(myStream, Encoding.UTF8);
StringBuilder strBuilder = new StringBuilder();
while (-1 != sr.Peek())
{
strBuilder.Append(sr.ReadLine());
}
strResult = strBuilder.ToString();
//更新weixin_user_info表中微信用户信息
JObject tmpobj = (JObject)JsonConvert.DeserializeObject(HttpUtility.UrlDecode(strResult));
QinMingToolsDB.UpdateTable("update weixin_user_info set nickname='" + tmpobj["nickname"].ToString().Replace("\"", "") + "',"
+ "headimgurl='" + tmpobj["headimgurl"].ToString().Replace("\"", "") + "',"
+ "province='" + tmpobj["province"].ToString().Replace("\"", "") + "',"
+ "city='" + tmpobj["city"].ToString().Replace("\"", "") + "',"
+ "country='" + tmpobj["country"].ToString().Replace("\"", "") + "',"
+ "sex='" + tmpobj["sex"].ToString().Replace("\"", "") + "' "
+ " where openid='" + open_id + "'");
}
catch
{
strResult = "err";
}
JObject obj = (JObject)JsonConvert.DeserializeObject(HttpUtility.UrlDecode(strResult));
return obj;
}
/*
//正确返回时JObject格式如下
{
"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"
}
*/
}
}
二、仅获取微信用户openid
使用方法:http://www.xxxx.com/weixin/RedirectWithOpenIdFirst.aspx?final_url=myweb.aspx
这样在myweb.aspx打开时将带入微信用户的openid参数。
RedirectWithOpenIdFirst.aspx源码:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="RedirectWithOpenIdFirst.aspx.cs" Inherits="Jjlm.RedirectWithOpenIdFirst" %>
RedirectWithOpenIdFirst.aspx.cs源码:
using System;
using System.Web;
using QinMing.Config;
using QinMing.WeixinOAuth;
namespace Jjlm
{
public partial class RedirectWithOpenIdFirst : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string final_url = Request.QueryString["final_url"];
string appid = QinMingConfig.Weixin_AppId;
string redirect_url = QinMingConfig.Url_WebServer + "/weixin/RedirectWithOpenIdSecond.aspx" + "?final_url=" + final_url;
QinMingWeixinOAuth oauth = new QinMingWeixinOAuth();
string newurl = oauth.GetCodeBase(appid, redirect_url);
Response.Redirect(newurl);
}
}
}
RedirectWithOpenIdSecond.aspx源码:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="RedirectWithOpenIdSecond.aspx.cs" Inherits="Jjlm.RedirectWithOpenIdSecond" %>
RedirectWithOpenIdSecond.aspx.cs源码:
using System;
using System.Web;
using QinMing.WeixinOAuth;
namespace Jjlm
{
public partial class RedirectWithOpenIdSecond : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string final_url = Request.QueryString["final_url"];
string code = Request.QueryString["code"];
QinMingWeixinOAuth oauth=new QinMingWeixinOAuth();
//仅获取openid
string open_id = oauth.GetOpenId(code);
Response.Redirect(final_url + "?open_id=" + open_id);
}
}
}
三、获取微信用户openid和昵称、头像等信息
使用方法:http://www.xxxx.com/Coalition/OAuthOpenidAndUserinfoFirst.aspx?final_url=myweb.aspx
这样在myweb.aspx打开时将带入微信用户的openid参数,同时带入了昵称nickname以及头像head_img_url参数。
OAuthOpenidAndUserinfoFirst.aspx源码:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="OAuthOpenidAndUserinfoFirst.aspx.cs" Inherits="Coalition.OAuthOpenidAndUserinfoFirst" %>
OAuthOpenidAndUserinfoFirst.aspx.cs源码:
using System;
using System.Web;
using QinMing.Config;
using QinMing.WeixinOAuth;
namespace Coalition
{
public partial class OAuthOpenidAndUserinfoFirst : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string final_url = Request.QueryString["final_url"];
string appid = QinMingConfig.Weixin_AppId;
string redirect_url = QinMingConfig.Url_WebServer + "/Coalition/OAuthOpenidAndUserinfoSecond.aspx" + "?final_url=" + final_url;
QinMingWeixinOAuth oauth = new QinMingWeixinOAuth();
string newurl = oauth.GetCodeUserInfo(appid, redirect_url);
Response.Redirect(newurl);
}
}
}
OAuthOpenidAndUserinfoSecond.aspx源码:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="OAuthOpenidAndUserinfoSecond.aspx.cs" Inherits="Coalition.OAuthOpenidAndUserinfoSecond" %>
OAuthOpenidAndUserinfoSecond.aspx.cs源码:
using System;
using System.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using QinMing.WeixinOAuth;
using QinMing.Tools;
namespace Coalition
{
public partial class OAuthOpenidAndUserinfoSecond : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string final_url = Request.QueryString["final_url"];
string code = Request.QueryString["code"];
QinMingWeixinOAuth oauth=new QinMingWeixinOAuth();
//获取openid和access_token
string openid_accesstoken = oauth.GetOpenIdAndAccessToken(code);
//QinMingTools.WriteLog("oauth2.0获取信息测试", openid_accesstoken);
//获取用户头像链接和昵称
string[] array = openid_accesstoken.Split(new Char[] { '|' });
string open_id = array[0];
string access_token = array[1];
JObject obj = oauth.GetUserInfo(open_id, access_token);
string head_img_url = obj["headimgurl"].ToString().Replace("\"", "");
string nickname = obj["nickname"].ToString().Replace("\"", "");
Response.Redirect(final_url + "?open_id=" + open_id + "&head_img_url=" + head_img_url + "&nickname=" + nickname);
}
}
}
重要提醒:通过网页获取微信用户的openid,除了用来实现线上销售等业务逻辑,常见的应用还有投票拉票、合伙人、裂变传播等。