ABP Vnext OpenIddect 扩展授权登录&扩展Grant Type

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值