abp vnext 8.0 微信小程序 第三方登录 connect/token的扩展实现 OpenIddect
最近需要用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 扩展微信小程序授权登录

更多推荐



所有评论(0)