13 changed files with 397 additions and 11 deletions
@ -0,0 +1,7 @@ |
|||||
|
namespace LINGYUN.Abp.Account; |
||||
|
public class ExternalLoginInfoDto |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string DisplayName { get; set; } |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Account; |
||||
|
public class ExternalLoginResultDto |
||||
|
{ |
||||
|
public List<UserLoginInfoDto> UserLogins { get; set; } = new List<UserLoginInfoDto>(); |
||||
|
public List<ExternalLoginInfoDto> ExternalLogins { get; set; } = new List<ExternalLoginInfoDto>(); |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Account; |
||||
|
public class RemoveExternalLoginInput |
||||
|
{ |
||||
|
[Required] |
||||
|
public string LoginProvider { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
public string ProviderKey { get; set; } |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace LINGYUN.Abp.Account; |
||||
|
public class UserLoginInfoDto |
||||
|
{ |
||||
|
public string LoginProvider { get; set; } |
||||
|
public string ProviderKey { get; set; } |
||||
|
public string ProviderDisplayName { get; set; } |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net9.0</TargetFramework> |
||||
|
<AssemblyName>LINGYUN.Abp.Account.WeChat.Work</AssemblyName> |
||||
|
<PackageId>LINGYUN.Abp.Account.WeChat.Work</PackageId> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\framework\wechat\LINGYUN.Abp.WeChat.Work\LINGYUN.Abp.WeChat.Work.csproj" /> |
||||
|
<ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.Domain\LINGYUN.Abp.Identity.Domain.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,27 @@ |
|||||
|
using LINGYUN.Abp.WeChat.Work; |
||||
|
using LINGYUN.Abp.WeChat.Work.Localization; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.Abp.AspNetCore.Mvc.Localization; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Account.WeChat.Work; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpWeChatWorkModule), |
||||
|
typeof(AbpAspNetCoreMvcModule))] |
||||
|
public class AbpAccountWeChatWorkModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.AddAssemblyResource(typeof(WeChatWorkResource), typeof(AbpAccountWeChatWorkModule).Assembly); |
||||
|
}); |
||||
|
|
||||
|
PreConfigure<IMvcBuilder>(mvcBuilder => |
||||
|
{ |
||||
|
mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpAccountWeChatWorkModule).Assembly); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.Users; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Account.WeChat.Work.Controllers; |
||||
|
|
||||
|
[Authorize] |
||||
|
public class WeChatWorkAccountController : AbpControllerBase |
||||
|
{ |
||||
|
private readonly IdentityUserManager identityUserManager; |
||||
|
/// <summary>
|
||||
|
/// 绑定用户
|
||||
|
/// </summary>
|
||||
|
/// <param name="code"></param>
|
||||
|
/// <returns></returns>
|
||||
|
public async virtual Task BindAsync(string code) |
||||
|
{ |
||||
|
var user = await identityUserManager.GetByIdAsync(CurrentUser.GetId()); |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,111 @@ |
|||||
|
using LINGYUN.Abp.Account.Web.ExternalProviders; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.WebUtilities; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Account; |
||||
|
using Volo.Abp.Account.Localization; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.Identity.AspNetCore; |
||||
|
using Volo.Abp.Users; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Account.Web.Areas.Account.Controllers; |
||||
|
|
||||
|
[Controller] |
||||
|
[Area(AccountRemoteServiceConsts.ModuleName)] |
||||
|
[Route($"api/{AccountRemoteServiceConsts.ModuleName}")] |
||||
|
[RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)] |
||||
|
public class AccountController : AbpController |
||||
|
{ |
||||
|
protected AbpSignInManager SignInManager => LazyServiceProvider.LazyGetRequiredService<AbpSignInManager>(); |
||||
|
protected IdentityUserManager UserManager => LazyServiceProvider.LazyGetRequiredService<IdentityUserManager>(); |
||||
|
protected IExternalProviderService ExternalProviderService => LazyServiceProvider.LazyGetRequiredService<IExternalProviderService>(); |
||||
|
|
||||
|
public AccountController() |
||||
|
{ |
||||
|
LocalizationResource = typeof(AccountResource); |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Authorize] |
||||
|
[Route("external-logins")] |
||||
|
public async virtual Task<ExternalLoginResultDto> GetExternalLoginsAsync() |
||||
|
{ |
||||
|
var currentUser = await UserManager.GetByIdAsync(CurrentUser.GetId()); |
||||
|
var userLogins = await UserManager.GetLoginsAsync(currentUser); |
||||
|
var externalProviders = await ExternalProviderService.GetAllAsync(); |
||||
|
|
||||
|
return new ExternalLoginResultDto |
||||
|
{ |
||||
|
UserLogins = userLogins.Select(x => new UserLoginInfoDto |
||||
|
{ |
||||
|
ProviderDisplayName = x.ProviderDisplayName, |
||||
|
ProviderKey = x.ProviderKey, |
||||
|
LoginProvider = x.LoginProvider, |
||||
|
}).ToList(), |
||||
|
ExternalLogins = externalProviders.Select(x => |
||||
|
{ |
||||
|
return new ExternalLoginInfoDto |
||||
|
{ |
||||
|
Name = x.Name, |
||||
|
DisplayName = x.DisplayName, |
||||
|
}; |
||||
|
}).ToList(), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
[HttpDelete] |
||||
|
[Authorize] |
||||
|
[Route("external-logins/remove")] |
||||
|
public async virtual Task RemoveExternalLoginAsync(RemoveExternalLoginInput input) |
||||
|
{ |
||||
|
var currentUser = await UserManager.GetByIdAsync(CurrentUser.GetId()); |
||||
|
var identityResult = await UserManager.RemoveLoginAsync( |
||||
|
currentUser, |
||||
|
input.LoginProvider, |
||||
|
input.ProviderKey); |
||||
|
|
||||
|
if (!identityResult.Succeeded) |
||||
|
{ |
||||
|
throw new UserFriendlyException("Operation failed: " + identityResult.Errors.Select(e => $"[{e.Code}] {e.Description}").JoinAsString(", ")); |
||||
|
} |
||||
|
// 解绑的是当前身份认证方案则退出登录
|
||||
|
var amr = CurrentUser.FindClaimValue(ClaimTypes.AuthenticationMethod); |
||||
|
if (!amr.IsNullOrWhiteSpace() && string.Equals(amr, input.LoginProvider, StringComparison.InvariantCultureIgnoreCase)) |
||||
|
{ |
||||
|
await SignInManager.SignOutAsync(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[HttpGet] |
||||
|
[Authorize] |
||||
|
[Route("external-logins/bind")] |
||||
|
public virtual async Task<IActionResult> ExternalLoginBindAsync(string provider, string returnUrl) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(provider) || string.IsNullOrWhiteSpace(returnUrl)) |
||||
|
{ |
||||
|
Logger.LogWarning("The parameter is incorrect"); |
||||
|
return Redirect(QueryHelpers.AddQueryString(returnUrl, new Dictionary<string, string>() |
||||
|
{ |
||||
|
["error"] = "The parameter is incorrect" |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
var tenantId = CurrentTenant.Id; |
||||
|
var userId = CurrentUser.GetId(); |
||||
|
|
||||
|
var redirectUrl = Url.Page("/Account/ExternalLoginBind", pageHandler: "BindCallback", values: new { returnUrl, userId, tenantId }); |
||||
|
|
||||
|
var properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, userId.ToString()); |
||||
|
properties.Items["scheme"] = provider; |
||||
|
|
||||
|
return await Task.FromResult(Challenge(properties, provider)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
@page |
||||
|
@model LINGYUN.Abp.Account.Web.Pages.Account.ExternalLoginBindModel |
||||
|
@{ |
||||
|
} |
||||
@ -0,0 +1,117 @@ |
|||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.WebUtilities; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.Identity.AspNetCore; |
||||
|
using Volo.Abp.Uow; |
||||
|
using Volo.Abp.Users; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Account.Web.Pages.Account; |
||||
|
|
||||
|
[IgnoreAntiforgeryToken] |
||||
|
public class ExternalLoginBindModel : AbpPageModel |
||||
|
{ |
||||
|
protected AbpSignInManager SignInManager => LazyServiceProvider.LazyGetRequiredService<AbpSignInManager>(); |
||||
|
|
||||
|
protected IdentityUserManager UserManager => LazyServiceProvider.LazyGetRequiredService<IdentityUserManager>(); |
||||
|
|
||||
|
protected IOptions<IdentityOptions> IdentityOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<IdentityOptions>>(); |
||||
|
|
||||
|
public virtual async Task<IActionResult> OnGetAsync() |
||||
|
{ |
||||
|
return await Task.FromResult(NotFound()); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SPA绑定第三方绑定
|
||||
|
/// </summary>
|
||||
|
/// <param name="provider">第三方提供者</param>
|
||||
|
/// <param name="returnUrl">绑定成功回调地址</param>
|
||||
|
/// <returns>IActionResult</returns>
|
||||
|
public virtual async Task<IActionResult> OnPostAsync(string provider, string returnUrl) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(provider) || string.IsNullOrWhiteSpace(returnUrl)) |
||||
|
{ |
||||
|
Logger.LogWarning("The parameter is incorrect"); |
||||
|
return Redirect(QueryHelpers.AddQueryString(returnUrl, new Dictionary<string, string>() |
||||
|
{ |
||||
|
["error"] = "The parameter is incorrect" |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
var tenantId = CurrentTenant.Id; |
||||
|
Logger.LogInformation("CurrentTenant:{TenantId}", tenantId); |
||||
|
var userId = CurrentUser.GetId(); |
||||
|
Logger.LogInformation("CurrentUser:{UserId}", userId); |
||||
|
|
||||
|
var redirectUrl = Url.Page("ExternalLoginBind", pageHandler: "BindCallback", values: new { returnUrl, userId, tenantId }); |
||||
|
|
||||
|
var properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, userId.ToString()); |
||||
|
properties.Items["scheme"] = provider; |
||||
|
|
||||
|
return await Task.FromResult(Challenge(properties, provider)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SPA绑定第三方登录回调
|
||||
|
/// </summary>
|
||||
|
/// <param name="returnUrl">绑定成功回调地址</param>
|
||||
|
/// <param name="userId">用户Id</param>
|
||||
|
/// <param name="tenantId">租户Id</param>
|
||||
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
|
[UnitOfWork] |
||||
|
public virtual async Task<IActionResult> OnGetBindCallbackAsync(string returnUrl, string userId, Guid? tenantId) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(returnUrl)) |
||||
|
{ |
||||
|
Logger.LogWarning("The returnUrl cannot be empty"); |
||||
|
return Redirect(QueryHelpers.AddQueryString(returnUrl, new Dictionary<string, string>() |
||||
|
{ |
||||
|
["error"] = "The returnUrl cannot be empty" |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
using (CurrentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
Logger.LogInformation("CurrentTenant:{TenantId}", tenantId); |
||||
|
|
||||
|
await IdentityOptions.SetAsync(); |
||||
|
|
||||
|
var loginInfo = await SignInManager.GetExternalLoginInfoAsync(userId); |
||||
|
if (loginInfo == null) |
||||
|
{ |
||||
|
Logger.LogWarning("External login info is not available"); |
||||
|
return Redirect(QueryHelpers.AddQueryString(returnUrl, new Dictionary<string, string>() |
||||
|
{ |
||||
|
["error"] = "External login info is not available." |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
await SignInManager.SignOutAsync(); |
||||
|
|
||||
|
if (await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey) == null) |
||||
|
{ |
||||
|
var externalUser = await UserManager.FindByIdAsync(userId); |
||||
|
CheckIdentityErrors(await UserManager.AddLoginAsync(externalUser, loginInfo)); |
||||
|
} |
||||
|
|
||||
|
return Redirect(returnUrl); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual void CheckIdentityErrors(IdentityResult identityResult) |
||||
|
{ |
||||
|
if (!identityResult.Succeeded) |
||||
|
{ |
||||
|
throw new UserFriendlyException("Operation failed: " + identityResult.Errors.Select(e => $"[{e.Code}] {e.Description}").JoinAsString(", ")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue