Browse Source

Merge pull request #1129 from colinin/scan-code-login-openiddict

feat(openiddict): 增加扫码登录实现
pull/1149/head
yx lin 1 year ago
committed by GitHub
parent
commit
dd1c8ccdaa
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 21
      aspnet-core/LINGYUN.MicroService.SingleProject.sln
  2. 2
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs
  3. 6
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Areas/Account/Controllers/Models/GenerateQrCodeResult.cs
  4. 9
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Areas/Account/Controllers/Models/QrCodeInfoResult.cs
  5. 8
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Areas/Account/Controllers/Models/QrCodeUserInfoResult.cs
  6. 104
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Areas/Account/Controllers/QrCodeLoginController.cs
  7. 1
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj
  8. 3
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore.QrCode/FodyWeavers.xml
  9. 30
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore.QrCode/FodyWeavers.xsd
  10. 24
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore.QrCode/LINGYUN.Abp.Identity.AspNetCore.QrCode.csproj
  11. 20
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore.QrCode/LINGYUN/Abp/Identity/AspNetCore/QrCode/AbpIdentityAspNetCoreQrCodeModule.cs
  12. 19
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore.QrCode/LINGYUN/Abp/Identity/AspNetCore/QrCode/QrCodeUserTokenProvider.cs
  13. 3
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/FodyWeavers.xml
  14. 30
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/FodyWeavers.xsd
  15. 25
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN.Abp.Identity.QrCode.csproj
  16. 26
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/AbpIdentityQrCodeModule.cs
  17. 16
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/IQrCodeLoginProvider.cs
  18. 8
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/Localization/Resources/en.json
  19. 8
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/Localization/Resources/zh-Hans.json
  20. 39
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeCacheItem.cs
  21. 29
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeInfo.cs
  22. 143
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeLoginProvider.cs
  23. 10
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeLoginProviderConsts.cs
  24. 22
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeScanParams.cs
  25. 9
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeStatus.cs
  26. 9
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.AspNetCore/LINGYUN/Abp/OpenIddict/AspNetCore/Controllers/UserInfoController.cs
  27. 3
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/FodyWeavers.xml
  28. 30
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/FodyWeavers.xsd
  29. 24
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/LINGYUN.Abp.OpenIddict.QrCode.csproj
  30. 31
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/LINGYUN/Abp/OpenIddict/QrCode/AbpOpenIddictQrCodeModule.cs
  31. 204
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/LINGYUN/Abp/OpenIddict/QrCode/QrCodeTokenExtensionGrant.cs
  32. 11
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/Microsoft/Extensions/DependencyInjection/QrCodeOpenIddictServerBuilderExtensions.cs
  33. 96
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/README.md
  34. 1
      aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj
  35. 2
      aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs
  36. 3
      aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs

21
aspnet-core/LINGYUN.MicroService.SingleProject.sln

