最近需要用abp vnext实现微信小程序的登录,查找了一些资料,参考了

逸只猫 大佬的 abp Vnext OpenIddect 扩展微信小程序授权登录 文章,通过扩展token的登陆接口ITokenExtensionGrant(实现:TokenExtensionGrant)来实现的。
但是可能是大佬的是6.0版本的, 在使用8.0时会报 “The Specified GrantType Is NotImplemented” 的错误,通过排查源码发现是因为没有将扩展类注入到容器中,底层在调用是出现异常。
底层的源码如下:
原因就是TokenController在执行代码时,不是直接从容器中获取开发者实现的TokenExtensionGrant,而是从AbpOpenIddictExtensionGrantsOptions.value中获取的,所以基于逸只猫大佬的代码,在HostModule的ConfigureServices方法中加入以下代码即可正确运行:

Configure<AbpOpenIddictExtensionGrantsOptions>(o =>
{
    o.Grants.Add(TokenExtensionGrant.ExtensionGrantName, new TokenExtensionGrant());
});


由于我是基于应用的开发,所以增加了一些初始化的内容,包括:种子初始化、新用户首次登陆自动绑定账号、调整配置方式等,以下则是详细方法:

1. 配置application种子:
          在DbMigrator工程的appsettings.json中增加wechat节点

"OpenIddict": {
  "Applications": {
    "Noji_App": {
      "ClientId": "Noji_App"
    },
    "Noji_Blazor": {
      "ClientId": "Noji_Blazor",
      "RootUrl": "https://localhost:44342"
    },
    "Noji_Swagger": {
      "ClientId": "Noji_Swagger",
      "RootUrl": "https://localhost:44325/"
    },
    "WeChat": {
      "ClientId": "WeChat",
      "ClientSecret": "1q2w3E*"
    }
  }
}

2. 配置种子生成文件(Domain工程下的/OpenIddict/OpenIddictDataSeedContributor):

           找到CreateApplicationsAsync方法,在方法的最后面增加初始化应用的代码

// WeChat MiniProgram
var weChatMiniProgramClientId =
    configurationSection["WeChat:ClientId"];
if (!weChatMiniProgramClientId.IsNullOrWhiteSpace())
{
    await CreateApplicationAsync(
        name: weChatMiniProgramClientId,
        type: OpenIddictConstants.ClientTypes.Confidential,
        consentType: OpenIddictConstants.ConsentTypes.Implicit,
        displayName: "WeChat Mini-program",
        secret: configurationSection["WeChat:ClientSecret"] ?? "1q2w3e*",
        grantTypes: new List<string>
        {
            OpenIddictConstants.GrantTypes.RefreshToken,"wechat_code"
        },
        scopes: commonScopes
    );
}

3. 运行DbMigrator工程,生成种子到数据库

4.创建微信api请求返回结果类:WeChatRequestResult

public class WeChatRequestResult
{
    /// <summary>
    /// 用户唯一标识
    /// </summary>
    public string? OpenId { get; set; }
    /// <summary>
    /// 会话密钥
    /// </summary>
    public string? Session_Key { get; set; }
    /// <summary>
    /// 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回
    /// </summary>
    public string? UnionId { get; set; }
    /// <summary>
    /// 错误码
    /// </summary>
    public int ErrCode { get; set; }
    /// <summary>
    /// 错误信息
    /// </summary>
    public string? ErrMsg { get; set; }
}

