ABP Zero集成微信小程序登录
首先是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就可以了