假設如今有一個H5須要有微信登陸、手機號登陸、郵箱登陸 三種登陸方式。讓咱們一塊兒來看看微信登陸如何實現吧html
界面:前端
最終實現的效果圖(登陸成功後返回我的頁):android
由於微信登陸目前沒有實現移動端的其餘瀏覽器受權登陸,因此,再用除微信之外的瀏覽器操做登陸時,咱們須要給出用戶提醒,好比這樣:ios
登陸服務號或訂閱號的微信公衆號後臺,找到AppId以及AppSecret。後面會用到web
在公衆號設置中,設置安全域名、js域名以及網頁受權域名sql
其中再網頁受權域名設置的時候須要注意,將騰訊提供的一個惟一標識文件存放於項目根目錄下數據庫
、小程序
新建一張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處理判斷是不是微信內點擊便可:api
<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; } } }
這樣,運行項目以後,數據庫中就會插入相應的數據: