abp vnext6.0之后官方替换了原来的ids4,采用了openIddict的oauth认证框架。使用之前的方法已经不行,以下是OpenIddect 使用ITokenExtensionGrant接口进行的授权登入扩展,按照以下代码可实现包括钉钉、微信第三方授权登录;
*以下代码以钉钉authcode为例
用于接收返回数据字段类的接口
此代码建议放在 .Domain.Shared 层
using System.Text.Json.Serialization;
[Serializable]
namespace Dome.ExtensionApp
{
[Serializable]
public abstract class AbstractResults : IResults
{
/// <summary>
/// 错误码, 0代表成功,其它代表失败。
/// </summary>
[JsonPropertyName("errcode")]
public long Errcode { get; set; }
/// <summary>
/// 错误信息。
/// </summary>
[JsonPropertyName("errmsg")]
public string Errmsg { get; set; }
}
}
新建 AbstractTokenExtensionGrant.cs
此代码建议放在.AuthServer(分层架构)或.httpApi.Host(单层架构)
用于方便扩展多个Grant Type或者多个第三方授权登入
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.OpenIddict;
using Volo.Abp.OpenIddict.Controllers;
using Volo.Abp.OpenIddict.ExtensionGrantTypes;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
using AECEIP.Utils.ExtensionApp;
using AECEIP.Utils;
using System;
using AECEIP.Settings;
using Volo.Abp.SettingManagement;
namespace Dome.ExtensionGrant
{
public abstract class AbstractTokenExtensionGrant : AbpOpenIdDictControllerBase, ITokenExtensionGrant
{
protected ExtensionGrantContext GrantContext { get; set; }
protected string ProviderKey { get; set; }
protected IOptions<IdentityOptions> IdentityOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<IdentityOptions>>();
protected IdentitySecurityLogManager IdentitySecurityLogManager => LazyServiceProvider.LazyGetRequiredService<IdentitySecurityLogManager>();
public abstract string Name { get; }
/// <summary>
/// 服务器验证
/// </summary>
/// <returns></returns>
protected virtual async Task<ClaimsPrincipal> ServerValidate()
{
var user = await UserManager.FindByLoginAsync(Name, ProviderKey);
if (user == null)
{
return null;
}
var principal = await SignInManager.CreateUserPrincipalAsync(user);
var scopes = GrantContext.Request.GetScopes();
principal.SetScopes(scopes);
var resources = await GetResourcesAsync(scopes);
principal.SetResources(resources);
await GrantContext
.HttpContext
.RequestServices
.GetRequiredService<AbpOpenIddictClaimsPrincipalManager>()
.HandleAsync(GrantContext.Request, principal)
return principal;
}
/// <summary>
/// 根据code得到用户信息
/// </summary>
/// <returns></returns>
protected abstract Task<IResults> GetResultsAsync();
public virtual async Task<IActionResult> HandleAsync(ExtensionGrantContext context)
{
GrantContext = context;
LazyServiceProvider = context.HttpContext.RequestServices.GetRequiredService<IAbpLazyServiceProvider>();
var results = await GetResultsAsync();
if (results.Errcode!=0)
{
string msg = "errcode:" + results.Errcode + ";msg:" + results.Errmsg;
return await NewForbidResult(msg);
}
//ProviderKey = results.UserId;
await IdentityOptions.SetAsync();
return await SwitchtVoidAsync(results);
}
protected virtual async Task<IActionResult> SwitchtVoidAsync(IResults results)
{
switch (results.Errcode)
{
case 0:
var claimsPrincipal = await ServerValidate();
if (claimsPrincipal == null)
{
return await NewForbidResult("未绑定", ProviderKey);
}
else
{
return SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
default:
return await NewForbidResult(results.Errmsg);
}
}
protected async Task<ForbidResult> NewForbidResult(string msg, string userCode = null)
{
return new ForbidResult(
new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
await GetAuthProperties(msg, userCode));
}
private async Task<AuthenticationProperties> GetAuthProperties(string msg, string userCode)
{
if (userCode == null)
{
return new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = msg
});
}
else
{
var dictionary = new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.LoginRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = msg
};
if (userCode != null)
{
///加密获得的UserId或OpenId等,用于将前段用户登入后进行绑定
Encrypter encrypter = new Encrypter();
var ivBytes = encrypter.GenerateRandomHexText(16);
string secureKey = "sKey123456";
var eCode = encrypter.AES_Encrypt(userCode, secureKey, ivBytes);
string iv = Convert.ToBase64String(ivBytes).Replace('/','_');
string url = "/user/login?redirect=%252Fworkplace?m={0}%2526code={1}.{2}";
dictionary.Add(
OpenIddictServerAspNetCoreConstants.Properties.ErrorUri,
string.Format(url, Name, eCode, iv));
}
return new AuthenticationProperties(dictionary);
}
}
}
}
新建DingTalkTokenExtensionGrant.cs
扩展钉钉第三方登入、钉钉Grant Type
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
using AECEIP.Utils.ExtensionApp.DingTalk;
using AECEIP.Utils.ExtensionApp;
namespace Dome.ExtensionGrant.DingTalk
{
[IgnoreAntiforgeryToken]
[ApiExplorerSettings(IgnoreApi = true)]
public class DingTalkTokenExtensionGrant : AbstractTokenExtensionGrant
{
public const string ExtensionGrantName = "dingtalk_code";
public override string Name => ExtensionGrantName;
protected override async Task<IResults> GetResultsAsync()
{
string code = GrantContext.Request.GetParameter("code").ToString();
if (string.IsNullOrEmpty(code))
{
return new Results { Errcode = 400, Errmsg = "code参数为空!" };
}
try
{
DingTalkExtensionService extService = new DingTalkExtensionService(SettingManager);
IResults _result;
//二维码免登授权(企业内部)
var prResult = await extService.GetContactUsersAsync(code);
_result = prResult;
ProviderKey = prResult.Errcode == 0 ? prResult.Result.UnionId : "";
return _result;
}catch(Exception ex)
{
return new Results { Errcode = 500, Errmsg = "接口请求失败." + ex.Message };
}
}
}
}
此代码片段可以按照需求放在 .Domain层或者.Authserver等
/// <summary>
/// 获取第三方登入信息
/// </summary>
/// <param name="authCode">临时授权码authCode</param>
/// <returns></returns>
public async Task<GetContactUsersResult> GetContactUsersAsync(string authCode)
{
//获取用户token
var token = await PostUserAccessTokenAsync(authCode);
//根据token获取钉钉通讯录信息
return GetContactUsersAsync(token.Result.AccessToken, "me");
}
public class GetContactUsersResult:IResults
{
public AlibabaCloud.SDK.Dingtalkcontact_1_0.Models.GetUserResponseBody Result { get; set; }
}
在HttpApiHostModule.cs 文件中添加注册
使用上面定义的ExtensionGrantName扩展的这个openiddict的认证流程的名字
public override void PreConfigureServices(ServiceConfigurationContext context)
{
///······
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.Configure(options =>
{
options.GrantTypes.Add(DingTalkTokenExtensionGrant.ExtensionGrantName);
});
});
}
请求地址和参数
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