@ -656,6 +656,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Gdpr.HttpApi.Cl
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Gdpr.Web", "modules\gdpr\LINGYUN.Abp.Gdpr.Web\LINGYUN.Abp.Gdpr.Web.csproj", "{00868838-6891-B034-AAB7-AADAA3ECDCB3}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Gdpr.Web", "modules\gdpr\LINGYUN.Abp.Gdpr.Web\LINGYUN.Abp.Gdpr.Web.csproj", "{00868838-6891-B034-AAB7-AADAA3ECDCB3}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Identity.QrCode", "modules\identity\LINGYUN.Abp.Identity.QrCode\LINGYUN.Abp.Identity.QrCode.csproj", "{E40CB92D-6C03-4A61-9A47-057CA504A46B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.OpenIddict.QrCode", "modules\openIddict\LINGYUN.Abp.OpenIddict.QrCode\LINGYUN.Abp.OpenIddict.QrCode.csproj", "{4C7594CE-FB2F-4309-B95C-D4460892CF6E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Identity.AspNetCore.QrCode", "modules\identity\LINGYUN.Abp.Identity.AspNetCore.QrCode\LINGYUN.Abp.Identity.AspNetCore.QrCode.csproj", "{C9266D5D-3860-09C3-F566-489BBB57A534}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -1738,6 +1744,18 @@ Global
{00868838-6891-B034-AAB7-AADAA3ECDCB3}.Debug|Any CPU.Build.0 = Debug|Any CPU {00868838-6891-B034-AAB7-AADAA3ECDCB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{00868838-6891-B034-AAB7-AADAA3ECDCB3}.Release|Any CPU.ActiveCfg = Release|Any CPU {00868838-6891-B034-AAB7-AADAA3ECDCB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{00868838-6891-B034-AAB7-AADAA3ECDCB3}.Release|Any CPU.Build.0 = Release|Any CPU {00868838-6891-B034-AAB7-AADAA3ECDCB3}.Release|Any CPU.Build.0 = Release|Any CPU
{E40CB92D-6C03-4A61-9A47-057CA504A46B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E40CB92D-6C03-4A61-9A47-057CA504A46B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E40CB92D-6C03-4A61-9A47-057CA504A46B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E40CB92D-6C03-4A61-9A47-057CA504A46B}.Release|Any CPU.Build.0 = Release|Any CPU
{4C7594CE-FB2F-4309-B95C-D4460892CF6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C7594CE-FB2F-4309-B95C-D4460892CF6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C7594CE-FB2F-4309-B95C-D4460892CF6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C7594CE-FB2F-4309-B95C-D4460892CF6E}.Release|Any CPU.Build.0 = Release|Any CPU
{C9266D5D-3860-09C3-F566-489BBB57A534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9266D5D-3860-09C3-F566-489BBB57A534}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9266D5D-3860-09C3-F566-489BBB57A534}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9266D5D-3860-09C3-F566-489BBB57A534}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -2057,6 +2075,9 @@ Global
{B080B644-A835-42EE-EBF5-C47B6D075588} = {4F837B81-EA7D-472A-8482-3D5A730DF810} {B080B644-A835-42EE-EBF5-C47B6D075588} = {4F837B81-EA7D-472A-8482-3D5A730DF810}
{170F4C72-BED2-C77A-64B0-F4CB9A232026} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {170F4C72-BED2-C77A-64B0-F4CB9A232026} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{00868838-6891-B034-AAB7-AADAA3ECDCB3} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {00868838-6891-B034-AAB7-AADAA3ECDCB3} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{E40CB92D-6C03-4A61-9A47-057CA504A46B} = {D94D6AFE-20BD-4F21-8708-03F5E34F49FC}
{4C7594CE-FB2F-4309-B95C-D4460892CF6E} = {7C714185-D3D9-4D94-B5CB-D857A0091F04}
{C9266D5D-3860-09C3-F566-489BBB57A534} = {D94D6AFE-20BD-4F21-8708-03F5E34F49FC}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {711A43C0-A2F8-4E5C-9B9F-F2551E4B3FF1} SolutionGuid = {711A43C0-A2F8-4E5C-9B9F-F2551E4B3FF1}

2
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs

@ -1,6 +1,7 @@
using LINGYUN.Abp.Account.Emailing; using LINGYUN.Abp.Account.Emailing;
using LINGYUN.Abp.Account.Web.ProfileManagement; using LINGYUN.Abp.Account.Web.ProfileManagement;
using LINGYUN.Abp.Identity; using LINGYUN.Abp.Identity;
using LINGYUN.Abp.Identity.AspNetCore.QrCode;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Account.Localization; using Volo.Abp.Account.Localization;
using Volo.Abp.Account.Web.Pages.Account; using Volo.Abp.Account.Web.Pages.Account;
@ -21,6 +22,7 @@ namespace LINGYUN.Abp.Account.Web;
typeof(VoloAbpAccountWebModule), typeof(VoloAbpAccountWebModule),
typeof(AbpIdentityDomainModule), typeof(AbpIdentityDomainModule),
typeof(AbpAccountEmailingModule), typeof(AbpAccountEmailingModule),
typeof(AbpIdentityAspNetCoreQrCodeModule),
typeof(AbpAccountApplicationContractsModule))] typeof(AbpAccountApplicationContractsModule))]
public class AbpAccountWebModule : AbpModule public class AbpAccountWebModule : AbpModule
{ {

6
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Areas/Account/Controllers/Models/GenerateQrCodeResult.cs

@ -0,0 +1,6 @@
namespace LINGYUN.Abp.Account.Web.Areas.Account.Controllers.Models;
public class GenerateQrCodeResult
{
public string Key { get; set; }
}

9
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Areas/Account/Controllers/Models/QrCodeInfoResult.cs

@ -0,0 +1,9 @@
using LINGYUN.Abp.Identity.QrCode;
namespace LINGYUN.Abp.Account.Web.Areas.Account.Controllers.Models;
public class QrCodeInfoResult
{
public string Key { get; set; }
public QrCodeStatus Status { get; set; }
}

8
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Areas/Account/Controllers/Models/QrCodeUserInfoResult.cs

@ -0,0 +1,8 @@
namespace LINGYUN.Abp.Account.Web.Areas.Account.Controllers.Models;
public class QrCodeUserInfoResult : QrCodeInfoResult
{
public string UserId { get; set; }
public string UserName { get; set; }
public string Picture { get; set; }
}

104
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Areas/Account/Controllers/QrCodeLoginController.cs

@ -0,0 +1,104 @@
using Asp.Versioning;
using LINGYUN.Abp.Account.Web.Areas.Account.Controllers.Models;
using LINGYUN.Abp.Identity.QrCode;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Identity;
using Volo.Abp.Security.Claims;
using Volo.Abp.Users;
namespace LINGYUN.Abp.Account.Web.Areas.Account.Controllers;
[Controller]
[ControllerName("QrCodeLogin")]
[Area(AccountRemoteServiceConsts.ModuleName)]
[Route($"api/{AccountRemoteServiceConsts.ModuleName}/qrcode")]
[RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)]
public class QrCodeLoginController : AbpControllerBase
{
private readonly IdentityUserManager _userManager;
private readonly IQrCodeLoginProvider _qrCodeLoginProvider;
public QrCodeLoginController(
IdentityUserManager userManager,
IQrCodeLoginProvider qrCodeLoginProvider)
{
_userManager = userManager;
_qrCodeLoginProvider = qrCodeLoginProvider;
}
[HttpPost]
[Route("generate")]
[AllowAnonymous]
public async Task<GenerateQrCodeResult> GenerateAsync()
{
var qrCodeInfo = await _qrCodeLoginProvider.GenerateAsync();
return new GenerateQrCodeResult
{
Key = qrCodeInfo.Key,
};
}
[HttpGet]
[Route("{key}/check")]
[AllowAnonymous]
public async Task<QrCodeUserInfoResult> CheckCodeAsync(string key)
{
var qrCodeInfo = await _qrCodeLoginProvider.GetCodeAsync(key);
return new QrCodeUserInfoResult
{
Key = qrCodeInfo.Key,
Status = qrCodeInfo.Status,
Picture = qrCodeInfo.Picture,
UserId = qrCodeInfo.UserId,
UserName = qrCodeInfo.UserName,
};
}
[HttpPost]
[Route("{key}/scan")]
[Authorize]
public async Task<QrCodeUserInfoResult> ScanCodeAsync(string key)
{
var currentUser = await _userManager.GetByIdAsync(CurrentUser.GetId());
var picture = CurrentUser.FindClaim(AbpClaimTypes.Picture)?.Value;
var userName = CurrentUser.FindClaim(AbpClaimTypes.Name)?.Value ?? currentUser.UserName;
var userId = await _userManager.GetUserIdAsync(currentUser);
var qrCodeInfo = await _qrCodeLoginProvider.ScanCodeAsync(key,
new QrCodeScanParams(userId, userName, picture, currentUser.TenantId));
return new QrCodeUserInfoResult
{
Key = qrCodeInfo.Key,
Status = qrCodeInfo.Status,
Picture = qrCodeInfo.Picture,
UserId = qrCodeInfo.UserId,
UserName = qrCodeInfo.UserName
};
}
[HttpPost]
[Route("{key}/confirm")]
[Authorize]
public async Task<QrCodeUserInfoResult> ConfirmCodeAsync(string key)
{
var qrCodeInfo = await _qrCodeLoginProvider.ConfirmCodeAsync(key);
return new QrCodeUserInfoResult
{
Key = qrCodeInfo.Key,
Status = qrCodeInfo.Status,
Picture = qrCodeInfo.Picture,
UserId = qrCodeInfo.UserId,
UserName = qrCodeInfo.UserName
};
}
}

1
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj

@ -43,6 +43,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.Domain\LINGYUN.Abp.Identity.Domain.csproj" /> <ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.Domain\LINGYUN.Abp.Identity.Domain.csproj" />
<ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.AspNetCore.QrCode\LINGYUN.Abp.Identity.AspNetCore.QrCode.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.Account.Application.Contracts\LINGYUN.Abp.Account.Application.Contracts.csproj" /> <ProjectReference Include="..\LINGYUN.Abp.Account.Application.Contracts\LINGYUN.Abp.Account.Application.Contracts.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.Account.Emailing\LINGYUN.Abp.Account.Emailing.csproj" /> <ProjectReference Include="..\LINGYUN.Abp.Account.Emailing\LINGYUN.Abp.Account.Emailing.csproj" />
</ItemGroup> </ItemGroup>

3
aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore.QrCode/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>

30
aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore.QrCode/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

24
aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore.QrCode/LINGYUN.Abp.Identity.AspNetCore.QrCode.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AssemblyName>LINGYUN.Abp.Identity.AspNetCore.QrCode</AssemblyName>
<PackageId>LINGYUN.Abp.Identity.AspNetCore.QrCode</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Identity.AspNetCore" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.Identity.QrCode\LINGYUN.Abp.Identity.QrCode.csproj" />
</ItemGroup>
</Project>

20
aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore.QrCode/LINGYUN/Abp/Identity/AspNetCore/QrCode/AbpIdentityAspNetCoreQrCodeModule.cs

@ -0,0 +1,20 @@
using LINGYUN.Abp.Identity.QrCode;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.Identity.AspNetCore;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Identity.AspNetCore.QrCode;
[DependsOn(
typeof(AbpIdentityQrCodeModule),
typeof(AbpIdentityAspNetCoreModule))]
public class AbpIdentityAspNetCoreQrCodeModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<IdentityBuilder>(builder =>
{
builder.AddTokenProvider<QrCodeUserTokenProvider>(QrCodeUserTokenProvider.ProviderName);
});
}
}

