(微信有个坑需要注意下,开发过程中有的时候会出现明明获取到code了,但是用code去获取token的时候却获取不到,这是因为程序多次回调获取code 导致code失效了。。报错{"errcode":40029,"errmsg":"invalid code"},官方回复:正常只有一次是微信的回调,有时候由于本地的安全策略或防火墙设置,在微信测调用url的时候本地服务器会构造一个相同请求。。。 解决方法1.如果用的测试号,可以尝试换成正式服务号再试下。2.获取code后用会话保持机制存储一个变量,然后在引导用户进入授权页面那里判断下这个变量有没有值,有值就不要在去请求授权页了。。问为什么不用code判断,,因为我试过用code判断没有用,我也不知道为什么,当然你也可以试下。。)
实现
第一步:引导用户进入授权页面同意授权,获取code
授权页面:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
因为微信公众号的授权回调页面域名只能设置一个,而我们的程序部署和授权回调页面域名不在同一个域名下,所以这里就出现了一个问题
解决办法:
因为授权回调页面域名就只有在获取code的时候需要使用,所以我们可以在授权回调页面域名服务器下写一个小程序作为中转站,通过这个中转站获取code然后再把code传回给我们的程序,这样就解决了授权回调页面域名的问题
具体代码:
<%@ WebHandler Language="C#" Class="Handler" %>
using System;
using System.Web;
public class Handler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
string appid = "你的appid";
string redirect_uri = context.Request.QueryString["redirect_uri"]; //获取code后回调的页面
string code = context.Request.QueryString["code"];
string state = context.Request.QueryString["state"]; //为了安全加上自定义密钥
if (!IsURL(redirect_uri))
{
context.Response.Write("URl验证失败");
return;
}
if (state != "你的秘钥")
{
context.Response.Write("验证失败");
return;
}
if (string.IsNullOrEmpty(code))
{
//第一次请求中转站code为空 引到用户进入登录授权页
string url = string.Format("https://open.weixin.qq.com/connect/oauth2/authorize?appid={0}&redirect_uri={1}&response_type=code&scope=snsapi_userinfo&state={2}#wechat_redirect"
, appid, "回调到中转站用来获取code" + redirect_uri, "你的秘钥");
context.Response.Redirect(url);
}
else //用户授权登录后会带着code请求redirect_uri,此时code不为空
{
//拼接url带着code请求我们的程序
if (redirect_uri.Contains("?"))
{
redirect_uri += "&code=" + code + "&state=你的秘钥";
}
else
{
redirect_uri += "?code=" + code + "&state=你的秘钥";
}
context.Response.Redirect(redirect_uri);
}
}
///
/// 验证是否是URL链接
///
/// 指定字符串
///
public static bool IsURL(string str)
{
string pattern = @"^(https?|ftp|file|ws)://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?$";
System.Text.RegularExpressions.Regex r = new System.Text.RegularExpressions.Regex(pattern);
System.Text.RegularExpressions.Match m = r.Match(str);
return m.Success;
}
public bool IsReusable
{
get
{
return false;
}
}
}
上面中转站中获取到code并请求我们的程序 ,然后我们的程序就可以拿这个code来做后面的事情了
第二步:通过code换取网页授权access_token
第三步:刷新access_token(如果需要)
第四步:拉取用户信息(需scope为 snsapi_userinfo)
public ActionResult GetUserInfo()
{
string state = QueryHelper.GetString("state");
if (state.Trim() != "你的密钥")
{
return Content("验证失败");
}
string redirect_url = "Index";
if (Session["redirect_url"] != null)
{
redirect_url = Session["redirect_url"].ToString();
}
if (Session["userInfo"] != null) //获取到的用户信息会存到session中
{
return Redirect(redirect_url);
}
string code = QueryHelper.GetString("Code");
WeiXinUserInfoProxy userInfo = new WeiXinUserInfoProxy();
WebClient client = new System.Net.WebClient();
client.Encoding = System.Text.Encoding.UTF8;
//获取access_token
string url = string.Format("https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret={1}&code={2}&grant_type=authorization_code", appid, secret, code);
string data = client.DownloadString(url);
JavaScriptSerializer serializer = new JavaScriptSerializer();
var obj = serializer.Deserialize>(data);
string accessToken;
if (!obj.TryGetValue("access_token", out accessToken))
return Content("access_toke获取失败" + data + ";" + code + ";" + url);
//获取用户信息
var opentid = obj["openid"];
Session["openid"] = opentid;
url = string.Format("https://api.weixin.qq.com/sns/userinfo?access_token={0}&openid={1}&lang=zh_CN", accessToken, opentid);
data = client.DownloadString(url);
userInfo = serializer.Deserialize(data);
Session["userInfo"] = userInfo;
Session.Remove("redirect_url");
return Redirect(redirect_url);
}
自定义分享可以参考这里点击打开链接
完整代码:
public class WeiXinDrawController : Controller
{
//
// GET: /WeiXinDraw/
string appid = "";
string secret = "";
public ActionResult Index()
{
if (Session["userInfo"] == null)
{
Session["redirect_url"] = Request.Url.PathAndQuery;
string url = string.Format("获取code的中转站地址?redirect_uri={0}&state={1}", "回调地址", "密钥");
return Redirect(url);
}
ViewData["appId"] = appid;
ViewData["timestamp"] = getTimestamp();
ViewData["nonceStr"] = getNoncestr();
ViewData["signature"] = Getsignature(getNoncestr(), getTimestamp());
return View();
}
[HttpPost]
public ActionResult Index(传入的参数)
{
WeiXinUserInfoProxy userInfo = new WeiXinUserInfoProxy();
if (Session["userInfo"] != null)
{
userInfo = (WeiXinUserInfoProxy)Session["userInfo"];
}
//后续代码
}
public ActionResult GetUserInfo()
{
string state = QueryHelper.GetString("state");
if (state.Trim() != "你的密钥")
{
return Content("验证失败");
}
string redirect_url = "Index";
if (Session["redirect_url"] != null)
{
redirect_url = Session["redirect_url"].ToString();
}
if (Session["userInfo"] != null) //获取到的用户信息会存到session中
{
return Redirect(redirect_url);
}
string code = QueryHelper.GetString("Code");
WeiXinUserInfoProxy userInfo = new WeiXinUserInfoProxy();
WebClient client = new System.Net.WebClient();
client.Encoding = System.Text.Encoding.UTF8;
//获取access_token
string url = string.Format("https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret={1}&code={2}&grant_type=authorization_code", appid, secret, code);
string data = client.DownloadString(url);
JavaScriptSerializer serializer = new JavaScriptSerializer();
var obj = serializer.Deserialize>(data);
string accessToken;
if (!obj.TryGetValue("access_token", out accessToken))
return Content("access_toke获取失败" + data + ";" + code + ";" + url);
//获取用户信息
var opentid = obj["openid"];
Session["openid"] = opentid;
url = string.Format("https://api.weixin.qq.com/sns/userinfo?access_token={0}&openid={1}&lang=zh_CN", accessToken, opentid);
data = client.DownloadString(url);
userInfo = serializer.Deserialize(data);
Session["userInfo"] = userInfo;
Session.Remove("redirect_url");
return Redirect(redirect_url);
}
///
/// 生成时间戳
/// 从 1970 年 1 月 1 日 00:00:00 至今的秒数,即当前的时间,且最终需要转换为字符串形式
///
///
public string getTimestamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalSeconds).ToString();
}
///
/// 生成随机字符串
///
///
public string getNoncestr()
{
Random random = new Random();
return StEntLib.Utility.ASymEncryption.MD5_Lower(random.Next(1000).ToString(), "GBK");
}
///
/// 获取access_token
///
///
public string Getaccesstoken()
{
string urljson = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + secret;
string strjson = "";
UTF8Encoding encoding = new UTF8Encoding();
HttpWebRequest myRequest =
(HttpWebRequest)WebRequest.Create(urljson);
myRequest.Method = "GET";
myRequest.ContentType = "application/x-www-form-urlencoded";
HttpWebResponse response;
Stream responseStream;
StreamReader reader;
string srcString;
response = myRequest.GetResponse() as HttpWebResponse;
responseStream = response.GetResponseStream();
reader = new System.IO.StreamReader(responseStream, Encoding.UTF8);
srcString = reader.ReadToEnd();
reader.Close();
if (srcString.Contains("access_token"))
{
//CommonJsonModel model = new CommonJsonModel(srcString);
BLL.CommonJsonModel model = new BLL.CommonJsonModel(srcString);
strjson = model.GetValue("access_token");
Session["access_tokenzj"] = strjson;
}
return strjson;
}
///
/// 获得jsapi_ticket
///
///
public string Getjsapi_ticket()
{
string accesstoken = (string)Session["access_tokenzj"];
string urljson = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accesstoken + "&type=jsapi";
string strjson = "";
UTF8Encoding encoding = new UTF8Encoding();
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(urljson);
myRequest.Method = "GET";
myRequest.ContentType = "application/x-www-form-urlencoded";
HttpWebResponse response = myRequest.GetResponse() as HttpWebResponse;
Stream responseStream = response.GetResponseStream();
StreamReader reader = new System.IO.StreamReader(responseStream, Encoding.UTF8);
string srcString = reader.ReadToEnd();
reader.Close();
if (srcString.Contains("ticket"))
{
BLL.CommonJsonModel model = new BLL.CommonJsonModel(srcString);
strjson = model.GetValue("ticket");
Session["ticketzj"] = strjson;
}
return strjson;
}
///
/// 生成signature
///
///
///
///
public string Getsignature(string nonceStr, string timespanstr)
{
if (Session["access_tokenzj"] == null)
{
Getaccesstoken();
}
if (Session["ticketzj"] == null)
{
Getjsapi_ticket();
}
string url = Request.Url.ToString();
string str = "jsapi_ticket=" + (string)Session["ticketzj"] + "&noncestr=" + nonceStr +
"×tamp=" + timespanstr + "&url=" + url;// +"&wxref=mp.weixin.qq.com";
string singature = getSha1(str);
string ss = singature;
return ss;
}
public static String getSha1(String str)
{
//建立SHA1对象
SHA1 sha = new SHA1CryptoServiceProvider();
//将mystr转换成byte[]
ASCIIEncoding enc = new ASCIIEncoding();
byte[] dataToHash = enc.GetBytes(str);
//Hash运算
byte[] dataHashed = sha.ComputeHash(dataToHash);
//将运算结果转换成string
string hash = BitConverter.ToString(dataHashed).Replace("-", "");
return hash;
}
页面:
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '<%=ViewData["appId"] %>', // 必填,公众号的唯一标识
timestamp: '<%=ViewData["timestamp"] %>', // 必填,生成签名的时间戳
nonceStr: '<%=ViewData["nonceStr"] %>', // 必填,生成签名的随机串
signature: '<%=ViewData["signature"] %>', // 必填,签名,见附录1
jsApiList: ['onMenuShareTimeline'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
wx.ready(function () {
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
wx.onMenuShareTimeline({
title: '', // 分享标题
link: '', // 分享链接
imgUrl: '', // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
$.ajax({
url: 'Index',
type: 'Post',
dataType: 'json',
data: $("#form").serialize(),
timeout: 1000,
cache: false,
success: function (msg) {
}
})
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
});