我已覆盖了微软身份验证提供的默认IdentityUser和UserStore。
public class ApplicationUser<TIdentityKey, TClientKey> : IdentityUser<TIdentityKey>, IApplicationUser<TIdentityKey, TClientKey>
where TIdentityKey : IEquatable<TIdentityKey>
where TClientKey : IEquatable<TClientKey>
{
public TClientKey TenantId { get; set; }
}
public class ApplicationUserStore<TUser, TRole, TIdentityKey, TClientKey> : UserStore<TUser, TRole, IdentityServerDbContext<TIdentityKey, TClientKey>, TIdentityKey>
where TUser : ApplicationUser<TIdentityKey, TClientKey>
where TRole : ApplicationRole<TIdentityKey>
where TIdentityKey : IEquatable<TIdentityKey>
where TClientKey : IEquatable<TClientKey>
{
private readonly IdentityServerDbContext<TIdentityKey, TClientKey> _context;
private readonly ITenantService<TIdentityKey, TClientKey> _tenantService;
public ApplicationUserStore(IdentityServerDbContext<TIdentityKey, TClientKey> context, ITenantService<TIdentityKey, TClientKey> tenantService) : base(context)
{
_context = context;
_tenantService = tenantService;
}
public async override Task<IdentityResult> CreateAsync(TUser user, CancellationToken cancellationToken = default)
{
user.TenantId = await GetTenantId();
bool combinationExists = await _context.Users
.AnyAsync(x => x.UserName == user.UserName
&& x.Email == user.Email
&& x.TenantId.Equals(user.TenantId));
if (combinationExists)
{
var IdentityError = new IdentityError { Description = "The specified username and email are already registered" };
return IdentityResult.Failed(IdentityError);
}
return await base.CreateAsync(user);
}
private async Task<TClientKey> GetTenantId()
{
var tenant = await _tenantService.GetCurrentTenant();
if (tenant == null)
return default(TClientKey);
else
return tenant.Id;
}
}
我已经将这些内容制作在一个类库中并导入到不同的项目中。这样我就可以根据项目需求为用户提供不同的键,例如基于 Guid、int、字符串等。我遇到的问题是,当我尝试在身份验证页面(如 ConfirmPassword 页面)中使用它们时,我需要在模型中指定泛型,以便我可以使用依赖注入来进行控制。
public class ConfirmEmailModel<TIdentityKey,TClientKey> : PageModel
where TIdentityKey:IEqutable<TIdentityKey>
where TClientKey:IEqutable<TClientKey>
{
private readonly UserManager<ApplicationUser<TIdentityKey,TClientKey>> _userManager;
public ConfirmEmailModel (UserManager<ApplicationUser<TIdentityKey,TClientKey>> userManager)
{
_userManager = userManager;
}
[TempData]
public virtual string StatusMessage { get; set; }
public virtual async Task<IActionResult> OnGetAsync(string userId, string code)
{
if (userId == null || code == null)
{
return RedirectToPage("/Index");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound($"Unable to load user with ID '{userId}'.");
}
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
var result = await _userManager.ConfirmEmailAsync(user, code);
StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
return Page();
}
}
当我这样指定通用类型时,我无法在Razor页面内使用它,因为Razor页面不支持通用类型。
@page
@model ConfirmEmailModel<T>// SYNTAX ERROR
@{
ViewData["Title"] = "Confirm email";
}
<h1>@ViewData["Title"]</h1>
另一个问题是当我尝试在控制器中使用SignInManager或UserStore时,我无法使用依赖注入将泛型注入到相应的位置。
Public class BaseUserInfoController<TIdentityKey,TClientKey> : Controller
where TIdentityKey:IEqutable<TIdentityKey>
where TClientKey:IEqutable<TClientKey>
{
private readonly UserManager<ApplicationUser<TIdentityKey,TClientKey>> _userManager;
public BaseUserInfoController(UserManager<ApplicationUser<TIdentityKey,TClientKey>> userManager)
=> _userManager = userManager;
//
// GET: /api/userinfo
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
[HttpGet("~/connect/userinfo"), HttpPost("~/connect/userinfo"), Produces("application/json")]
public virtual async Task<IActionResult> Userinfo()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return Challenge(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidToken,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The specified access token is bound to an account that no longer exists."
}));
}
var claims = new Dictionary<string, object>(StringComparer.Ordinal)
{
// Note: the "sub" claim is a mandatory claim and must be included in the JSON response.
[Claims.Subject] = await _userManager.GetUserIdAsync(user)
};
if (User.HasScope(Scopes.Email))
{
claims[Claims.Email] = await _userManager.GetEmailAsync(user);
claims[Claims.EmailVerified] = await _userManager.IsEmailConfirmedAsync(user);
}
if (User.HasScope(Scopes.Phone))
{
claims[Claims.PhoneNumber] = await _userManager.GetPhoneNumberAsync(user);
claims[Claims.PhoneNumberVerified] = await _userManager.IsPhoneNumberConfirmedAsync(user);
}
if (User.HasScope(Scopes.Roles))
{
//claims[Claims.Role] = await _userManager.GetRolesAsync(user);
List<string> roles = new List<string> { "dataEventRecords", "dataEventRecords.admin", "admin", "dataEventRecords.user" };
}
// Note: the complete list of standard claims supported by the OpenID Connect specification
// can be found here: http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
return Ok(claims);
}
}
我写了一个 IUnitOfWork 用于另一个服务。在控制器中使用该 IUnitOfWork,我需要再次在控制器内指定所有的键。
public interface IUnitOfWork<TRoleKey, TUserKey, TClientKey> : IDisposable
where TRoleKey : IEquatable<TRoleKey>
where TUserKey : IEquatable<TUserKey>
where TClientKey : IEquatable<TClientKey>
{
IUserService<TRoleKey, TUserKey, TClientKey> UserService { get; }
IRoleService<TRoleKey, TUserKey, TClientKey> RoleService { get; }
IUserRoleService<TRoleKey, TUserKey, TClientKey> UserRoleService { get; }
IRolePermissionService<TRoleKey, TUserKey, TClientKey> RolePermissionService { get; }
Task<bool> Commit();
}
为了解决所有这些问题,我考虑使用标记接口来处理不同的服务,例如使用ApplicationUser。
public interface IMarkerApplicationUser{}
public class ApplicationUser<TIdentityKey, TClientKey> : IMarkerApplicationUser,IdentityUser<TIdentityKey>, IApplicationUser<TIdentityKey, TClientKey>
where TIdentityKey : IEquatable<TIdentityKey>
where TClientKey : IEquatable<TClientKey>
{
public TClientKey TenantId { get; set; }
}
之后,我只需将它们作为构造参数并使用依赖注入来指定通用函数和类,而不是GenericType。
services.AddScoped<IMarkerApplicationUser, ApplicationUser<Guid,Guid>>();
这是一种好的方法吗?我已经看到很多地方说使用标记接口是不好的实践。
这样做的主要目的是为我的常见项目创建通用的微服务。像用户管理、角色管理、审计管理、异常管理等,然后从主项目传递键类型。我不想在每个地方都使用GUID作为主键,因为一些系统没有使用Guid的要求,并且有空间限制。