.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();
        }