.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 loginaccount=@recordId;";

                    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;
                }
            }
        }

這樣,運行項目之后,數據庫中就會插入相應的數據:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM