一个系统组织架构是不可少的,所以 认证(authentication) 和 授权(authorization)是必不可少的,但是 微软很人性化的为我们提供了Identity这个东西,嗯,是好东西,但是总是稍微有那么一点不足,就是命名,以及部分字段可能用不到。没关系,我们可以自定义。
1.第三方或官方提供的便捷方式?
1.1.默认的IdentityUser,IdentityRole :
生成方式没有什么好说的,创建项目时,修改下身份验证方式即可见到,如上面的表结构。这里涉及到 Claims identityClaims,如果还不知道是什么 看这里 应该是最简的总结了吧。这里很明显的,默认生成的表名称前缀是AspNet打头,,,根本不需要是不是。而且需要使用到 IdentityDbContext这个对象进行 迁移,那么如果我们的项目中已经自定义或者已有一个上下文对象,这两个是不是有些冲突?该用哪个呢?这里的 IdentityDbContext定义如下(Microsoft.AspNetCore.Identity.EntityFrameworkCore下定义的):
1.2.ids4(identityServer4)生成的表如下:
不得不说ids4很方便,但是有点多有点乱,不是我们想要的,所以怎么办呢?
2.简要分析。
按照我们以前用FX的时候的一贯逻辑,知道identity使用SignInManager,但是SignInManager使用到一个 UserManager对象,而这个UserManager使用到了 UserStore这个东西,好,这样的话我们就打开core.Indetity的源码再看看访问数据那里是如何定义的(因为我们一开始就说了,要自定义或者使用现有的模型)。
很明显,思路也很明确,UserStore的实现,还是继承自 Identity.EntityFramework下的一个UserStore,继续跟进:
到这里就很直观了,Framework下面的这个UserStore实现了一系列的接口,在构造函数中注入了默认的上下问对象DbContext,呵呵,找就找到了根源了,那么我们就重新实现这个UserStore就可以了,将其中的数据上下文DbContext换成我们自己的就好了,我这么说没意见吧?
3.重定义UserStore
定义前我们要注意下,2中,泛型涉及到的 参数类型,我们都需要定义,可以直接使用core Identity中定义的,改下名称即可,我们自定义的UserStore名称我依旧定义成UserStore.cs: (有点长。。。)
public class UserStore : IQueryableUserStore<User>, IUserLoginStore<User>, IUserClaimStore<User>, IUserPasswordStore<User>, IUserSecurityStampStore<User>, IUserEmailStore<User>, IUserLockoutStore<User>, IUserPhoneNumberStore<User>, IUserTwoFactorStore<User>, IUserAuthenticationTokenStore<User>, IUserAuthenticatorKeyStore<User>, IUserTwoFactorRecoveryCodeStore<User>, IUserRoleStore<User> /*<User, Guid, UserClain, UserLogin, UserToken, Role, Guid, UserRole>*/ { #region ctor public UserStore(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; _userRepository = _unitOfWork.Repository<User, Guid>(); _roleRepository = _unitOfWork.Repository<Role, Guid>(); _userRoleRepository = _unitOfWork.Repository<UserRole, Guid>(); _userLoginRepository = _unitOfWork.Repository<UserLogin, Guid>(); _userClaimRepository = _unitOfWork.Repository<UserClaim, Guid>(); _userTokenRepository = _unitOfWork.Repository<UserToken, Guid>(); } #endregion #region fields private bool _disposed; private readonly IUnitOfWork _unitOfWork; private readonly IRepository<User, Guid> _userRepository; private readonly IRepository<Role, Guid> _roleRepository; private readonly IRepository<UserRole, Guid> _userRoleRepository; private readonly IRepository<UserLogin, Guid> _userLoginRepository; private readonly IRepository<UserClaim, Guid> _userClaimRepository; private readonly IRepository<UserToken, Guid> _userTokenRepository; #endregion #region Implementation of IQueryableUserStore<User> /// <summary> /// Returns an <see cref="T:System.Linq.IQueryable`1" /> collection of users. /// </summary> /// <value>An <see cref="T:System.Linq.IQueryable`1" /> collection of users.</value> public IQueryable<User> Users => _userRepository.Query(x => x.ID != null); #endregion #region Implementation of IDisposable /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary> public void Dispose() { _disposed = true; } #endregion #region Implementation of IUserStore<User> /// <summary> /// Gets the user identifier for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose identifier should be retrieved.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the identifier for the specified <paramref name="user" />.</returns> public Task<string> GetUserIdAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(ConvertIdToString(user.ID)); } /// <summary> /// Gets the user name for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose name should be retrieved.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the name for the specified <paramref name="user" />.</returns> public Task<string> GetUserNameAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.UserName); } /// <summary> /// Sets the given <paramref name="userName" /> for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose name should be set.</param> /// <param name="userName">The user name to set.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetUserNameAsync(User user, string userName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); user.UserName = userName; return Task.CompletedTask; } /// <summary> /// Gets the normalized user name for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose normalized name should be retrieved.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the normalized user name for the specified <paramref name="user" />.</returns> public Task<string> GetNormalizedUserNameAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.UserName); } /// <summary> /// Sets the given normalized name for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose name should be set.</param> /// <param name="normalizedName">The normalized name to set.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetNormalizedUserNameAsync(User user, string normalizedName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); user.UserName = normalizedName; return Task.CompletedTask; } /// <summary> /// Creates the specified <paramref name="user" /> in the user store. /// </summary> /// <param name="user">The user to create.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the <see cref="T:Microsoft.AspNetCore.Identity.IdentityResult" /> of the creation operation.</returns> public async Task<IdentityResult> CreateAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); await _userRepository.InsertAsync(user); //系统的第一个用户,自动成为超级管理员 int count = _userRepository.Query(x => x.ID != null).Count(); if (count == 1) { Role adminRole = _roleRepository.Query().FirstOrDefault(); if (adminRole != null) { UserRole userRole = new UserRole() { UserId = user.ID, RoleId = adminRole.ID }; await _userRoleRepository.InsertAsync(userRole); user.IsSystem = true; _userRepository.Update(user); } } //默认角色 Role defaultRole = _roleRepository.Query().FirstOrDefault(m => m.IsDefault); if (defaultRole != null) { UserRole userRole = new UserRole() { UserId = user.ID, RoleId = defaultRole.ID }; await _userRoleRepository.InsertAsync(userRole); } await _unitOfWork.CommitAsync(); return IdentityResult.Success; } /// <summary> /// Updates the specified <paramref name="user" /> in the user store. /// </summary> /// <param name="user">The user to update.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the <see cref="T:Microsoft.AspNetCore.Identity.IdentityResult" /> of the update operation.</returns> public async Task<IdentityResult> UpdateAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); if (user.Email.IsMissing()) { user.EmailConfirmed = false; } if (user.PhoneNumber.IsMissing()) { user.PhoneNumberConfirmed = false; } _userRepository.Update(user); await _unitOfWork.CommitAsync(); return IdentityResult.Success; } /// <summary> /// Deletes the specified <paramref name="user" /> from the user store. /// </summary> /// <param name="user">The user to delete.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the <see cref="T:Microsoft.AspNetCore.Identity.IdentityResult" /> of the update operation.</returns> public async Task<IdentityResult> DeleteAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); if (user.IsSystem) { return new IdentityResult().Failed($"用户“{user.UserName}”是系统用户,不能删除"); } _userRepository.Remove(user); await _unitOfWork.CommitAsync(); return IdentityResult.Success; } /// <summary> /// Finds and returns a user, if any, who has the specified <paramref name="userId" />. /// </summary> /// <param name="userId">The user ID to search for.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the user matching the specified <paramref name="userId" /> if it exists. /// </returns> public Task<User> FindByIdAsync(string userId, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Guid id = ConvertIdFromString(userId); return Task.FromResult(_userRepository.GetByKey(id)); } /// <summary> /// Finds and returns a user, if any, who has the specified normalized user name. /// </summary> /// <param name="normalizedUserName">The normalized user name to search for.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the user matching the specified <paramref name="normalizedUserName" /> if it exists. /// </returns> public Task<User> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(_userRepository.Query().FirstOrDefault(m => m.UserName == normalizedUserName)); } #endregion #region Implementation of IUserLoginStore<User> /// <summary> /// Adds an external <see cref="T:Microsoft.AspNetCore.Identity.UserLoginInfo" /> to the specified <paramref name="user" />. /// </summary> /// <param name="user">The user to add the login to.</param> /// <param name="login">The external <see cref="T:Microsoft.AspNetCore.Identity.UserLoginInfo" /> to add to the specified <paramref name="user" />.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public async Task AddLoginAsync(User user, UserLoginInfo login, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotNull(login, nameof(login)); UserLogin userLogin = new UserLogin() { UserId = user.ID, LoginProvider = login.LoginProvider, ProviderKey = login.ProviderKey, ProviderDisplayName = login.ProviderDisplayName }; await _userLoginRepository.InsertAsync(userLogin); await _unitOfWork.CommitAsync(); } /// <summary> /// Attempts to remove the provided login information from the specified <paramref name="user" />. /// and returns a flag indicating whether the removal succeed or not. /// </summary> /// <param name="user">The user to remove the login information from.</param> /// <param name="loginProvider">The login provide whose information should be removed.</param> /// <param name="providerKey">The key given by the external login provider for the specified user.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public async Task RemoveLoginAsync(User user, string loginProvider, string providerKey, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotNullOrEmpty(loginProvider, nameof(loginProvider)); Check.NotNullOrEmpty(providerKey, nameof(providerKey)); _userLoginRepository.Remove(m => m.UserId.Equals(user.ID) && m.LoginProvider == loginProvider && m.ProviderKey == providerKey); await _unitOfWork.CommitAsync(); } /// <summary> /// Retrieves the associated logins for the specified <param ref="user" />. /// </summary> /// <param name="user">The user whose associated logins to retrieve.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The <see cref="T:System.Threading.Tasks.Task" /> for the asynchronous operation, containing a list of <see cref="T:Microsoft.AspNetCore.Identity.UserLoginInfo" /> for the specified <paramref name="user" />, if any. /// </returns> public Task<IList<UserLoginInfo>> GetLoginsAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); IList<UserLoginInfo> loginInfos = _userLoginRepository.Query(m => m.UserId.Equals(user.ID)).Select(m => new UserLoginInfo(m.LoginProvider, m.ProviderKey, m.ProviderDisplayName)).ToList(); return Task.FromResult(loginInfos); } /// <summary> /// Retrieves the user associated with the specified login provider and login provider key. /// </summary> /// <param name="loginProvider">The login provider who provided the <paramref name="providerKey" />.</param> /// <param name="providerKey">The key provided by the <paramref name="loginProvider" /> to identify a user.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The <see cref="T:System.Threading.Tasks.Task" /> for the asynchronous operation, containing the user, if any which matched the specified login provider and key. /// </returns> public Task<User> FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNullOrEmpty(loginProvider, nameof(loginProvider)); Check.NotNullOrEmpty(providerKey, nameof(providerKey)); Guid userId = _userLoginRepository.Query(m => m.LoginProvider == loginProvider && m.ProviderKey == providerKey) .Select(m => m.UserId).FirstOrDefault(); if (Equals(userId, default(Guid))) { return Task.FromResult(default(User)); } User user = _userRepository.GetByKey(userId); return Task.FromResult(user); } #endregion #region Implementation of IUserClaimStore<User> /// <summary> /// Gets a list of <see cref="T:System.Security.Claims.Claim" />s to be belonging to the specified <paramref name="user" /> as an asynchronous operation. /// </summary> /// <param name="user">The role whose claims to retrieve.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// A <see cref="T:System.Threading.Tasks.Task`1" /> that represents the result of the asynchronous query, a list of <see cref="T:System.Security.Claims.Claim" />s. /// </returns> public Task<IList<Claim>> GetClaimsAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); IList<Claim> claims = _userClaimRepository.Query(m => m.UserId.Equals(user.ID)) .Select(m => new Claim(m.ClaimType, m.ClaimValue)).ToList(); return Task.FromResult(claims); } /// <summary>Add claims to a user as an asynchronous operation.</summary> /// <param name="user">The user to add the claim to.</param> /// <param name="claims">The collection of <see cref="T:System.Security.Claims.Claim" />s to add.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The task object representing the asynchronous operation.</returns> public async Task AddClaimsAsync(User user, IEnumerable<Claim> claims, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); UserClaim[] userClaims = claims.Select(m => new UserClaim() { UserId = user.ID, ClaimType = m.Type, ClaimValue = m.Value }).ToArray(); await _userClaimRepository.InsertAsync(userClaims); await _unitOfWork.CommitAsync(); } /// <summary> /// Replaces the given <paramref name="claim" /> on the specified <paramref name="user" /> with the <paramref name="newClaim" /> /// </summary> /// <param name="user">The user to replace the claim on.</param> /// <param name="claim">The claim to replace.</param> /// <param name="newClaim">The new claim to replace the existing <paramref name="claim" /> with.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The task object representing the asynchronous operation.</returns> public Task ReplaceClaimAsync(User user, Claim claim, Claim newClaim, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); List<UserClaim> userClaims = _userClaimRepository.Query(m => m.UserId.Equals(user.ID) && m.ClaimType == claim.Type && m.ClaimValue == claim.Value).ToList(); foreach (UserClaim userClaim in userClaims) { userClaim.ClaimType = newClaim.Type; userClaim.ClaimValue = newClaim.Value; } return Task.CompletedTask; } /// <summary> /// Removes the specified <paramref name="claims" /> from the given <paramref name="user" />. /// </summary> /// <param name="user">The user to remove the specified <paramref name="claims" /> from.</param> /// <param name="claims">A collection of <see cref="T:System.Security.Claims.Claim" />s to remove.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The task object representing the asynchronous operation.</returns> public async Task RemoveClaimsAsync(User user, IEnumerable<Claim> claims, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); _userClaimRepository.Remove(m => m.UserId.Equals(user.ID) && claims.Any(n => n.Type == m.ClaimType && n.Value == m.ClaimValue)); await _unitOfWork.CommitAsync(); } /// <summary> /// Returns a list of users who contain the specified <see cref="T:System.Security.Claims.Claim" />. /// </summary> /// <param name="claim">The claim to look for.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// A <see cref="T:System.Threading.Tasks.Task`1" /> that represents the result of the asynchronous query, a list of <typeparamref name="User" /> who /// contain the specified claim. /// </returns> public Task<IList<User>> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(claim, nameof(claim)); Guid[] userIds = _userClaimRepository.Query(m => m.ClaimType == claim.Type && m.ClaimValue == claim.Value) .Select(m => m.UserId).ToArray(); IList<User> users = _userRepository.Query(m => userIds.Contains(m.ID)).ToList(); return Task.FromResult(users); } #endregion #region Implementation of IUserPasswordStore<User> /// <summary> /// Sets the password hash for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose password hash to set.</param> /// <param name="passwordHash">The password hash to set.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetPasswordHashAsync(User user, string passwordHash, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); user.PasswordHash = passwordHash; return Task.CompletedTask; } /// <summary> /// Gets the password hash for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose password hash to retrieve.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, returning the password hash for the specified <paramref name="user" />.</returns> public Task<string> GetPasswordHashAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.PasswordHash); } /// <summary> /// Gets a flag indicating whether the specified <paramref name="user" /> has a password. /// </summary> /// <param name="user">The user to return a flag for, indicating whether they have a password or not.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, returning true if the specified <paramref name="user" /> has a password /// otherwise false. /// </returns> public Task<bool> HasPasswordAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(!string.IsNullOrEmpty(user.PasswordHash)); } #endregion #region Implementation of IUserSecurityStampStore<User> /// <summary> /// Sets the provided security <paramref name="stamp" /> for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose security stamp should be set.</param> /// <param name="stamp">The security stamp to set.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetSecurityStampAsync(User user, string stamp, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); user.SecurityStamp = stamp; //移除用户在线缓存 //OnlineUserCacheRemoveEventData eventData = new OnlineUserCacheRemoveEventData() { UserNames = new[] { user.UserName } }; //_eventBus.Publish(eventData); return Task.CompletedTask; } /// <summary> /// Get the security stamp for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose security stamp should be set.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the security stamp for the specified <paramref name="user" />.</returns> public Task<string> GetSecurityStampAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.SecurityStamp); } #endregion #region Implementation of IUserEmailStore<User> /// <summary> /// Sets the <paramref name="email" /> address for a <paramref name="user" />. /// </summary> /// <param name="user">The user whose email should be set.</param> /// <param name="email">The email to set.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The task object representing the asynchronous operation.</returns> public Task SetEmailAsync(User user, string email, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotNull(email, nameof(email)); user.Email = email; return Task.CompletedTask; } /// <summary> /// Gets the email address for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose email should be returned.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The task object containing the results of the asynchronous operation, the email address for the specified <paramref name="user" />.</returns> public Task<string> GetEmailAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.Email); } /// <summary> /// Gets a flag indicating whether the email address for the specified <paramref name="user" /> has been verified, true if the email address is verified otherwise /// false. /// </summary> /// <param name="user">The user whose email confirmation status should be returned.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The task object containing the results of the asynchronous operation, a flag indicating whether the email address for the specified <paramref name="user" /> /// has been confirmed or not. /// </returns> public Task<bool> GetEmailConfirmedAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.EmailConfirmed); } /// <summary> /// Sets the flag indicating whether the specified <paramref name="user" />'s email address has been confirmed or not. /// </summary> /// <param name="user">The user whose email confirmation status should be set.</param> /// <param name="confirmed">A flag indicating if the email address has been confirmed, true if the address is confirmed otherwise false.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The task object representing the asynchronous operation.</returns> public Task SetEmailConfirmedAsync(User user, bool confirmed, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); user.EmailConfirmed = true; return Task.CompletedTask; } /// <summary> /// Gets the user, if any, associated with the specified, normalized email address. /// </summary> /// <param name="normalizedEmail">The normalized email address to return the user for.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The task object containing the results of the asynchronous lookup operation, the user if any associated with the specified normalized email address. /// </returns> public Task<User> FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); User user = _userRepository.Query().FirstOrDefault(m => m.Email == normalizedEmail); return Task.FromResult(user); } /// <summary> /// Returns the normalized email for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose email address to retrieve.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The task object containing the results of the asynchronous lookup operation, the normalized email address if any associated with the specified user. /// </returns> public Task<string> GetNormalizedEmailAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.UserName); } /// <summary> /// Sets the normalized email for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose email address to set.</param> /// <param name="normalizedEmail">The normalized email to set for the specified <paramref name="user" />.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The task object representing the asynchronous operation.</returns> public Task SetNormalizedEmailAsync(User user, string normalizedEmail, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); user.Email = normalizedEmail; return Task.CompletedTask; } #endregion #region Implementation of IUserLockoutStore<User> /// <summary> /// Gets the last <see cref="T:System.DateTimeOffset" /> a user's last lockout expired, if any. /// Any time in the past should be indicates a user is not locked out. /// </summary> /// <param name="user">The user whose lockout date should be retrieved.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// A <see cref="T:System.Threading.Tasks.Task`1" /> that represents the result of the asynchronous query, a <see cref="T:System.DateTimeOffset" /> containing the last time /// a user's lockout expired, if any. /// </returns> public Task<DateTimeOffset?> GetLockoutEndDateAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.LockoutEnd); } /// <summary> /// Locks out a user until the specified end date has passed. Setting a end date in the past immediately unlocks a user. /// </summary> /// <param name="user">The user whose lockout date should be set.</param> /// <param name="lockoutEnd">The <see cref="T:System.DateTimeOffset" /> after which the <paramref name="user" />'s lockout should end.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetLockoutEndDateAsync(User user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); user.LockoutEnd = lockoutEnd; return Task.CompletedTask; } /// <summary> /// Records that a failed access has occurred, incrementing the failed access count. /// </summary> /// <param name="user">The user whose cancellation count should be incremented.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the incremented failed access count.</returns> public Task<int> IncrementAccessFailedCountAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); user.AccessFailedCount++; return Task.FromResult(user.AccessFailedCount); } /// <summary>Resets a user's failed access count.</summary> /// <param name="user">The user whose failed access count should be reset.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> /// <remarks>This is typically called after the account is successfully accessed.</remarks> public Task ResetAccessFailedCountAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); user.AccessFailedCount = 0; return Task.CompletedTask; } /// <summary> /// Retrieves the current failed access count for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose failed access count should be retrieved.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the failed access count.</returns> public Task<int> GetAccessFailedCountAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.AccessFailedCount); } /// <summary> /// Retrieves a flag indicating whether user lockout can enabled for the specified user. /// </summary> /// <param name="user">The user whose ability to be locked out should be returned.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, true if a user can be locked out, otherwise false. /// </returns> public Task<bool> GetLockoutEnabledAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); //TODO return Task.FromResult(true); } /// <summary> /// Set the flag indicating if the specified <paramref name="user" /> can be locked out. /// </summary> /// <param name="user">The user whose ability to be locked out should be set.</param> /// <param name="enabled">A flag indicating if lock out can be enabled for the specified <paramref name="user" />.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetLockoutEnabledAsync(User user, bool enabled, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); //user.LockoutEnabled = enabled; return Task.CompletedTask; } #endregion #region Implementation of IUserPhoneNumberStore<User> /// <summary> /// Sets the telephone number for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose telephone number should be set.</param> /// <param name="phoneNumber">The telephone number to set.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetPhoneNumberAsync(User user, string phoneNumber, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); user.PhoneNumber = phoneNumber; return Task.CompletedTask; } /// <summary> /// Gets the telephone number, if any, for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose telephone number should be retrieved.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the user's telephone number, if any.</returns> public Task<string> GetPhoneNumberAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.PhoneNumber); } /// <summary> /// Gets a flag indicating whether the specified <paramref name="user" />'s telephone number has been confirmed. /// </summary> /// <param name="user">The user to return a flag for, indicating whether their telephone number is confirmed.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, returning true if the specified <paramref name="user" /> has a confirmed /// telephone number otherwise false. /// </returns> public Task<bool> GetPhoneNumberConfirmedAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(user.PhoneNumberConfirmed); } /// <summary> /// Sets a flag indicating if the specified <paramref name="user" />'s phone number has been confirmed. /// </summary> /// <param name="user">The user whose telephone number confirmation status should be set.</param> /// <param name="confirmed">A flag indicating whether the user's telephone number has been confirmed.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetPhoneNumberConfirmedAsync(User user, bool confirmed, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); user.PhoneNumberConfirmed = confirmed; return Task.CompletedTask; } #endregion #region Implementation of IUserTwoFactorStore<User> /// <summary> /// Sets a flag indicating whether the specified <paramref name="user" /> has two factor authentication enabled or not, /// as an asynchronous operation. /// </summary> /// <param name="user">The user whose two factor authentication enabled status should be set.</param> /// <param name="enabled">A flag indicating whether the specified <paramref name="user" /> has two factor authentication enabled.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetTwoFactorEnabledAsync(User user, bool enabled, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); //user.TwoFactorEnabled = enabled; return Task.CompletedTask; } /// <summary> /// Returns a flag indicating whether the specified <paramref name="user" /> has two factor authentication enabled or not, /// as an asynchronous operation. /// </summary> /// <param name="user">The user whose two factor authentication enabled status should be set.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing a flag indicating whether the specified /// <paramref name="user" /> has two factor authentication enabled or not. /// </returns> public Task<bool> GetTwoFactorEnabledAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); return Task.FromResult(true/*user.TwoFactorEnabled*/); } #endregion #region Implementation of IUserAuthenticationTokenStore<User> /// <summary>Sets the token value for a particular user.</summary> /// <param name="user">The user.</param> /// <param name="loginProvider">The authentication provider for the token.</param> /// <param name="name">The name of the token.</param> /// <param name="value">The value of the token.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public async Task SetTokenAsync(User user, string loginProvider, string name, string value, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); UserToken token = _userTokenRepository.Query() .FirstOrDefault(m => m.ID.Equals(user.ID) && m.LoginProvider == loginProvider && m.Name == name); if (token == null) { token = new UserToken() { UserId = user.ID, LoginProvider = loginProvider, Name = name, Value = value }; await _userTokenRepository.InsertAsync(token); } else { token.Value = value; _userTokenRepository.Update(token); await _unitOfWork.CommitAsync(); } } /// <summary>Deletes a token for a user.</summary> /// <param name="user">The user.</param> /// <param name="loginProvider">The authentication provider for the token.</param> /// <param name="name">The name of the token.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public async Task RemoveTokenAsync(User user, string loginProvider, string name, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); _userTokenRepository.Remove(m => m.UserId.Equals(user.ID) && m.LoginProvider == loginProvider && m.Name == name); await _unitOfWork.CommitAsync(); } /// <summary>Returns the token value.</summary> /// <param name="user">The user.</param> /// <param name="loginProvider">The authentication provider for the token.</param> /// <param name="name">The name of the token.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task<string> GetTokenAsync(User user, string loginProvider, string name, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); string value = _userTokenRepository.Query(m => m.UserId.Equals(user.ID) && m.LoginProvider == loginProvider && m.Name == name) .Select(m => m.Value).FirstOrDefault(); return Task.FromResult(value); } #endregion #region Implementation of IUserAuthenticatorKeyStore<User> private const string InternalLoginProvider = "[AspNetUserStore]"; private const string AuthenticatorKeyTokenName = "AuthenticatorKey"; private const string RecoveryCodeTokenName = "RecoveryCodes"; /// <summary> /// Sets the authenticator key for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose authenticator key should be set.</param> /// <param name="key">The authenticator key to set.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetAuthenticatorKeyAsync(User user, string key, CancellationToken cancellationToken) { return SetTokenAsync(user, InternalLoginProvider, AuthenticatorKeyTokenName, key, cancellationToken); } /// <summary> /// Get the authenticator key for the specified <paramref name="user" />. /// </summary> /// <param name="user">The user whose security stamp should be set.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing the security stamp for the specified <paramref name="user" />.</returns> public Task<string> GetAuthenticatorKeyAsync(User user, CancellationToken cancellationToken) { return GetTokenAsync(user, InternalLoginProvider, AuthenticatorKeyTokenName, cancellationToken); } #endregion #region Implementation of IUserTwoFactorRecoveryCodeStore<User> /// <summary> /// Updates the recovery codes for the user while invalidating any previous recovery codes. /// </summary> /// <param name="user">The user to store new recovery codes for.</param> /// <param name="recoveryCodes">The new recovery codes for the user.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The new recovery codes for the user.</returns> public Task ReplaceCodesAsync(User user, IEnumerable<string> recoveryCodes, CancellationToken cancellationToken) { string mergedCodes = string.Join(";", recoveryCodes); return SetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, mergedCodes, cancellationToken); } /// <summary> /// Returns whether a recovery code is valid for a user. Note: recovery codes are only valid /// once, and will be invalid after use. /// </summary> /// <param name="user">The user who owns the recovery code.</param> /// <param name="code">The recovery code to use.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>True if the recovery code was found for the user.</returns> public async Task<bool> RedeemCodeAsync(User user, string code, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotNullOrEmpty(code, nameof(code)); string mergedCodes = await GetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, cancellationToken) ?? String.Empty; string[] splitCodes = mergedCodes.Split(';'); if (splitCodes.Contains(code)) { List<string> updatedCodes = new List<string>(splitCodes.Where(s => s != code)); await ReplaceCodesAsync(user, updatedCodes, cancellationToken); return true; } return false; } /// <summary> /// Returns how many recovery code are still valid for a user. /// </summary> /// <param name="user">The user who owns the recovery code.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The number of valid recovery codes for the user..</returns> public async Task<int> CountCodesAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); string mergedCodes = await GetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, cancellationToken); if (mergedCodes.Length > 0) { return mergedCodes.Split(';').Length; } return 0; } #endregion #region Implementation of IUserRoleStore<User> /// <summary> /// Add a the specified <paramref name="user" /> to the named role. /// </summary> /// <param name="user">The user to add to the named role.</param> /// <param name="normalizedRoleName">The name of the role to add the user to.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public async Task AddToRoleAsync(User user, string normalizedRoleName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotNullOrEmpty(normalizedRoleName, nameof(normalizedRoleName)); Guid roleId = _roleRepository.Query(m => m.Name == normalizedRoleName).Select(m => m.ID).FirstOrDefault(); if (Equals(roleId, default(Guid))) { throw new InvalidOperationException($"名称为“{normalizedRoleName}”的角色信息不存在"); } UserRole userRole = new UserRole() { RoleId = roleId, UserId = user.ID }; await _userRoleRepository.InsertAsync(userRole); await _unitOfWork.CommitAsync(); } /// <summary> /// Add a the specified <paramref name="user" /> from the named role. /// </summary> /// <param name="user">The user to remove the named role from.</param> /// <param name="normalizedRoleName">The name of the role to remove.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public async Task RemoveFromRoleAsync(User user, string normalizedRoleName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Check.NotNullOrEmpty(normalizedRoleName, nameof(normalizedRoleName)); Role role = _roleRepository.Query().FirstOrDefault(m => m.Name == normalizedRoleName); if (role == null) { throw new InvalidOperationException($"名称为“{normalizedRoleName}”的角色信息不存在"); } if (user.IsSystem) { throw new InvalidOperationException($"系统用户“{user.UserName}”的系统角色“{role.Name}”不能移除"); } _userRoleRepository.Remove(m => m.UserId.Equals(user.ID) && m.RoleId.Equals(role.ID)); await _unitOfWork.CommitAsync(); } /// <summary> /// Gets a list of role names the specified <paramref name="user" /> belongs to. /// </summary> /// <param name="user">The user whose role names to retrieve.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing a list of role names.</returns> public Task<IList<string>> GetRolesAsync(User user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); IList<string> list = new List<string>(); List<Guid> roleIds = _userRoleRepository.Query(m => m.UserId.Equals(user.ID)).Select(m => m.RoleId).ToList(); if (roleIds.Count == 0) { return Task.FromResult(list); } list = _roleRepository.Query(m => roleIds.Contains(m.ID)).Select(m => m.Name).ToList(); return Task.FromResult(list); } /// <summary> /// Returns a flag indicating whether the specified <paramref name="user" /> is a member of the give named role. /// </summary> /// <param name="user">The user whose role membership should be checked.</param> /// <param name="roleName">The name of the role to be checked.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing a flag indicating whether the specified <paramref name="user" /> is /// a member of the named role. /// </returns> public Task<bool> IsInRoleAsync(User user, string roleName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(user, nameof(user)); Guid roleId = _roleRepository.Query(m => m.Name == roleName).Select(m => m.ID).FirstOrDefault(); if (Equals(roleId, default(Guid))) { throw new InvalidOperationException($"名称为“{roleName}”的角色信息不存在"); } bool exist = _userRoleRepository.Query(m => m.UserId.Equals(user.ID) && m.RoleId.Equals(roleId)).Any(); return Task.FromResult(exist); } /// <summary> /// Returns a list of Users who are members of the named role. /// </summary> /// <param name="roleName">The name of the role whose membership should be returned.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation, containing a list of users who are in the named role. /// </returns> public Task<IList<User>> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNullOrEmpty(roleName, nameof(roleName)); Guid roleId = _roleRepository.Query(m => m.Name == roleName).Select(m => m.ID).FirstOrDefault(); if (Equals(roleId, default(Guid))) { throw new InvalidOperationException($"名称为“{roleName}”的角色信息不存在"); } List<Guid> userIds = _userRoleRepository.Query(m => m.RoleId.Equals(roleId)).Select(m => m.UserId).ToList(); IList<User> users = _userRepository.Query(m => userIds.Contains(m.ID)).ToList(); return Task.FromResult(users); } #endregion #region Other /// <summary> /// Converts the provided <paramref name="id"/> to a strongly typed key object. /// </summary> /// <param name="id">The id to convert.</param> /// <returns>An instance of <typeparamref name="Guid"/> representing the provided <paramref name="id"/>.</returns> public virtual Guid ConvertIdFromString(string id) { if (id == null) { return default(Guid); } return (Guid)TypeDescriptor.GetConverter(typeof(Guid)).ConvertFromInvariantString(id); } /// <summary> /// Converts the provided <paramref name="id"/> to its string representation. /// </summary> /// <param name="id">The id to convert.</param> /// <returns>An <see cref="string"/> representation of the provided <paramref name="id"/>.</returns> public virtual string ConvertIdToString(Guid id) { if (id.Equals(default(Guid))) { return null; } return id.ToString(); } /// <summary> /// 如果已释放,则抛出异常 /// </summary> protected void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name); } } #endregion }
这里我是通过uow获取的仓储对象,如果 看不明白,看这里 关于UOW模式 ,然后重定义其中的 增删删改查操作,,嗯,是的就是这么简单的。
同样的 RoleStore我们也是需要自定义的,所以也看下一下源码的实现:
看到了吧,这里就实现了两个接口,所以比UserStore实现要简单很多了,自定义实现如下(其中涉及的Role,RoleClaim依旧需要自定义,建议赋值 core identity中的):
public class RoleStore : IQueryableRoleStore<Role>, IRoleClaimStore<Role> { private bool _disposed; #region ctor public RoleStore(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; _roleRepository = _unitOfWork.Repository<Role, Guid>(); _roleClaimRepository = _unitOfWork.Repository<RoleClaim, Guid>(); } #endregion #region fields private readonly IUnitOfWork _unitOfWork; private readonly IRepository<Role, Guid> _roleRepository; private readonly IRepository<RoleClaim, Guid> _roleClaimRepository; #endregion #region Implementation of IDisposable /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary> public void Dispose() { _disposed = true; } #endregion #region Implementation of IQueryableRoleStore<Role> /// <summary> /// Returns an <see cref="T:System.Linq.IQueryable`1" /> collection of roles. /// </summary> /// <value>An <see cref="T:System.Linq.IQueryable`1" /> collection of roles.</value> public IQueryable<Role> Roles => _roleRepository.Query(); #endregion #region Implementation of IRoleStore<Role> /// <summary> /// Creates a new role in a store as an asynchronous operation. /// </summary> /// <param name="role">The role to create in the store.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>A <see cref="T:System.Threading.Tasks.Task`1" /> that represents the <see cref="T:Microsoft.AspNetCore.Identity.IdentityResult" /> of the asynchronous query.</returns> public async Task<IdentityResult> CreateAsync(Role role, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)); if (role.IsDefault) { string defaultRole = _roleRepository.Query(m => m.IsDefault).Select(m => m.Name).FirstOrDefault(); if (defaultRole != null) { return new IdentityResult().Failed($"系统中已存在默认角色“{defaultRole}”,不能重复添加"); } } await _roleRepository.InsertAsync(role); await _unitOfWork.CommitAsync(); return IdentityResult.Success; } /// <summary> /// Updates a role in a store as an asynchronous operation. /// </summary> /// <param name="role">The role to update in the store.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>A <see cref="T:System.Threading.Tasks.Task`1" /> that represents the <see cref="T:Microsoft.AspNetCore.Identity.IdentityResult" /> of the asynchronous query.</returns> public async Task<IdentityResult> UpdateAsync(Role role, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)); if (role.IsDefault) { var defaultRole = _roleRepository.Query(m => m.IsDefault).Select(m => new { m.ID, m.Name }).FirstOrDefault(); if (defaultRole != null && !defaultRole.ID.Equals(role.ID)) { return new IdentityResult().Failed($"系统中已存在默认角色“{defaultRole.Name}”,不能重复添加"); } } _roleRepository.Update(role); await _unitOfWork.CommitAsync(); return IdentityResult.Success; } /// <summary> /// Deletes a role from the store as an asynchronous operation. /// </summary> /// <param name="role">The role to delete from the store.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>A <see cref="T:System.Threading.Tasks.Task`1" /> that represents the <see cref="T:Microsoft.AspNetCore.Identity.IdentityResult" /> of the asynchronous query.</returns> public async Task<IdentityResult> DeleteAsync(Role role, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)); _roleRepository.Remove(role); await _unitOfWork.CommitAsync(); return IdentityResult.Success; } /// <summary> /// Gets the ID for a role from the store as an asynchronous operation. /// </summary> /// <param name="role">The role whose ID should be returned.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>A <see cref="T:System.Threading.Tasks.Task`1" /> that contains the ID of the role.</returns> public Task<string> GetRoleIdAsync(Role role, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)); return Task.FromResult(ConvertIdToString(role.ID)); } /// <summary> /// Gets the name of a role from the store as an asynchronous operation. /// </summary> /// <param name="role">The role whose name should be returned.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>A <see cref="T:System.Threading.Tasks.Task`1" /> that contains the name of the role.</returns> public Task<string> GetRoleNameAsync(Role role, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)); return Task.FromResult(role.Name); } /// <summary> /// Sets the name of a role in the store as an asynchronous operation. /// </summary> /// <param name="role">The role whose name should be set.</param> /// <param name="roleName">The name of the role.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetRoleNameAsync(Role role, string roleName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)); role.Name = roleName; return Task.CompletedTask; } /// <summary> /// Get a role's normalized name as an asynchronous operation. /// </summary> /// <param name="role">The role whose normalized name should be retrieved.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>A <see cref="T:System.Threading.Tasks.Task`1" /> that contains the name of the role.</returns> public Task<string> GetNormalizedRoleNameAsync(Role role, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)); return Task.FromResult(role.Name); } /// <summary> /// Set a role's normalized name as an asynchronous operation. /// </summary> /// <param name="role">The role whose normalized name should be set.</param> /// <param name="normalizedName">The normalized name to set</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous operation.</returns> public Task SetNormalizedRoleNameAsync(Role role, string normalizedName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)); role.Name = normalizedName; return Task.CompletedTask; } /// <summary> /// Finds the role who has the specified ID as an asynchronous operation. /// </summary> /// <param name="roleId">The role ID to look for.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>A <see cref="T:System.Threading.Tasks.Task`1" /> that result of the look up.</returns> public Task<Role> FindByIdAsync(string roleId, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Guid id = ConvertIdFromString(roleId); return Task.FromResult(_roleRepository.Query().FirstOrDefault(m => m.ID.Equals(id))); } /// <summary> /// Finds the role who has the specified normalized name as an asynchronous operation. /// </summary> /// <param name="normalizedRoleName">The normalized role name to look for.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>A <see cref="T:System.Threading.Tasks.Task`1" /> that result of the look up.</returns> public Task<Role> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); return Task.FromResult(_roleRepository.Query().FirstOrDefault(m => m.Name == normalizedRoleName)); } #endregion #region Implementation of IRoleClaimStore<Role> /// <summary> /// Gets a list of <see cref="T:System.Security.Claims.Claim" />s to be belonging to the specified <paramref name="role" /> as an asynchronous operation. /// </summary> /// <param name="role">The role whose claims to retrieve.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns> /// A <see cref="T:System.Threading.Tasks.Task`1" /> that represents the result of the asynchronous query, a list of <see cref="T:System.Security.Claims.Claim" />s. /// </returns> public Task<IList<Claim>> GetClaimsAsync(Role role, CancellationToken cancellationToken = new CancellationToken()) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)); IList<Claim> list = _roleClaimRepository.Query(m => m.RoleId.Equals(role.ID)).Select(n => new Claim(n.ClaimType, n.ClaimValue)).ToList(); return Task.FromResult(list); } /// <summary> /// Add a new claim to a role as an asynchronous operation. /// </summary> /// <param name="role">The role to add a claim to.</param> /// <param name="claim">The <see cref="T:System.Security.Claims.Claim" /> to add.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The task object representing the asynchronous operation.</returns> public async Task AddClaimAsync(Role role, Claim claim, CancellationToken cancellationToken = new CancellationToken()) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)); Check.NotNull(claim, nameof(claim)); RoleClaim roleClaim = new RoleClaim() { RoleId = role.ID, ClaimType = claim.Type, ClaimValue = claim.Value }; await _roleClaimRepository.InsertAsync(roleClaim); await _unitOfWork.CommitAsync(); } /// <summary> /// Remove a claim from a role as an asynchronous operation. /// </summary> /// <param name="role">The role to remove the claim from.</param> /// <param name="claim">The <see cref="T:System.Security.Claims.Claim" /> to remove.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param> /// <returns>The task object representing the asynchronous operation.</returns> public async Task RemoveClaimAsync(Role role, Claim claim, CancellationToken cancellationToken = new CancellationToken()) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); Check.NotNull(role, nameof(role)); Check.NotNull(claim, nameof(claim)); _roleClaimRepository.Remove(m => m.RoleId.Equals(role.ID) && m.ClaimValue == claim.Type && m.ClaimValue == claim.Value); await _unitOfWork.CommitAsync(); } #endregion /// <summary> /// Converts the provided <paramref name="id"/> to a strongly typed key object. /// </summary> /// <param name="id">The id to convert.</param> /// <returns>An instance of <typeparamref name="TRoleKey"/> representing the provided <paramref name="id"/>.</returns> public virtual Guid ConvertIdFromString(string id) { if (id == null) { return default(Guid); } return (Guid)TypeDescriptor.GetConverter(typeof(Guid)).ConvertFromInvariantString(id); } /// <summary> /// Converts the provided <paramref name="id"/> to its string representation. /// </summary> /// <param name="id">The id to convert.</param> /// <returns>An <see cref="string"/> representation of the provided <paramref name="id"/>.</returns> public virtual string ConvertIdToString(Guid id) { if (id.Equals(default(Guid))) { return null; } return id.ToString(); } /// <summary> /// 如果已释放,则抛出异常 /// </summary> protected void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name); } } }
这样我们就完成了 50% 的工作了,那么重点来了,怎么用起来?
services.AddScoped<IUserStore<User>, UserStore>();
services.AddScoped<IRoleStore<Role>, RoleStore>();
//services.AddScoped<SignInManager<User>>();
//services.AddScoped<UserManager<User>>();
//services.AddScoped<RoleManager<Role>>();
如此注入到 IOC 容器就好啦,嗯,我本很开心的觉得这样就完事了,但是错误不断,也就是上面红色(注释掉)的三行,解决一个有提示另一个没有注入而无法正常使用,其实,少了一步骤,你可能会说是 Configures中 app.UseAuthentication() 是吧,其实不是的,呵呵:
configureSercvices中继续加入
IdentityBuilder builder = services.AddIdentity<User, Role>(
(options) =>
{
options.SignIn.RequireConfirmedEmail = true;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(20);
}
);
builder.AddDefaultTokenProviders();
//关于 IdentityBuilder ,这里的两个类型User,Role,分别是我们自定义的 User实体和Role实体,此时我们再 app.UseAuthentication() 就可以了。
此时我们只需要在项目中如此注入即可:
public IdentityService( IUnitOfWork unitOfWork, SignInManager<User> signInManager, UserManager<User> userManager, ILogger<IdentityService> logger) { _unitOfWork = unitOfWork; _signInManager = signInManager; _userManager = userManager; _logger = logger; } #region fields private readonly IUnitOfWork _unitOfWork; private readonly SignInManager<User> _signInManager; private readonly UserManager<User> _userManager; private readonly ILogger<IdentityService> _logger;
这里的泛型参数 分别是我们自定义的 User和Role的模型。
这时候我们使用已有的上下文进行迁移,将得到如下结果:
看我们自定义的结果:
看我们的API的请求操作:
(注册 并启用邮箱验证):
这里使用自定义的个注册API模拟注册一个用户,然后模拟登录:
嗯,结果很理想,需要验证。
其他,可自定扩展。
下班,,,,