19
aspnet-core/modules/identity/LINGYUN.Abp.Identity.AspNetCore.QrCode/LINGYUN/Abp/Identity/AspNetCore/QrCode/QrCodeUserTokenProvider.cs

@ -0,0 +1,19 @@
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
namespace LINGYUN.Abp.Identity.QrCode;
public class QrCodeUserTokenProvider : DataProtectorTokenProvider<IdentityUser>
{
public static string ProviderName => QrCodeLoginProviderConsts.Name;
public QrCodeUserTokenProvider(
IDataProtectionProvider dataProtectionProvider,
IOptions<DataProtectionTokenProviderOptions> options,
ILogger<DataProtectorTokenProvider<IdentityUser>> logger)
: base(dataProtectionProvider, options, logger)
{
}
}

3
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

25
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN.Abp.Identity.QrCode.csproj

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>net9.0</TargetFrameworks>
<AssemblyName>LINGYUN.Abp.Identity.QrCode</AssemblyName>
<PackageId>LINGYUN.Abp.Identity.QrCode</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<Content Remove="LINGYUN\Abp\Identity\QrCode\Localization\Resources\*.json" />
<EmbeddedResource Include="LINGYUN\Abp\Identity\QrCode\Localization\Resources\*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Identity.Domain" />
</ItemGroup>
</Project>

