首先是ABPZero的第三方登錄模塊,通過調用第三方的登錄接口返回用戶信息,再交給ABP的登錄驗證模塊去執行對應的登錄注冊。
涉及的數據庫表主要是
這兩個表,AbpUsers存儲了用戶信息,AbpUserLogins存儲了登錄方式,第三方登錄的信息就是存儲在這里的

主要是四個字段 LoginProvider ProviderKey TenantId UserId
登錄提供器 用戶唯一Id 對應的租戶Id和用戶Id
首先需要編寫一個LoginProvider,代碼如下
using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using Abp.AspNetZeroCore.Web.Authentication.External; using Castle.Core.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Schema; namespace Web.Authentication.External { public class WechatMiniProgramAuthProviderApi : ExternalAuthProviderApiBase { /// <summary> /// 微信小程序 /// </summary> public const string ProviderName = "WeChatMiniProgram"; WeChatMiniProgramOptions _options; JSchema schema = JSchema.Parse(JsonConvert.SerializeObject(new WeChatSession())); JSchema accessSchema = JSchema.Parse(JsonConvert.SerializeObject(new { AccessCode="", Name="" })); const string url = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&grant_type=authorization_code&js_code={2}"; private readonly IExternalAuthConfiguration _externalAuthConfiguration; private readonly ILogger logger; public WechatMiniProgramAuthProviderApi(IExternalAuthConfiguration externalAuthConfiguration, ILogger logger) { _externalAuthConfiguration = externalAuthConfiguration; var r = externalAuthConfiguration.Providers.First(p => p.Name == ProviderName); _options = new WeChatMiniProgramOptions { AppId = r.ClientId, Secret = r.ClientSecret }; this.logger = logger; } public async override Task<ExternalAuthUserInfo> GetUserInfo(string accessCode)//因為需要獲取微信放進User.Name { //所以accessCode需要多一個Name JObject jObject = JObject.Parse(accessCode); //就長這樣 {"AccessCode":"xxxxxxxx", "Name":"Sam"} if (!jObject.IsValid(accessSchema)) //所以用Jobect解析出來 { throw new Abp.UI.UserFriendlyException("accessCode Json inVaild"); } accessCode = jObject["AccessCode"].ToString(); string name = jObject["Name"].ToString(); //string rowData = jObject["RowData"].ToString(); var result = await GetOpenId(accessCode); //獲取到OpenId,說明accessCode是對的 實際上應該再通過OpenId解密數據后獲取NickName的,偷懶了... //logger.Info("OpenId:" + result.Openid); //獲取不到則在方法內部拋出異常,不會返回用戶信息,也就不會執行之后的登陸注冊操作 var t = result == null ? new ExternalAuthUserInfo() : new ExternalAuthUserInfo// { EmailAddress = result.Openid + "@test.cn", Surname = name, ProviderKey = result.Openid,//唯一 Provider = ProviderName, Name = name }; return t; } private async Task<WeChatSession> GetOpenId(string code) { string geturl = string.Format(url, _options.AppId, _options.Secret, code); HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var result = await client.GetAsync(geturl); if (result.IsSuccessStatusCode) { //{"errcode":40163,"errmsg":"code been used, hints: [ req_id: tWZH6a0160th19 ]"} string re = await result.Content.ReadAsStringAsync();//{"session_key":"eafmjK9FYzCVpqPSo\/FBsQ==","openid":"oUigJ47QGkNOOXUjHkii5LyJbukw"} var jo = JObject.Parse(re); if (jo.IsValid(schema)) { var m = JsonConvert.DeserializeObject<WeChatSession>(re); return m; } } return null ; } } class WeChatSession { public string Openid { get; set; } public string Session_key { get; set; } }
/// <summary>
/// 微信小程序配置選項
/// </summary>
public class WeChatMiniProgramOptions
{
/// <summary>
/// AppId
/// </summary>
public string AppId { get; set; }
/// <summary>
/// 密鑰
/// </summary>
public string Secret { get; set; }
} }
然后在appsettings.json的Authentication節點中配置微信小程序的開啟和appid 密鑰的配置

然后在WebHostModule.cs中判斷是否開啟,執行配置(如果是MVC項目則是 項目名+WebMVCModule.cs)

因為默認的ProviderKey要求同一個登陸器下的同一用唯一,但是微信小程序里只有OpenId能做到用戶唯一,OpenId又不能放到網絡里傳輸,因此就需要修改一下默認的方式
注釋掉 WebCore項目中Controller中TokenAuthController的GetExternalUserInfo方法中的判斷調用接口傳入ProviderKey和提供器返回用戶信息ProviderKey一致。
這樣Login表中的ProviderKey就會存儲第一次登錄時傳入的accessCode。
最后只需要在LoginAsync方法傳入的Login對象時傳入獲取到的用戶模型的ProviderKey就能通過驗證了,為了 不破壞原有的登錄接口,我重寫了一個WeChatAuthenticate方法。(這里的和上面的GetExternalUserInfo方法都是在WebCore項目Controller下的TokenAuthController.cs文件里)
注意 標紅的代碼,這里要傳入的是從GetUserInfo獲取到的ProviderKey,而不是從model里獲取的ProviderKey,否則由於傳入的和數據庫里存儲的不匹配導致登錄失敗而認為是新的賬戶,執行注冊,最后發現用戶名沖突而注冊失敗。
1 [HttpPost] 2 public async Task<ExternalAuthenticateResultModel> WeChatAuthenticate([FromBody] ExternalAuthenticateModel model) 3 { 4 var externalUser = await GetExternalUserInfo(model); 5 //Logger.Info($"用戶模型:{Newtonsoft.Json.JsonConvert.SerializeObject(externalUser)}"); 6 //Logger.Debug(Newtonsoft.Json.JsonConvert.SerializeObject(new UserLoginInfo(model.AuthProvider, externalUser.ProviderKey, model.AuthProvider) ) + GetTenancyNameOrNull()); 7 var loginResult = await _logInManager.LoginAsync(new UserLoginInfo(model.AuthProvider, externalUser.ProviderKey, model.AuthProvider), GetTenancyNameOrNull()); 8 //Logger.Debug(loginResult.Result.ToString()); 9 switch (loginResult.Result) 10 { 11 case AbpLoginResultType.Success: 12 { 13 var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity)); 14 15 var returnUrl = model.ReturnUrl; 16 17 if (model.SingleSignIn.HasValue && model.SingleSignIn.Value && loginResult.Result == AbpLoginResultType.Success) 18 { 19 loginResult.User.SetSignInToken(); 20 returnUrl = AddSingleSignInParametersToReturnUrl(model.ReturnUrl, loginResult.User.SignInToken, loginResult.User.Id, loginResult.User.TenantId); 21 } 22 return new ExternalAuthenticateResultModel 23 { 24 AccessToken = accessToken, 25 EncryptedAccessToken = GetEncrpyedAccessToken(accessToken), 26 ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds, 27 ReturnUrl = returnUrl 28 }; 29 } 30 case AbpLoginResultType.UnknownExternalLogin: 31 { 32 var newUser = await RegisterExternalUserAsync(externalUser); 33 if (!newUser.IsActive) 34 { 35 return new ExternalAuthenticateResultModel 36 { 37 WaitingForActivation = true 38 }; 39 } 40 41 //Try to login again with newly registered user! 42 loginResult = await _logInManager.LoginAsync(new UserLoginInfo(model.AuthProvider, externalUser.ProviderKey, model.AuthProvider), GetTenancyNameOrNull()); 43 if (loginResult.Result != AbpLoginResultType.Success) 44 { 45 throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt( 46 loginResult.Result, 47 model.ProviderKey, 48 GetTenancyNameOrNull() 49 ); 50 } 51 52 var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity)); 53 return new ExternalAuthenticateResultModel 54 { 55 AccessToken = accessToken, 56 EncryptedAccessToken = GetEncrpyedAccessToken(accessToken), 57 ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds 58 }; 59 } 60 default: 61 { 62 throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt( 63 loginResult.Result, 64 model.ProviderKey, 65 GetTenancyNameOrNull() 66 ); 67 } 68 } 69 }
最后只需要調用WeChatExternalAuthenticate接口就可以了

需要注意的是這幾個參數需要全部傳入,哪怕傳入為空。providerKey和ProviderAccessCode都傳入微信小程序提供的accessCode。

這樣就會返回accessToken了,調用接口時Hearder加上Bearer accessToken就可以了
