.NET Core中 实现H5微信登录(静默授权方式)

需求

假设现在有一个H5需要有微信登录、手机号登录、邮箱登录 三种登录方式。让我们一起来看看微信登录如何实现吧

界面:

最终实现的效果图(登录成功后返回个人页):

因为微信登录目前没有实现移动端的其他浏览器授权登录,所以,再用除微信以外的浏览器操作登录时,我们需要给出用户提醒,比如这样:

实现

准备工作

登录服务号或订阅号的微信公众号后台,找到AppId以及AppSecret。后面会用到

在公众号设置中,设置安全域名、js域名以及网页授权域名

其中再网页授权域名设置的时候需要注意,将腾讯提供的一个唯一标识文件存放于项目根目录下

数据库部分

新建一张Login表,用于存放用户登录信息

CREATE TABLE `NewTable` (
`id`  int(11) NOT NULL AUTO_INCREMENT ,
`loginaccount`  varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL ,
`password`  varchar(45) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL ,
`type`  tinyint(4) NOT NULL ,
`userid`  int(11) NULL DEFAULT 0 ,
`isvalid`  tinyint(2) NULL DEFAULT 1 ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_bin
AUTO_INCREMENT=28
ROW_FORMAT=DYNAMIC
;

前端部分

前端要做的 比较简单,放置一个button按钮,以及js处理判断是否是微信内点击即可:

<div class="row">
    <div class="col-xs-12">
        <button type="button" class="btn  btn-lg btn-block wechatBtn" id="weChatLogin">微信</button>
    </div>
</div>

对应的js部分为:

$("#weChatLogin").on("click",
        function () {
            var layerLoading = layer.load(1, {
                icon: 0,
                shade: [0.3, ‘black‘]
            });
            var result = isWeiXin();
            if (result === 0) {
                setTimeout(function () {
                    layer.closeAll();
                    var local = "回调地址";
                    window.location.href =
                        ‘https://open.weixin.qq.com/connect/oauth2/authorize?appid=服务号的appId&redirect_uri=‘ +
                        encodeURIComponent(local) +
                        ‘&response_type=code&scope=snsapi_base&state=a#wechat_redirect‘;
                },
                    500);
            } else {
                setTimeout(function () {
                    layer.closeAll();
                    layer.msg("请在微信内打开~<br/>或尝试下其他登录方式哦");

                },500);
            }
        });

上面这段js代码中,有两个黄色背景的代码需要注意,函数isWeiXin是用于判断当前用户打开的浏览器是否是微信浏览器,而参数snsapi_base则表示微信登录时采取的静默授权方式(即这样 只能获取到用户的Openid,无法获取到其他资料).

isWeiXin函数如下

 //判断是否是微信浏览器的函数
    function isWeiXin() {
        //window.navigator.userAgent属性包含了浏览器类型、版本、操作系统类型、浏览器引擎类型等信息,这个属性可以用来判断浏览器类型
        if (browser.versions.mobile) {//判断是否是移动设备打开。browser代码在下面
            var ua = navigator.userAgent.toLowerCase();//获取判断用的对象
            if (ua.match(/MicroMessenger/i) == "micromessenger") {
                return 0;
            } else {
                return 1;
            }
        } else {
            //否则就是PC浏览器打开
            return 2;
        }
    }

    var browser = {
        versions: function () {
            var u = navigator.userAgent, app = navigator.appVersion;
            return {         //移动终端浏览器版本信息
                trident: u.indexOf(‘Trident‘) > -1, //IE内核
                presto: u.indexOf(‘Presto‘) > -1, //opera内核
                webKit: u.indexOf(‘AppleWebKit‘) > -1, //苹果、谷歌内核
                gecko: u.indexOf(‘Gecko‘) > -1 && u.indexOf(‘KHTML‘) == -1, //火狐内核
                mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端
                ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
                android: u.indexOf(‘Android‘) > -1 || u.indexOf(‘Linux‘) > -1, //android终端或uc浏览器
                iPhone: u.indexOf(‘iPhone‘) > -1, //是否为iPhone或者QQHD浏览器
                iPad: u.indexOf(‘iPad‘) > -1, //是否iPad
                webApp: u.indexOf(‘Safari‘) == -1 //是否web应该程序,没有头部与底部
            };
        }(),
        language: (navigator.browserLanguage || navigator.language).toLowerCase()
    }

后端部分

其中code和state是微信服务器发起请求的时候会带过来。code有效期为5分钟,state为自定义的一个参数,具体可参考微信网页授权文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842

 public async Task<IActionResult> Index()
 {
            var code = Request.Query["code"];
            var state = Request.Query["state"];
            OAuthToken tokenModel = new OAuthToken();
            if (!string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(state))
            {
                tokenModel = await _dataServices.LoginWeChat(code, _config[ConfigurationKeys.PAY_APPID]);//调取接口           _logger.LogError($"微信登录:{tokenModel.Openid}");           code = string.Empty;
                if (tokenModel.errmsg.Contains("success"))
                {
                    var model = await _dataServices.GetUserByIdAccount(tokenModel.Openid);//具体可根据自己的项目业务来操作                    //TODO
                }
            }
        return View("~/Views/Home/Index.cshtml");
 }

上述代码中从第一个OAuthToken说起,它是一个自定义存放微信授权的实体类,内容如下

public class OAuthToken:BaseRes
    {
        /// <summary>
        /// 网页授权接口调用凭证。注意:此access_token与基础支持的access_token不同
        /// </summary>
        [JsonProperty("access_token")]
        public string AccessToken { get; set; }

        private int _expiresIn;

        /// <summary>
        /// access_token接口调用凭证超时时间,单位(秒)
        /// </summary>
        [JsonProperty("expires_in")]
        public int ExpiresIn
        {
            get { return _expiresIn; }
            set
            {
                ExpiresTime = DateTime.Now.AddSeconds(value);
                _expiresIn = value;
            }
        }
        /// <summary>
        /// 用于刷新access_token
        /// </summary>
        [JsonProperty("refresh_token")]
        public string RefreshToken { get; set; }

        /// <summary>
        /// 用户唯一标识。请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的openid
        /// </summary>
        [JsonProperty("openid")]
        public string Openid { get; set; }

        /// <summary>
        /// 用户授权的作用域,使用逗号(,)分隔
        /// </summary>
        [JsonProperty("scope")]
        public string Scope { get; set; }

        [JsonProperty("expires_time")]
        public DateTime ExpiresTime { get; set; }

        [JsonProperty("unionid")]
        public string Unionid { get; set; }
    }

其中BaseRes,是返回的错误实体类

public class BaseRes
{
   public BaseRes()
   {
     errmsg = "success";
   }
   public int errcode { get; set; }
   public string errmsg { get; set; }

}

第二个ConfigurationKeys.PAY_APPID是获取配置项

第三个LoginWeChat,我们来看看这个接口中是如何实现的

首先我们看到这个接口接收两个参数,和上面我们请求的参数与对应,一个是code,另一个是appId

        [Route("[controller]/LoginByWeChat")]
        [HttpGet]
        public async Task<OAuthTokenDto> LoginByWeChat(string code, string appid = "")
        {
            return await _authenticationService.LoginByWeChat(code, appid);
        }

请求微信登录:

/// <summary>
        /// 通过code换取网页授权access_token
        /// </summary>
        /// <param name="appid">公众号的唯一标识</param>
        /// <param name="code">填写第一步获取的code参数</param>
        /// <returns></returns>
        public  async Task<OAuthTokenModel> LoginByWeChat(string code, string appid = "")
        {
            var config = OpenApi.GetConfig(appid, PlatformType.Mp);
            var url =
                $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={config.AppId}&secret={config.AppSecret}&code={code}&grant_type=authorization_code";
            return await HttpUtil.GetResultAsync<OAuthTokenModel>(url);
        }
        /// <summary>
        /// 根据appid,获取对应的接口参数信息
        /// </summary>
        /// <param name="appid"></param>
        /// <returns></returns>
        public static ApiConfig GetConfig(string appid = "", PlatformType platform = PlatformType.Mp)
        {
            if (string.IsNullOrEmpty(appid) && apiConfigs?.Count > 0)
            {
                return apiConfigs.FirstOrDefault(a => a.Platform == platform);
            }
            return apiConfigs.FirstOrDefault(a => a.AppId == appid);
        }
    public class ApiConfig
    {
        public string AppId { get; set; }
        public string AppSecret { get; set; }
        public PlatformType Platform { get; set; } = PlatformType.Mp;
    }
    public enum PlatformType
    {
        /// <summary>
        /// 公众号
        /// </summary>
        Mp,
        /// <summary>
        /// 小程序
        /// </summary>
        Mini,
        /// <summary>
        /// 开放平台
        /// </summary>
        Open,
        /// <summary>
        /// 企业号
        /// </summary>
        Qy
    }
        /// <summary>
        /// 发起GET请求,并获取请求返回值
        /// </summary>
        /// <typeparam name="T">返回值类型</typeparam>
        /// <param name="url">接口地址</param>
        public static async Task<T> GetResultAsync<T>(string url)
        {
            var retdata = await HttpGetAsync(url);
            return JsonConvert.DeserializeObject<T>(retdata);
        }

这里我们调用了Get异步请求:

        public static async Task<string> HttpGetAsync(string url)
        {
            var request = CreateRequest(url, HttpMethod.GET);
            return await GetResponseStringAsync(request);
        }
        private static HttpWebRequest CreateRequest(string url, HttpMethod method, string postData = "", string certpath = "", string certpwd = "")
        {
            var request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = method.ToString();
            request.ContentType = "application/x-www-form-urlencoded";
            request.Accept = "*/*";
            request.Timeout = 15000;
            request.AllowAutoRedirect = false;
            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback((a, b, c, d) => true);
            if (!string.IsNullOrEmpty(certpath) && !string.IsNullOrEmpty(certpwd))
            {
                X509Certificate2 cer = new X509Certificate2(certpath, certpwd,
                    X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet);
                request.ClientCertificates.Add(cer);
            }
            if (method == HttpMethod.POST)
            {
                using (var sw = new StreamWriter(request.GetRequestStream()))
                {
                    sw.Write(postData);
                }
            }
            return request;
        }
       private static async Task<string> GetResponseStringAsync(HttpWebRequest request)
        {
            using (var response = await request.GetResponseAsync() as HttpWebResponse)
            {
                using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
                {
                    return reader.ReadToEnd();//获取响应
                }
            }
        }
    public enum HttpMethod
    {
        GET,
        POST
    }

这样,我们就可以拿到返回的响应结果了

 OAuthToken resultDto = JsonConvert.DeserializeObject<OAuthToken>(resultDetail);
  _logger.LogError($"OpenId:{resultDto.Openid},ErrorMsg:{resultDto.errmsg}");

 return Task.FromResult(resultDto);

我们查看日志,可以看到OpenId已经被打印出来了

这样,我们只需要将我们的Openid 再数据库中进行查找,就可以知道是否存在此用户,若不存在,则可以操作新增

//判断是否数据库有登录记录 ,若无则新增
                if (!string.IsNullOrEmpty(model.Openid))
                {
                    var result = await _authenticationDataServices.FindAccountById(model.Openid);
                    if (string.IsNullOrEmpty(result.LoginAccount))
                    {
                        LoginUserModel userData = new LoginUserModel
                        {
                            LoginAccount = model.Openid,
                            Password = string.Empty,
                            Type = (int) LoginType.Wexin,
                            UserId = 0,
                            IsValid = true
                        };
                        var res =await _authenticationDataServices.AddLoginUser(userData);
                        if (res <= 0) logger.Error(res);
                    }
                }

查找用户的实现

public async Task<LoginUserModel> FindAccountById(string account)
        {
            using (var conn = GetMySqlConnection())
            {
                if (conn.State == ConnectionState.Closed)
                {
                    await conn.OpenAsync();
                }

                try
                {
                    string sql =
                        @"select Loginaccount,Password,Type,Userid,Isvalid from centraldb.login  where [email protected];";

                    var user = conn.Query<LoginUserModel>(sql, new { recordId = account }, commandType: CommandType.Text).FirstOrDefault();
                    return user ?? new LoginUserModel();
                }
                catch (Exception e)
                {
                    throw;
                }

            }
        }

新增的实现

 public async Task<int> AddLoginUser(LoginUserModel loginUser)
        {
            using (var conn = GetMySqlConnection())
            {
                if (conn.State == ConnectionState.Closed)
                {
                    await conn.OpenAsync();
                }

                const string sql =
                    @"insert into centraldb.login(loginaccount, `password`, `type`, userid, isvalid)
                            values(@loginaccount, @password, @type, @userid, @isvalid);
                      select max(id) from centraldb.login;";

                try
                {
                    var userId = (await conn.QueryAsync<int>(sql, new
                    {
                        loginaccount = loginUser.LoginAccount, password = loginUser.Password,
                        type = loginUser.Type, userid = loginUser.UserId, isvalid = loginUser.IsValid
                    }, commandType: CommandType.Text)).FirstOrDefault();

                    return userId;
                }
                catch (Exception e)
                {
                    return -1;
                }
            }
        }

这样,运行项目之后,数据库中就会插入相应的数据:

原文地址:https://www.cnblogs.com/zhangxiaoyong/p/10847743.html

时间: 2024-10-30 20:51:10

.NET Core中 实现H5微信登录(静默授权方式)的相关文章

微信开发一获取用户授权(静默授权方式)

/// <summary> /// 获取用户授权返回json序列化为UserAuthorizeInfo /// </summary> /// <param name="code"></param> /// <returns></returns> public static UserAuthorizeInfo GetDicUserAuthorize(string code) { string url = "

捋一捋PHP第三方微信登录

PC端扫二维码登录先说步骤吧:    微信网站登录的文档在https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=41ab5f757248bbbdcc2aad1a6d52b49fdc19579e&lang=zh_CN 1.微信登录请求,其实你可以当成是个url跳转.https://open.

微信静默授权问题

微信中需要获取openid,为了安全起见,采用服务器端静默授权方式,最终过程是: 前端页面(获取openid) -> 指定服务器端地址 -> 服务器端跳转到微信授权页 -> 微信授权页跳回服务器端 -> 服务器端跳回前端页面 在这里会存在一个问题,即微信授权返回死循环问题,因为这边前端跳转全部都是使用replace跳转,所以不会在history上再增加1一个页面,后来以为是我们服务器的问题,然后撇开微信直接让服务器端返回一个假的openid发现是可以返回的,所以问题出在微信的授权问

web登录与授权

web开发已经流行了很多年,登录与授权也基本有一套通用的流程,下面是我自己常用的登录与授权方式,欢迎大家讨论与吐槽. 概念: 登录是过程,授权是结果.登录只是为了获得页面的访问权限 or 操作权限 or 数据权限 or api调用权限. 登录授权分为两个域:用户域和权限域. 用户域:用户信息,角色信息. 用户信息:登录帐号,登录密码,用户名,角色,机构,部门等. 角色信息:角色名,机构,部门等. 权限域:模块信息,权限信息,角色权限. 模块信息:应用中的一个功能块,可以是一个页面(api),也可

微信第三方登录与静默授权和非静默授权

微信的授权登录在日常应用中应用的非常广泛,最多就是第三方登录 微信登录分为两类:授权登录(非静默授权)与静默授权 授权登录:需要用户确认登录,这样可以通过用户的个人确认,获取用户全面的信息,无论是否关注相关微信公众号等都可以获取. 静默授权不需要用户确认,只需要用户访问某个网页,属于嵌套在普通网页里的授权形式,但是只能获取到用户的唯一标示openid,无法获取用户的个人信息,对于用户的简单认证还是很有用的.

ASP.NET Core集成微信登录

工具: Visual Studio 2015 update 3 Asp.Net Core 1.0 1 准备工作 申请微信公众平台接口测试帐号,申请网址:(http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login).申请接口测试号无需公众帐号,可以直接体验和测试公众平台所有高级接口. 1.1 配置接口信息 1.2 修改网页授权信息 点击“修改”后在弹出页面填入你的网站域名: 2  新建网站项目 2.1 选择ASP.NET Core

微信H5页面登录到支付的心得

这几天帮朋友做了一个微信H5页面,代码都是down下来的,只添加了微信登录和微信支付功能. 以前没这么搞过这方面的,不知道怎么入手,在同事的帮助下终于完成了,现在记录下来,避免以后继续踩坑. 1.首先你的公众号中必须支持微信网页授权的权限,创建了一个方法里面判断用户是否登录,没有的话那么就会去请求微信的接口获取用户的基本信息, 获取信息后存入到数据库,cookie和session中:下次进入的时候判断cookie和session中的数据是否和数据库的一致,一致的话就默认为登录状态.这样微信登录的

小程序-文章:微信第三方登录(静默授权和非静默授权)

ylbtech-小程序-文章:微信第三方登录(静默授权和非静默授权) 1.返回顶部 1. 用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 微信的授权登录在日常应用中应用的非常广泛,越来越多的平台支持用户使用微信进行授权第三方登录 使用微信授权登录有哪些优势/好处: 1.用户量足够大,基本所有用户都会有微信,登录起来比较方便快捷: 2.微信作为一个开放平台,为众多公众号/服务开放了许多服务接口,让公众号能够为自己的用户提供更加个性.更加优质

.net core 2.x - 微信、QQ 授权登录

上一篇是关于模拟请求配置,包括域名问题的解决,本篇就说下授权登录.嗯,比较闲.以前的fx 开发web的时候好像使用的 微信提供的js插件生成二维码,然后扫码登录,,,记不清了,好久不开发微信了. 1.准备工作. 1.1.单独解决ajax的跨域问题 首先考虑到web端(ajax)跨域的问题,所以我们首先要解决的就是在core中配置跨域的设置(案例比较多所以不多说只贴代码): //ConfigureServices中 services.AddCors(options => { options.Add