26
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/AbpIdentityQrCodeModule.cs

@ -0,0 +1,26 @@
using Volo.Abp.Identity;
using Volo.Abp.Identity.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.Identity.QrCode;
[DependsOn(typeof(AbpIdentityDomainModule))]
public class AbpIdentityQrCodeModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpIdentityQrCodeModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<IdentityResource>()
.AddVirtualJson("/LINGYUN/Abp/Identity/QrCode/Localization/Resources");
});
}
}

16
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/IQrCodeLoginProvider.cs

@ -0,0 +1,16 @@
using System.Threading.Tasks;
namespace LINGYUN.Abp.Identity.QrCode;
public interface IQrCodeLoginProvider
{
Task<QrCodeInfo> GenerateAsync();
Task<QrCodeInfo> GetCodeAsync(string key);
Task<QrCodeInfo> ScanCodeAsync(string key, QrCodeScanParams @params);
Task<QrCodeInfo> ConfirmCodeAsync(string key);
Task RemoveAsync(string key);
}

8
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/Localization/Resources/en.json

@ -0,0 +1,8 @@
{
"culture": "en",
"texts": {
"QrCode:Invalid": "Invalid qrcode!",
"QrCode:NotScaned": "Qrcode not scaned!",
"QrCode:Scaned": "Please confirm the QR code."
}
}

8
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/Localization/Resources/zh-Hans.json

@ -0,0 +1,8 @@
{
"culture": "zh-Hans",
"texts": {
"QrCode:Invalid": "二维码已失效!",
"QrCode:NotScaned": "未扫描二维码!",
"QrCode:Scaned": "请确认二维码."
}
}

39
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeCacheItem.cs

@ -0,0 +1,39 @@
using System;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.Identity.QrCode;
[Serializable]
[IgnoreMultiTenancy]
public class QrCodeCacheItem
{
public string Key { get; set; }
public string Token { get; set; }
public QrCodeStatus Status { get; set; }
public string UserId { get; set; }
public string UserName { get; set; }
public string Picture { get; set; }
public Guid? TenantId { get; set; }
public QrCodeCacheItem()
{
}
public QrCodeCacheItem(string key)
{
Key = key;
Status = QrCodeStatus.Created;
}
public QrCodeInfo GetQrCodeInfo()
{
var qrCodeInfo = new QrCodeInfo(Key)
{
UserId = UserId,
UserName = UserName,
Picture = Picture,
};
qrCodeInfo.SetToken(Token);
qrCodeInfo.SetStatus(Status);
return qrCodeInfo;
}
}

29
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeInfo.cs

@ -0,0 +1,29 @@
using System;
namespace LINGYUN.Abp.Identity.QrCode;
public class QrCodeInfo
{
public string Key { get; }
public string Token { get; private set; }
public QrCodeStatus Status { get; private set; }
public string UserId { get; set; }
public string UserName { get; set; }
public string Picture { get; set; }
public QrCodeInfo(string key)
{
Key = key;
Status = QrCodeStatus.Created;
}
public void SetToken(string token)
{
Token = token;
}
public void SetStatus(QrCodeStatus status)
{
Status = status;
}
}

143
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeLoginProvider.cs

@ -0,0 +1,143 @@
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Localization;
using System;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Identity.Localization;
namespace LINGYUN.Abp.Identity.QrCode;
public class QrCodeLoginProvider : IQrCodeLoginProvider, ITransientDependency
{
protected IdentityUserManager UserManager { get; }
protected IStringLocalizer<IdentityResource> L { get; }
protected IDistributedCache<QrCodeCacheItem> QrCodeCache { get; }
public QrCodeLoginProvider(
IStringLocalizer<IdentityResource> stringLocalizer,
IDistributedCache<QrCodeCacheItem> qrCodeCache,
IdentityUserManager userManager)
{
L = stringLocalizer;
QrCodeCache = qrCodeCache;
UserManager = userManager;
}
public async virtual Task<QrCodeInfo> GenerateAsync()
{
var key = Guid.NewGuid().ToString("n");
var cacheItem = new QrCodeCacheItem(key);
await QrCodeCache.SetAsync(key, cacheItem,
new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromSeconds(180),
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(300)
});
return cacheItem.GetQrCodeInfo();
}
public async virtual Task<QrCodeInfo> GetCodeAsync(string key)
{
var cacheItem = await QrCodeCache.GetAsync(key);
if (cacheItem == null)
{
var qrCodeInfo = new QrCodeInfo(key);
qrCodeInfo.SetStatus(QrCodeStatus.Invalid);
return qrCodeInfo;
}
return cacheItem.GetQrCodeInfo();
}
public async virtual Task<QrCodeInfo> ScanCodeAsync(string key, QrCodeScanParams @params)
{
var cacheItem = await QrCodeCache.GetAsync(key);
if (cacheItem == null)
{
var qrCodeInfo = new QrCodeInfo(key);
qrCodeInfo.SetStatus(QrCodeStatus.Invalid);
return qrCodeInfo;
}
if (cacheItem.Status == QrCodeStatus.Scaned)
{
return cacheItem.GetQrCodeInfo();
}
cacheItem.UserId = @params.UserId;
cacheItem.UserName = @params.UserName;
cacheItem.Picture = @params.Picture;
cacheItem.TenantId = @params.TenantId;
cacheItem.Status = QrCodeStatus.Scaned;
await QrCodeCache.SetAsync(key, cacheItem,
new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromSeconds(180),
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(300)
});
return cacheItem.GetQrCodeInfo();
}
public async virtual Task<QrCodeInfo> ConfirmCodeAsync(string key)
{
var cacheItem = await QrCodeCache.GetAsync(key);
if (cacheItem == null)
{
var qrCodeInfo = new QrCodeInfo(key);
qrCodeInfo.SetStatus(QrCodeStatus.Invalid);
return qrCodeInfo;
}
if (cacheItem.Status == QrCodeStatus.Confirmed)
{
return cacheItem.GetQrCodeInfo();
}
if (cacheItem.UserId.IsNullOrWhiteSpace())
{
throw new UserFriendlyException(L["QrCode:NotScaned"]);
}
var token = await GenerateConfirmToken(cacheItem.UserId);
cacheItem.Token = token;
cacheItem.Status = QrCodeStatus.Confirmed;
await QrCodeCache.SetAsync(key, cacheItem,
new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromSeconds(180),
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(300)
});
return cacheItem.GetQrCodeInfo();
}
public async virtual Task RemoveAsync(string key)
{
var cacheItem = await QrCodeCache.GetAsync(key);
if (cacheItem != null)
{
await QrCodeCache.RemoveAsync(key);
}
}
protected async virtual Task<string> GenerateConfirmToken(string userId)
{
var user = await UserManager.FindByIdAsync(userId);
return await UserManager.GenerateUserTokenAsync(user,
QrCodeLoginProviderConsts.Name,
QrCodeLoginProviderConsts.Purpose);
}}