5. 在Host工程中创建核心类:TokenExtensionGrant,最好不要在别的工程中创建,引用包和后续扩展会比较麻烦

 public class TokenExtensionGrant : AbpOpenIdDictControllerBase, ITokenExtensionGrant
 {
     protected IOptions<IdentityOptions> IdentityOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<IdentityOptions>>();

     public const string ExtensionGrantName = "wechat_code";
     public string Name => ExtensionGrantName;

     public async Task<IActionResult> HandleAsync(ExtensionGrantContext context)
     {
         LazyServiceProvider = context.HttpContext.RequestServices.GetRequiredService<IAbpLazyServiceProvider>();

         string code = context.Request.GetParameter("code").ToString()!;
         if (string.IsNullOrWhiteSpace(code))
         {
             return NewForbidResult("code参数为空!");
         }
         var results = GetResults(context, code);

         switch (results.ErrCode)
         {
             case 0:
                 string providerKey = results.OpenId!;
                 var claimsPrincipal = await ServerValidate(context, "wechat", providerKey);
                 return claimsPrincipal == null ? NewForbidResult("登录失败") :
                     SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
             //可以自行根据微信返回错误代码进行相应的错误处理
             default:
                 return NewForbidResult(results.ErrMsg ?? "登录失败");
         }
     }

     private ForbidResult NewForbidResult(string msg)
     {
         return new ForbidResult(
             new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
             GetAuthenticationProperties(msg));
     }

     private AuthenticationProperties GetAuthenticationProperties(string msg)
     {
         return new AuthenticationProperties(new Dictionary<string, string>
         {
             [OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest,
             [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = msg
         }!);
     }
     /// <summary>
     /// 服务器验证
     /// </summary>
     /// <param name="context"></param>
     /// <param name="loginProvider"></param>
     /// <param name="providerKey"></param>
     /// <returns></returns>
     private async Task<ClaimsPrincipal> ServerValidate(ExtensionGrantContext context, string loginProvider, string providerKey)
     {
         await IdentityOptions.SetAsync();
         //如果忽略多租户可使用
         /*
             using (_dataFilter.Disable<IMultiTenant>())
             {
                     user = await UserManager.FindByLoginAsync(loginProvider,         providerKey);
               }
          */
         var user = await UserManager.FindByLoginAsync(loginProvider, providerKey);
         if (user == null)
         {
             // 用户不存在,创建新用户
             user = new Volo.Abp.Identity.IdentityUser(GuidGenerator.Create(), providerKey, $"{providerKey}@qq.com");
             // ... 可选: 设置用户密码
             // ... 可选: 设置用户Name
             // 创建用户
             var createUserResult = await UserManager.CreateAsync(user);

             if (createUserResult.Succeeded)
             {
                 // 关联外部登录信息
                 await UserManager.AddLoginAsync(user, new UserLoginInfo(loginProvider, providerKey, "wechat"));

                 // ... 可选:提示用户完善个人资料
                 // ... 可选:重定向到欢迎页面
             }
             else
             {
                 // 处理创建用户失败的情况
             }
         }

         var principal = await SignInManager.CreateUserPrincipalAsync(user);
         var scopes = context.Request.GetScopes();
         principal.SetScopes(scopes);
         var resources = await GetResourcesAsync(scopes);
         principal.SetResources(resources);
         await OpenIddictClaimsPrincipalManager.HandleAsync(context.Request, principal);
         return principal;
     }
     /// <summary>
     /// 根据code得到微信openid
     /// </summary>
     /// <param name="context"></param>
     /// <param name="code">小程序提供的code</param>
     /// <returns></returns>
     private WeChatRequestResult GetResults(ExtensionGrantContext context, string code)
     {
         string _appid = "wx2a88566666666"; //开发者平台申请的appId
         string _secret = "3d8f0abe112af3536666666666"; //开发者平台申请的secret

         string url = string.Format(
                     "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code",
                     _appid,
                     _secret,
                     code);
         HttpClient client = new HttpClient();
         HttpResponseMessage response = client.GetAsync(url).Result;
         if (response.IsSuccessStatusCode)
         {
             string resContent = response.Content.ReadAsStringAsync().Result;
             return JsonConvert.DeserializeObject<WeChatRequestResult>(resContent)!;
         }
         else
         {
             return new WeChatRequestResult { ErrMsg = "微信接口请求失败" };
         }
     }
 }

6.配置模块:HttpApiHostModule文件中找到 PreConfigureServices方法,增加代码

 PreConfigure<OpenIddictServerBuilder>(builder =>
 {
     builder.AllowCustomFlow(TokenExtensionGrant.ExtensionGrantName);
 });

7.配置注入:HttpApiHostModule文件中找到 ConfigureServices 方法,增加代码

 Configure<AbpOpenIddictExtensionGrantsOptions>(o =>
 {
     o.Grants.Add(TokenExtensionGrant.ExtensionGrantName, new TokenExtensionGrant());
 });

8.前端请求token

host:/connect/token
methon:POST
media type:aplication/x-www-form-urlencoded
body:
	grant_type=wechat_code
	code=微信小程序提供的code
	client_id=自己项目的client_id
	scope=自己项目 openid offline_access
    client_secret=1q2w3E*  (DbMigrator配置的ClientSecret)

9.完结

如有问题可以私信我,最后再次感谢 

逸只猫 大佬及大佬的 abp Vnext OpenIddect 扩展微信小程序授权登录 


       

更多推荐