.NetCore5实现第三方微信扫码登录
第1步:请求code(前端)
第2步:前端通过获取的code请求API获取access_token(第2步之后都是后台API写代码)
第3步:API通过access_token调用接口
第4步:获取用户个人信息(UnionID机制)
先申请测试账号 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
这里填写你的域名
代码:
WeChatDefaults.cs
public static class WeChatDefaults
{
public const string AuthenticationScheme = "WeChat";
public static bool OpenWechatLogin = false;
/// <summary>
/// 开放平台申请审核后发放的AppId
/// </summary>
public static string AppId = "企业申请的AppId ";
/// <summary>
/// 开放平台申请审核后发放的密钥
/// </summary>
public static string AppSecret = "企业申请的AppSecret ";
/// <summary>
/// 开放平台申请的回调域名
/// </summary>
public static string RedirectUri = "xxx.com";
/// <summary>
/// 认证接口
/// </summary>
public static readonly string AuthorizationEndpoint = "https://open.weixin.qq.com/connect/oauth2/authorize";
/// <summary>
/// 认证接口二维码
/// https://open.weixin.qq.com/connect/qrconnect?appid=wxf1f84ee1c835dc1e&redirect_uri=http%3a%2f%2fwe-focus.cn&response_type=code&scope=snsapi_login&state=state#wechat_redirect
/// </summary>
public static readonly string AuthorizationQrEndpoint = "https://open.weixin.qq.com/connect/qrconnect";
/// <summary>
/// 通过code获取access_token 接口
/// https://api.weixin.qq.com/sns/oauth2/access_token?appid=wxf1f84ee1c835dc1e&secret=a0b7f9516303199ea3fe3b39f7a930b0&code=CODE&grant_type=authorization_code
/// </summary>
public static readonly string AccessTokenEndpoint = "https://api.weixin.qq.com/sns/oauth2/access_token";
/// <summary>
/// 刷新token 接口
/// https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=wxf1f84ee1c835dc1e&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
/// </summary>
public static readonly string RefreshTokenEndpoint = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
/// <summary>
/// 用户信息接口
/// https://api.weixin.qq.com/sns/userinfo?access_token=access_token&openid=open_id;
/// </summary>
public static readonly string UserInformationEndpoint = "https://api.weixin.qq.com/sns/userinfo";
}
WeChatHelper.cs
public class WeChatHelper
{
public WeChatHelper()
{
}
/// <summary>
/// 根据AppID和AppSecret获得access token(默认过期时间为2小时)
/// </summary>
/// <returns>Dictionary</returns>
public static Dictionary<string, object> GetAccessToken()
{
//获得配置信息
//oauth_config config = oauth_helper.get_config(2);
string send_url = WeChatDefaults.AccessTokenEndpoint
+ "?appid=" + WeChatDefaults.AppId
+ "&secret=" + WeChatDefaults.AppSecret
+ "&code=CODE&grant_type=authorization_code";
//发送并接受返回值
using (HttpClient client = new HttpClient())
{
var result = client.GetStringAsync(send_url);
if (string.IsNullOrEmpty(result.Result) && (result.Result.Contains("errmsg") || result.Result.Contains("errmsg")))
{
return null;
}
try
{
Dictionary<string, object> dic = JsonConvert.DeserializeObject<Dictionary<string, object>>(result.Result);
return dic;
}
catch
{
return null;
}
}
}
/// <summary>
/// 取得临时的Access Token(默认过期时间为2小时)
/// </summary>
/// <param name="code">临时Authorization Code</param>
/// <param name="state">防止CSRF攻击,成功授权后回调时会原样带回</param>
/// <returns>Dictionary</returns>
public static Dictionary<string, object> GetAccessToken(string code, string state)
{
string send_url = WeChatDefaults.AccessTokenEndpoint
+ "?appid=" + WeChatDefaults.AppId
+ "&secret=" + WeChatDefaults.AppSecret
+ "&code=" + code
+ "&state=" + state
+ "&grant_type=authorization_code";
//发送并接受返回值
using (HttpClient client = new HttpClient())
{
var result = client.GetStringAsync(send_url);
if (string.IsNullOrEmpty(result.Result) && (result.Result.Contains("errmsg") || result.Result.Contains("errmsg")))
{
return null;
}
try
{
Dictionary<string, object> dic = JsonConvert.DeserializeObject<Dictionary<string, object>>(result.Result);
return dic;
}
catch
{
return null;
}
}
}
/// <summary>
/// 根据access_token判断access_token是否过期
/// </summary>
/// <param name="access_token"></param>
/// <returns>true表示未失效</returns>
public static bool CheckAccessToken(string access_token)
{
string send_url = WeChatDefaults.AccessTokenEndpoint + "?access_token=" + access_token + "&openid=" + string.Empty;
//发送并接受返回值
using (HttpClient client = new HttpClient())
{
var result = client.GetStringAsync(send_url);
if (!string.IsNullOrEmpty(result.Result) && !(result.Result.Contains("errmsg") || result.Result.Contains("errmsg")))
{
try
{
Dictionary<string, object> dic = JsonConvert.DeserializeObject<Dictionary<string, object>>(result.Result);
if (dic.ContainsKey("errmsg"))
{
if (dic["errmsg"].ToString() == "ok")
{
return true;
}
else
{
return false;
}
}
return false;
}
catch
{
return false;
}
}
else
{
return false;
}
}
}
/// <summary>
/// 若fresh_token已过期则根据refresh_token取得新的refresh_token
/// </summary>
/// <param name="refresh_token">refresh_token</param>
/// <returns>Dictionary</returns>
public static Dictionary<string, object> GetRefreshToken(string refresh_token)
{
string send_url = WeChatDefaults.RefreshTokenEndpoint
+ "?appid=" + WeChatDefaults.AppId
+ "&grant_type=refresh_token&refresh_token=" + refresh_token;
//发送并接受返回值
using (HttpClient client = new HttpClient())
{
var result = client.GetStringAsync(send_url);
if (!string.IsNullOrEmpty(result.Result) && !(result.Result.Contains("errmsg") || result.Result.Contains("errmsg")))
{
return null;
}
try
{
Dictionary<string, object> dic = JsonConvert.DeserializeObject<Dictionary<string, object>>(result.Result);
return dic;
}
catch
{
return null;
}
}
}
/// <summary>
/// 获取登录用户自己的基本资料
/// </summary>
/// <param name="access_token">临时的Access Token</param>
/// <param name="open_id">用户openid</param>
/// <returns>Dictionary</returns>
public static Dictionary<string, object> GetUserInfo(string access_token, string open_id)
{
//发送并接受返回值
string send_url = WeChatDefaults.UserInformationEndpoint + "?access_token=" + access_token + "&openid=" + open_id;
//发送并接受返回值
using (HttpClient client = new HttpClient())
{
var result = client.GetStringAsync(send_url);
if (!string.IsNullOrEmpty(result.Result) && !(result.Result.Contains("errmsg") || result.Result.Contains("errmsg")))
{
return null;
}
Dictionary<string, object> dic = JsonConvert.DeserializeObject<Dictionary<string, object>>(result.Result);
return dic;
}
}
}
WeChatTocken.cs
public class WeChatTocken
{
/// <summary>
/// 接口调用凭证
/// </summary>
public string Access_Token { get; set; }
/// <summary>
/// access_token接口调用凭证超时时间,单位(秒)
/// </summary>
public int Expires_In { get; set; }
/// <summary>
/// 用户刷新access_token
/// </summary>
public string Refresh_Token { get; set; }
/// <summary>
/// 授权用户唯一标识
/// </summary>
public string OpenId { get; set; }
/// <summary>
/// 用户授权的作用域,使用逗号(,)分隔
/// </summary>
public string Scope { get; set; }
/// <summary>
/// 当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。
/// </summary>
public string UnionId { get; set; }
}
WeChatUserInfo.cs
public class WeChatUserInfo
{
/// <summary>
/// 授权用户唯一标识
/// </summary>
public string OpenId { get; set; }
/// <summary>
/// 昵称
/// </summary>
public string NickName { get; set; }
/// <summary>
/// 性别
/// </summary>
public int Sex { get; set; }
/// <summary>
/// 头像路径
/// </summary>
public string HeadImgUrl { get; set; }
/// <summary>
/// 省份
/// </summary>
public string Province { get; set; }
/// <summary>
/// 城市
/// </summary>
public string City { get; set; }
/// <summary>
/// 国家
/// </summary>
public string Country { get; set; }
/// <summary>
/// 特权
/// </summary>
public string[] Privilege { get; set; }
/// <summary>
/// 当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。
/// </summary>
public string UnionId { get; set; }
}
/// <summary>
/// 微信登录生成登录二维码
/// </summary>
/// <returns></returns>
[HttpPost]
[ServiceFilter(typeof(ResponseLogTimeFilter))]
public async Task<IActionResult> WeChatGenerateQRCode()
{
var redisKeyName = memoryCache.Get(GobalConfig.SYSTEM_CONFIG_UNIQUEIDENTIFICATION_STRING);
var ipLoginRes = await RedisHelper.GetAsync(redisKeyName+ GobalConfig.SYSTEM_WECHATQRREDISKEY_STRING + ip);
if (!string.IsNullOrEmpty(ipLoginRes))
{
var ipval = Convert.ToInt32(ipLoginRes);
if (ipval>=5)
{
return Content(new AjaxResult().Error("您频繁登录,已被禁止登录3分钟!"));
}
else
{
ipval++;
await RedisHelper.SetAsync(redisKeyName + GobalConfig.SYSTEM_WECHATQRREDISKEY_STRING + ip,ipval.ToString(), TimeSpan.FromMinutes(3));
}
}
else
{
await RedisHelper.SetAsync(redisKeyName + GobalConfig.SYSTEM_WECHATQRREDISKEY_STRING + ip, "1",TimeSpan.FromMinutes(3));
}
var guid = Guid.NewGuid().ToString();
var websiteUrl = memoryCache.Get(GobalConfig.SYSTEM_CONFIG_STRING+ "Url");
var url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + WeChatDefaults.AppId + "&redirect_uri="+ websiteUrl + "/api/admin/public/login/wechatloginin&response_type=code&scope=snsapi_userinfo&state="+ guid + "&connect_redirect=1#wechat_redirect";
HttpContext.Response.Cookies.Append(GobalConfig.SYSTEM_WECHATUSERREDISKEY_STRING, guid, new CookieOptions
{
Expires = DateTime.Now.AddMinutes(20),Secure= true
});
await RedisHelper.SetAsync(redisKeyName + GobalConfig.SYSTEM_WECHATUSERREDISKEY_STRING + guid, "0", TimeSpan.FromMinutes(10));
var result = new AjaxResult()
{
code = AjaxResultStateEnum.Ok, message = "/api/qrcode?url=" + StringExtension.UrlEncode(url), data = guid
};
return Content(JsonHelper.Serialize(result));
}
/// <summary>
/// 微信刷新登录状态
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> WeChatRefreshStatus()
{
var isOpen = memoryCache.Get(GobalConfig.SYSTEM_CONFIG_STRING + "OpenWechatLogin").ToString();
if (isOpen != "1")
{
return Content(new AjaxResult().Fail("网站关闭微信登录!"));
}
var value = "";
HttpContext.Request.Cookies.TryGetValue(GobalConfig.SYSTEM_WECHATUSERREDISKEY_STRING, out value);
var redisKeyName = memoryCache.Get(GobalConfig.SYSTEM_CONFIG_UNIQUEIDENTIFICATION_STRING);
var loginVal = await RedisHelper.GetAsync(redisKeyName + GobalConfig.SYSTEM_WECHATUSERREDISKEY_STRING + value);
if (string.IsNullOrWhiteSpace(loginVal))
{
return Content(new AjaxResult().Fail("超时!请重新扫码!"));
}
if (loginVal == "0")
{
return Content(new AjaxResult().Error("请扫码!"));
}
else
{
if (loginVal=="-1")
{
return Content(new AjaxResult().Fail("登录失败!该账号未绑定微信!"));
}
var ip = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (string.IsNullOrEmpty(ip))
{
ip = HttpContext.Connection.RemoteIpAddress.ToString();
}
ip = ip.Replace("::ffff:", "");
//登录处理
var user = await adminService.GetInfoById(Convert.ToInt32(loginVal));
if (user.status != 1)
{
return Content(new AjaxResult().Fail("您被禁止登录!"));
}
logger.LogInformation(new Exception("微信-登录成功"), "ip:" + ip.Replace("::ffff:", "") + " 用户名:" + user.username);
//更新最后登录信息
await adminService.UpdateLoginStatusInfo(user.id, ip.Replace("::ffff:", ""));
//存入Session
HttpContext.Session.SetString("Admin", JsonHelper.Serialize(user));
return Content(new AjaxResult().Ok("登录成功!"));
}
}
/// <summary>
/// 微信跳转授权登录
/// </summary>
/// <returns></returns>
[HttpGet]
[ServiceFilter(typeof(ResponseLogTimeFilter))]
public async Task<IActionResult> WeChatLoginIn(string code,string state)
{
try
{
logger.LogInformation(code);
HttpContext.Response.ContentType = "text/html;charset=utf-8";
var isOpen = memoryCache.Get(GobalConfig.SYSTEM_CONFIG_STRING + "OpenWechatLogin").ToString();
if (isOpen != "1")
{
return Content("<script>alert('网站关闭微信登录!')</script>");
}
if (string.IsNullOrWhiteSpace(code) || string.IsNullOrWhiteSpace(state))
{
return Content("<script>alert('登录失败')</script>");
}
Random random = new Random(Environment.TickCount);
//string tokenUri = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wxf1f84ee1c835dc1e&secret=a0b7f9516303199ea3fe3b39f7a930b0&code=0110XdHa1yrN4A0gojHa1kJ4ww40XdHe&state=state&grant_type=authorization_code";
string tokenUri = WeChatDefaults.AccessTokenEndpoint
+ "?appid=" + WeChatDefaults.AppId
+ "&secret=" + WeChatDefaults.AppSecret
+ "&code=" + code//code为前端传入参数,自行获取
+ "&state=" + state
+ "&grant_type=authorization_code";
string resultAccessToken = ClientRequest(tokenUri, string.Empty, "POST");
logger.LogInformation("dunbwquidbyuqwbvbyuwq");
if (!string.IsNullOrEmpty(resultAccessToken))
{
WeChatTocken weChatTocken1 = new WeChatTocken();
weChatTocken1 = JsonConvert.DeserializeObject<WeChatTocken>(resultAccessToken);
if (!string.IsNullOrEmpty(weChatTocken1.Access_Token))
{
string userUri = WeChatDefaults.UserInformationEndpoint
+ "?access_token="
+ weChatTocken1.Access_Token
+ "&openid=" + weChatTocken1.OpenId;
string resultUserInfo = ClientRequest(userUri, string.Empty, "POST");
if (!string.IsNullOrEmpty(resultUserInfo))
{
WeChatUserInfo weChatUserInfo1 = new WeChatUserInfo();
weChatUserInfo1 = JsonConvert.DeserializeObject<WeChatUserInfo>(resultUserInfo);
if (!string.IsNullOrEmpty(weChatUserInfo1.OpenId))
{
var redisKeyName = memoryCache.Get(GobalConfig.SYSTEM_CONFIG_UNIQUEIDENTIFICATION_STRING);
var loginVal = await RedisHelper.GetAsync(redisKeyName + GobalConfig.SYSTEM_WECHATUSERREDISKEY_STRING + state);
if (string.IsNullOrWhiteSpace(loginVal))
{
return Content("<script>alert('二维码过期,请重新扫码登录!')</script>");
}
var uid = await adminService.GetIdByOpenId("wechat", weChatUserInfo1.OpenId);
await RedisHelper.SetAsync(redisKeyName + GobalConfig.SYSTEM_WECHATUSERREDISKEY_STRING + state, uid, TimeSpan.FromMinutes(10));
return Content("<script>alert('扫码成功,请在网页中查看!')</script>");
}
else
{
return Ok("<script>alert('扫码失败,请联系网站管理员!')</script>");
}
}
else
{
logger.LogInformation("请求微信用户基本信息失败");
return Content("<script>alert('请求微信用户基本信息失败')</script>");
}
}
else
{
logger.LogInformation("请求access_token失败");
return Content("<script>alert('请求access_token失败')</script>");
}
}
return Content("<script>alert('登录失败')</script>");
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 发送请求(get/post/http/https)
/// </summary>
/// <param name="uri">请求地址</param>
/// <param name="JsonStr">json数据</param>
/// <param name="Method">请求方式POST/GET</param>
/// <returns></returns>
public string ClientRequest(string uri, string JsonStr, string Method = "POST")
{
try
{
var httpRequest = (HttpWebRequest)HttpWebRequest.Create(uri);
httpRequest.Method = Method;
httpRequest.ContentType = "application/json";
if (Method.ToLower() == "get")
{
httpRequest.ContentType = "application/x-www-form-urlencoded";
}
httpRequest.Proxy = null;
httpRequest.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13";
httpRequest.Headers.Add("Accept-Language", "zh-cn,en-us;q=0.8,zh-hk;q=0.6,ja;q=0.4,zh;q=0.2");
httpRequest.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
//如果是发送HTTPS请求
if (uri.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
httpRequest.ProtocolVersion = HttpVersion.Version10;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
}
else
{
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
}
if (!string.IsNullOrEmpty(JsonStr))
{
using (var dataStream = new StreamWriter(httpRequest.GetRequestStream()))
{
dataStream.Write(JsonStr);
dataStream.Flush();
dataStream.Close();
}
}
var httpResponse = (HttpWebResponse)httpRequest.GetResponse();
using (var dataStream = new StreamReader(httpResponse.GetResponseStream()))
{
var result = dataStream.ReadToEnd();
return result;
}
}
catch (Exception ex)
{
return "{\"error\":\"" + ex.Message + "\"}";
}
}
private bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
//throw new NotImplementedException();
}