10
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeLoginProviderConsts.cs

@ -0,0 +1,10 @@
namespace LINGYUN.Abp.Identity.QrCode;
public static class QrCodeLoginProviderConsts
{
public static string Name { get; set; } = "QrCode";
public static string Purpose { get; set; } = "QrCodeLogin";
public static string GrantType { get; set; } = "qr_code";
}

22
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeScanParams.cs

@ -0,0 +1,22 @@
using System;
namespace LINGYUN.Abp.Identity.QrCode;
public class QrCodeScanParams
{
public string UserId { get; }
public string UserName { get; }
public string Picture { get; }
public Guid? TenantId { get; }
public QrCodeScanParams(
string userId,
string userName,
string picture = null,
Guid? tenantId = null)
{
UserId = userId;
UserName = userName;
Picture = picture;
TenantId = tenantId;
}
}

9
aspnet-core/modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN/Abp/Identity/QrCode/QrCodeStatus.cs

@ -0,0 +1,9 @@
namespace LINGYUN.Abp.Identity.QrCode;
public enum QrCodeStatus
{
Invalid = -1,
Created = 0,
Scaned = 5,
Confirmed = 10,
}

9
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.AspNetCore/LINGYUN/Abp/OpenIddict/AspNetCore/Controllers/UserInfoController.cs

@ -1,11 +1,11 @@
using LINGYUN.Abp.Identity; using OpenIddict.Abstractions;
using OpenIddict.Abstractions;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims; using Volo.Abp.Security.Claims;
using Volo.Abp.Users;
using VoloUserInfoController = Volo.Abp.OpenIddict.Controllers.UserInfoController; using VoloUserInfoController = Volo.Abp.OpenIddict.Controllers.UserInfoController;
namespace LINGYUN.Abp.OpenIddict.AspNetCore.Controllers; namespace LINGYUN.Abp.OpenIddict.AspNetCore.Controllers;
@ -26,7 +26,8 @@ public class UserInfoController : VoloUserInfoController
var claims = new Dictionary<string, object>(StringComparer.Ordinal) var claims = new Dictionary<string, object>(StringComparer.Ordinal)
{ {
// Note: the "sub" claim is a mandatory claim and must be included in the JSON response. // Note: the "sub" claim is a mandatory claim and must be included in the JSON response.
[OpenIddictConstants.Claims.Subject] = await UserManager.GetUserIdAsync(user) [OpenIddictConstants.Claims.Subject] = await UserManager.GetUserIdAsync(user),
[AbpClaimTypes.SessionId] = CurrentUser.FindSessionId(),
}; };
if (User.HasScope(OpenIddictConstants.Scopes.Profile)) if (User.HasScope(OpenIddictConstants.Scopes.Profile))
@ -36,7 +37,7 @@ public class UserInfoController : VoloUserInfoController
claims[OpenIddictConstants.Claims.FamilyName] = user.Surname; claims[OpenIddictConstants.Claims.FamilyName] = user.Surname;
claims[OpenIddictConstants.Claims.GivenName] = user.Name; claims[OpenIddictConstants.Claims.GivenName] = user.Name;
// 重写添加用户头像 // 重写添加用户头像
var picture = user.Claims.FirstOrDefault(x => x.ClaimType == IdentityConsts.ClaimType.Avatar.Name); var picture = user.Claims.FirstOrDefault(x => x.ClaimType == AbpClaimTypes.Picture);
if (picture != null) if (picture != null)
{ {
claims[OpenIddictConstants.Claims.Picture] = picture.ClaimValue; claims[OpenIddictConstants.Claims.Picture] = picture.ClaimValue;

3
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

24
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/LINGYUN.Abp.OpenIddict.QrCode.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AssemblyName>LINGYUN.Abp.OpenIddict.QrCode</AssemblyName>
<PackageId>LINGYUN.Abp.OpenIddict.QrCode</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.OpenIddict.AspNetCore" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.QrCode\LINGYUN.Abp.Identity.QrCode.csproj" />
</ItemGroup>
</Project>

31
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/LINGYUN/Abp/OpenIddict/QrCode/AbpOpenIddictQrCodeModule.cs

@ -0,0 +1,31 @@
using LINGYUN.Abp.Identity.QrCode;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.OpenIddict;
using Volo.Abp.OpenIddict.ExtensionGrantTypes;
namespace LINGYUN.Abp.OpenIddict.QrCode;
[DependsOn(
typeof(AbpIdentityQrCodeModule),
typeof(AbpOpenIddictDomainModule))]
public class AbpOpenIddictQrCodeModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.AllowQrCodeFlow();
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpOpenIddictExtensionGrantsOptions>(options =>
{
options.Grants.TryAdd(
QrCodeLoginProviderConsts.GrantType,
new QrCodeTokenExtensionGrant());
});
}
}

204
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/LINGYUN/Abp/OpenIddict/QrCode/QrCodeTokenExtensionGrant.cs

@ -0,0 +1,204 @@
using LINGYUN.Abp.Identity.QrCode;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.OpenIddict;
using Volo.Abp.OpenIddict.ExtensionGrantTypes;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
using SignInResult = Microsoft.AspNetCore.Mvc.SignInResult;
namespace LINGYUN.Abp.OpenIddict.QrCode;
public class QrCodeTokenExtensionGrant : ITokenExtensionGrant
{
public string Name => QrCodeLoginProviderConsts.GrantType;
public async virtual Task<IActionResult> HandleAsync(ExtensionGrantContext context)
{
var logger = GetRequiredService<ILogger<QrCodeTokenExtensionGrant>>(context);
// 取出二维码Key
var qrcodeKey = context.Request.GetParameter("qrcode_key")?.ToString();
if (qrcodeKey.IsNullOrWhiteSpace())
{
logger.LogInformation("The user has not passed the QR code Key required for scanning and login.");
var properties = new AuthenticationProperties(
new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The Qr code is invalid."
}
);
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
var qrCodeProvider = GetRequiredService<IQrCodeLoginProvider>(context);
var qrCodeInfo = await qrCodeProvider.GetCodeAsync(qrcodeKey);
// 二维码扫描后用户Id不为空
if (qrCodeInfo == null || qrCodeInfo.Token.IsNullOrWhiteSpace() == true)
{
logger.LogInformation("The QR code Key {0} is invalid or the user has not scanned the QR code.", qrcodeKey);
var properties = new AuthenticationProperties(
new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The Qr code is invalid."
}
);
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
Guid? tenantId = null;
var tenantIdValue = context.Request.GetParameter("tenant_id")?.ToString();
if (!tenantIdValue.IsNullOrWhiteSpace() && Guid.TryParse(tenantIdValue, out var tenantGuid))
{
tenantId = tenantGuid;
}
var userManager = GetRequiredService<IdentityUserManager>(context);
var currentTenant = GetRequiredService<ICurrentTenant>(context);
using (currentTenant.Change(tenantId))
{
var user = await userManager.FindByIdAsync(qrCodeInfo.UserId);
if (user == null)
{
var properties = new AuthenticationProperties(
new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "Invalid user id."
}
);
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
if (!await userManager.VerifyUserTokenAsync(user, QrCodeLoginProviderConsts.Name, QrCodeLoginProviderConsts.Purpose, qrCodeInfo.Token))
{
logger.LogInformation("Authentication failed for username: {username}, reason: the user token is invalid", user.UserName);
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = OpenIddictResources.GetResourceString(OpenIddictResources.ID2019),
});
await SaveSecurityLogAsync(context, user, OpenIddictSecurityLogActionConsts.LoginLockedout);
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
// 检查是否已锁定
if (await userManager.IsLockedOutAsync(user))
{
logger.LogInformation("Authentication failed for username: {username}, reason: locked out", user.UserName);
var properties = new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user account has been locked out due to invalid login attempts. Please wait a while and try again.",
});
await SaveSecurityLogAsync(context, user, OpenIddictSecurityLogActionConsts.LoginLockedout);
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
await qrCodeProvider.RemoveAsync(qrcodeKey);
return await SetSuccessResultAsync(context, user, logger);
}
}
protected virtual T GetRequiredService<T>(ExtensionGrantContext context)
{
return context.HttpContext.RequestServices.GetRequiredService<T>();
}
protected virtual Task<string> FindClientIdAsync(ExtensionGrantContext context)
{
return Task.FromResult(context.Request.ClientId);
}
protected async virtual Task<IActionResult> SetSuccessResultAsync(ExtensionGrantContext context, IdentityUser user, ILogger<QrCodeTokenExtensionGrant> logger)
{
logger.LogInformation("Credentials validated for username: {username}", user.UserName);
var signInManager = GetRequiredService<SignInManager<IdentityUser>>(context);
var principal = await signInManager.CreateUserPrincipalAsync(user);
principal.SetScopes(context.Request.GetScopes());
principal.SetResources(await GetResourcesAsync(context));
await SetClaimsDestinationsAsync(context, principal);
await SaveSecurityLogAsync(
context,
user,
OpenIddictSecurityLogActionConsts.LoginSucceeded);
return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal);
}
protected async virtual Task SaveSecurityLogAsync(ExtensionGrantContext context, IdentityUser user, string action)
{
var logContext = new IdentitySecurityLogContext
{
Identity = OpenIddictSecurityLogIdentityConsts.OpenIddict,
Action = action,
UserName = user.UserName,
ClientId = await FindClientIdAsync(context)
};
logContext.WithProperty("GrantType", Name);
var identitySecurityLogManager = GetRequiredService<IdentitySecurityLogManager>(context);
await identitySecurityLogManager.SaveAsync(logContext);
}
protected async virtual Task SetClaimsDestinationsAsync(ExtensionGrantContext context, ClaimsPrincipal principal)
{
var principalManager = GetRequiredService<AbpOpenIddictClaimsPrincipalManager>(context);
await principalManager.HandleAsync(context.Request, principal);
}
public virtual ForbidResult Forbid(AuthenticationProperties properties, params string[] authenticationSchemes)
{
return new ForbidResult(
authenticationSchemes,
properties);
}
protected async virtual Task<IEnumerable<string>> GetResourcesAsync(ExtensionGrantContext context)
{
var scopes = context.Request.GetScopes();
var resources = new List<string>();
if (!scopes.Any())
{
return resources;
}
var scopeManager = GetRequiredService<IOpenIddictScopeManager>(context);
await foreach (var resource in scopeManager.ListResourcesAsync(scopes))
{
resources.Add(resource);
}
return resources;
}
}

11
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/Microsoft/Extensions/DependencyInjection/QrCodeOpenIddictServerBuilderExtensions.cs

@ -0,0 +1,11 @@
using LINGYUN.Abp.Identity.QrCode;
namespace Microsoft.Extensions.DependencyInjection;
public static class QrCodeOpenIddictServerBuilderExtensions
{
public static OpenIddictServerBuilder AllowQrCodeFlow(this OpenIddictServerBuilder builder)
{
return builder.AllowCustomFlow(QrCodeLoginProviderConsts.GrantType);
}
}

96
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.QrCode/README.md

@ -0,0 +1,96 @@
# LINGYUN.Abp.OpenIddict.QrCode
[![ABP version](https://img.shields.io/badge/dynamic/xml?style=flat-square&color=yellow&label=abp&query=%2F%2FProject%2FPropertyGroup%2FAbpVersion&url=https%3A%2F%2Fraw.githubusercontent.com%2Fcolinin%2Fabp-next-admin%2Fmaster%2Faspnet-core%2Fmodules%2FopenIddict%2FLINGYUN.Abp.OpenIddict.QrCode%2FLINGYUN.Abp.OpenIddict.QrCode.csproj)](https://abp.io)
[![NuGet](https://img.shields.io/nuget/v/LINGYUN.Abp.OpenIddict.QrCode.svg?style=flat-square)](https://www.nuget.org/packages/LINGYUN.Abp.OpenIddict.QrCode)
## 简介
`LINGYUN.Abp.OpenIddict.QrCode` 是 OpenIddict 的扫码登录认证扩展模块,提供了扫码登录认证功能。
[English](./README.EN.md)
## 功能特性
* 扫码二维码登录
* 安全日志
* 记录登录尝试
* 记录登录失败
* 记录密码修改
## 安装
```bash
dotnet add package LINGYUN.Abp.OpenIddict.QrCode
```
## 使用
1. 添加 `[DependsOn(typeof(AbpOpenIddictQrCodeModule))]` 到你的模块类。
2. 配置 OpenIddict 服务器:
```csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<OpenIddictServerBuilder>(builder =>
{
// 允许门户认证流程
builder.AllowQrCodeFlow();
});
}
```
3. 使用示例:
```http
POST /connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=qr_code_&
username=admin&
password=1q2w3E*&
enterpriseId=your-enterprise-id&
scope=openid profile
```
## 认证流程
1. 二维码验证
* 用户提供二维码Key (qrcode_key_)
* 如未提供或无效,返回无效的二维码错误
## 参数说明
* qrcode_key_ (必填)
* 二维码Key
* password (必填)
* 用户密码
* enterpriseId (必填)
* 企业ID,必须是有效的GUID格式
* TwoFactorProvider (可选)
* 双因素认证提供程序名称
* 仅在启用双因素认证时需要
* TwoFactorCode (可选)
* 双因素认证码
* 仅在启用双因素认证时需要
* ChangePasswordToken (可选)
* 修改密码令牌
* 仅在需要修改密码时需要
* NewPassword (可选)
* 新密码
* 仅在需要修改密码时需要
## 注意事项
* 企业ID必须是有效的GUID格式
* 密码必须符合系统配置的密码策略
* 双因素认证码有效期有限
* 所有认证操作都会记录安全日志
* 建议在生产环境中使用 HTTPS

1
aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj

@ -188,6 +188,7 @@
<ProjectReference Include="..\..\modules\localization-management\LINGYUN.Abp.LocalizationManagement.HttpApi\LINGYUN.Abp.LocalizationManagement.HttpApi.csproj" /> <ProjectReference Include="..\..\modules\localization-management\LINGYUN.Abp.LocalizationManagement.HttpApi\LINGYUN.Abp.LocalizationManagement.HttpApi.csproj" />
<ProjectReference Include="..\..\modules\openIddict\LINGYUN.Abp.OpenIddict.AspNetCore.Session\LINGYUN.Abp.OpenIddict.AspNetCore.Session.csproj" /> <ProjectReference Include="..\..\modules\openIddict\LINGYUN.Abp.OpenIddict.AspNetCore.Session\LINGYUN.Abp.OpenIddict.AspNetCore.Session.csproj" />
<ProjectReference Include="..\..\modules\openIddict\LINGYUN.Abp.OpenIddict.AspNetCore\LINGYUN.Abp.OpenIddict.AspNetCore.csproj" /> <ProjectReference Include="..\..\modules\openIddict\LINGYUN.Abp.OpenIddict.AspNetCore\LINGYUN.Abp.OpenIddict.AspNetCore.csproj" />
<ProjectReference Include="..\..\modules\openIddict\LINGYUN.Abp.OpenIddict.QrCode\LINGYUN.Abp.OpenIddict.QrCode.csproj" />
<ProjectReference Include="..\..\modules\realtime-message\LINGYUN.Abp.IM.SignalR\LINGYUN.Abp.IM.SignalR.csproj" /> <ProjectReference Include="..\..\modules\realtime-message\LINGYUN.Abp.IM.SignalR\LINGYUN.Abp.IM.SignalR.csproj" />
<ProjectReference Include="..\..\modules\realtime-message\LINGYUN.Abp.IM\LINGYUN.Abp.IM.csproj" /> <ProjectReference Include="..\..\modules\realtime-message\LINGYUN.Abp.IM\LINGYUN.Abp.IM.csproj" />
<ProjectReference Include="..\..\modules\realtime-message\LINGYUN.Abp.MessageService.Application.Contracts\LINGYUN.Abp.MessageService.Application.Contracts.csproj" /> <ProjectReference Include="..\..\modules\realtime-message\LINGYUN.Abp.MessageService.Application.Contracts\LINGYUN.Abp.MessageService.Application.Contracts.csproj" />

2
aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.Configure.cs

@ -1,3 +1,4 @@
using LINGYUN.Abp.Identity.QrCode;
using LY.MicroService.Applications.Single.DataSeeder; using LY.MicroService.Applications.Single.DataSeeder;
using VoloAbpExceptionHandlingOptions = Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingOptions; using VoloAbpExceptionHandlingOptions = Volo.Abp.AspNetCore.ExceptionHandling.AbpExceptionHandlingOptions;
@ -258,6 +259,7 @@ public partial class MicroServiceApplicationsSingleModule
options.PersistentSessionGrantTypes.Add(WeChatTokenExtensionGrantConsts.OfficialGrantType); options.PersistentSessionGrantTypes.Add(WeChatTokenExtensionGrantConsts.OfficialGrantType);
options.PersistentSessionGrantTypes.Add(WeChatTokenExtensionGrantConsts.MiniProgramGrantType); options.PersistentSessionGrantTypes.Add(WeChatTokenExtensionGrantConsts.MiniProgramGrantType);
options.PersistentSessionGrantTypes.Add(AbpWeChatWorkGlobalConsts.GrantType); options.PersistentSessionGrantTypes.Add(AbpWeChatWorkGlobalConsts.GrantType);
options.PersistentSessionGrantTypes.Add(QrCodeLoginProviderConsts.GrantType);
}); });
Configure<OpenIddictServerOptions>(options => Configure<OpenIddictServerOptions>(options =>

3
aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs

@ -3,6 +3,7 @@ using LINGYUN.Abp.Gdpr;
using LINGYUN.Abp.Gdpr.EntityFrameworkCore; using LINGYUN.Abp.Gdpr.EntityFrameworkCore;
using LINGYUN.Abp.Gdpr.Identity; using LINGYUN.Abp.Gdpr.Identity;
using LINGYUN.Abp.Gdpr.Web; using LINGYUN.Abp.Gdpr.Web;
using LINGYUN.Abp.OpenIddict.QrCode;
namespace LY.MicroService.Applications.Single; namespace LY.MicroService.Applications.Single;
@ -123,6 +124,8 @@ namespace LY.MicroService.Applications.Single;
typeof(AbpOpenIddictWeChatModule), typeof(AbpOpenIddictWeChatModule),
// OpenIddict扩展模块 企业微信认证 // OpenIddict扩展模块 企业微信认证
typeof(AbpOpenIddictWeChatWorkModule), typeof(AbpOpenIddictWeChatWorkModule),
// OpenIddict扩展模块 扫码登录
typeof(AbpOpenIddictQrCodeModule),
//typeof(AbpOssManagementMinioModule), // 取消注释以使用Minio //typeof(AbpOssManagementMinioModule), // 取消注释以使用Minio
// 对象存储模块 文件系统 // 对象存储模块 文件系统

Loading…
Cancel
Save