/// <summary> /// 反应Version变化 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="items"></param> /// <param name="lastUser"></param> /// <returns></returns> /// <exception cref="KVStoreException"></exception> public async Task UpdateAsync <T>(IEnumerable <T> items, string lastUser) where T : KVStoreEntity, new() { if (!items.Any()) { return; } ThrowIf.NotValid(items, nameof(items)); KVStoreEntityDef entityDef = EntityDefFactory.GetDef <T>(); try { IEnumerable <int> originalVersions = items.Select(t => t.Version).ToArray(); foreach (var t in items) { t.LastUser = lastUser; t.LastTime = TimeUtil.UtcNow; } await _engine.EntityUpdateAsync( entityDef.KVStoreName, entityDef.EntityType.FullName !, items.Select(t => GetEntityKey(t, entityDef)), items.Select(t => SerializeUtil.ToJson(t)), originalVersions).ConfigureAwait(false); //反应Version变化 foreach (var t in items) { t.Version++; } } catch (Exception ex) when(!(ex is KVStoreException)) { throw Exceptions.Unkown(entityDef.EntityType.FullName, entityDef.KVStoreName, items, ex); } }
/// <summary> /// AddOrUpdateByIdAsync /// </summary> /// <param name="database"></param> /// <param name="item"></param> /// <param name="transContext"></param> /// <returns></returns> /// <exception cref="DatabaseException"></exception> public static async Task AddOrUpdateByIdAsync <T>(this IDatabase database, T item, TransactionContext?transContext = null) where T : DatabaseEntity, new() { ThrowIf.NotValid(item, nameof(item)); EntityDef entityDef = EntityDefFactory.GetDef <T>() !; if (!entityDef.DatabaseWriteable) { throw Exceptions.NotWriteable(entityDef.EntityFullName, entityDef.DatabaseName); } try { item.LastTime = TimeUtil.UtcNow; if (item.Version < 0) { item.Version = 0; } var command = DbCommandBuilder.CreateAddOrUpdateCommand(database.EngineType, entityDef, item); using var reader = await database.DatabaseEngine.ExecuteCommandReaderAsync(transContext?.Transaction, entityDef.DatabaseName !, command, true).ConfigureAwait(false); IList <T> entities = reader.ToEntities <T>(database.EngineType, entityDef); T newItem = entities[0]; item.CreateTime = newItem.CreateTime; item.Version = newItem.Version; item.LastUser = newItem.LastUser; } catch (Exception ex) when(!(ex is DatabaseException)) { throw Exceptions.UnKown(entityDef.EntityFullName, SerializeUtil.ToJson(item), ex); } }
/// <summary> /// SignInAsync /// </summary> /// <param name="context"></param> /// <param name="lastUser"></param> /// <returns></returns> /// <exception cref="IdentityException"></exception> /// <exception cref="DatabaseException"></exception> /// <exception cref="KVStoreException"></exception> /// <exception cref="CacheException"></exception> public async Task <UserAccessResult> SignInAsync(SignInContext context, string lastUser) { ThrowIf.NotValid(context, nameof(context)); switch (context.SignInType) { case SignInType.ByMobileAndPassword: ThrowIf.NullOrEmpty(context.Mobile, "SignInContext.Mobile"); ThrowIf.NullOrEmpty(context.Password, "SignInContext.Password"); break; case SignInType.BySms: ThrowIf.NullOrEmpty(context.Mobile, "SignInContext.Mobile"); break; case SignInType.ByLoginNameAndPassword: ThrowIf.NullOrEmpty(context.LoginName, "SignInContext.LoginName"); ThrowIf.NullOrEmpty(context.Password, "SignInContext.Password"); break; default: break; } TransactionContext transactionContext = await _transaction.BeginTransactionAsync <SignInToken>().ConfigureAwait(false); try { //查询用户 User?user = context.SignInType switch { SignInType.ByLoginNameAndPassword => await _userRepo.GetByLoginNameAsync(context.LoginName !, transactionContext).ConfigureAwait(false), SignInType.BySms => await _userRepo.GetByMobileAsync(context.Mobile !, transactionContext).ConfigureAwait(false), SignInType.ByMobileAndPassword => await _userRepo.GetByMobileAsync(context.Mobile !, transactionContext).ConfigureAwait(false), _ => null }; //不存在,则新建用户 if (user == null && context.SignInType == SignInType.BySms) { user = await _identityService.CreateUserAsync(context.Mobile !, null, context.LoginName, context.Password, true, false, lastUser, transactionContext).ConfigureAwait(false); } if (user == null) { throw Exceptions.AuthorizationNotFound(signInContext: context); } UserLoginControl userLoginControl = await GetOrCreateUserLoginControlAsync(lastUser, user.Id).ConfigureAwait(false); //密码检查 if (context.SignInType == SignInType.ByMobileAndPassword || context.SignInType == SignInType.ByLoginNameAndPassword) { if (!PassowrdCheck(user, context.Password !)) { await OnSignInFailedAsync(userLoginControl, lastUser).ConfigureAwait(false); throw Exceptions.AuthorizationPasswordWrong(signInContext: context); } } //其他检查 await PreSignInCheckAsync(user, userLoginControl, lastUser).ConfigureAwait(false); //注销其他客户端 await DeleteSignInTokensAsync(user.Id, context.DeviceInfos.Idiom, context.LogOffType, context.DeviceInfos.Name, transactionContext).ConfigureAwait(false); //创建Token SignInToken signInToken = new SignInToken ( userId: user.Id, refreshToken: SecurityUtil.CreateUniqueToken(), expireAt: TimeUtil.UtcNow + (context.RememberMe ? _options.SignInOptions.RefreshTokenLongExpireTimeSpan : _options.SignInOptions.RefreshTokenShortExpireTimeSpan), deviceId: context.DeviceId, deviceVersion: context.DeviceVersion, deviceIp: context.DeviceIp, deviceName: context.DeviceInfos.Name, deviceModel: context.DeviceInfos.Model, deviceOSVersion: context.DeviceInfos.OSVersion, devicePlatform: context.DeviceInfos.Platform, deviceIdiom: context.DeviceInfos.Idiom, deviceType: context.DeviceInfos.Type ); await _signInTokenRepo.AddAsync(signInToken, lastUser, transactionContext).ConfigureAwait(false); //构造 Jwt string jwt = await ConstructJwtAsync(user, signInToken, context.SignToWhere, transactionContext).ConfigureAwait(false); UserAccessResult result = new UserAccessResult ( accessToken: jwt, refreshToken: signInToken.RefreshToken, currentUser: user ); await _transaction.CommitAsync(transactionContext).ConfigureAwait(false); return(result); } catch { await _transaction.RollbackAsync(transactionContext).ConfigureAwait(false); throw; } }
/// <summary> /// RefreshAccessTokenAsync /// </summary> /// <param name="context"></param> /// <param name="lastUser"></param> /// <returns></returns> /// <exception cref="IdentityException"></exception> /// <exception cref="DatabaseException"></exception> /// <exception cref="CacheException"></exception> public async Task <UserAccessResult> RefreshAccessTokenAsync(RefreshContext context, string lastUser) { ThrowIf.NotValid(context, nameof(context)); //解决并发涌入 using IDistributedLock distributedLock = await _lockManager.NoWaitLockAsync( nameof (RefreshAccessTokenAsync) + context.DeviceId, _options.RefreshIntervalTimeSpan, notUnlockWhenDispose : true).ConfigureAwait(false); if (!distributedLock.IsAcquired) { throw Exceptions.AuthorizationTooFrequent(context: context); } //AccessToken, Claims 验证 ClaimsPrincipal?claimsPrincipal = null; try { claimsPrincipal = JwtHelper.ValidateTokenWithoutLifeCheck(context.AccessToken, _options.OpenIdConnectConfiguration.Issuer, _issuerSigningKeys, _decryptionSecurityKey); } catch (Exception ex) { throw Exceptions.AuthorizationInvalideAccessToken(context: context, innerException: ex); } //TODO: 这里缺DeviceId验证. 放在了StartupUtil.cs中 if (claimsPrincipal == null) { //TODO: Black concern SigninToken by RefreshToken throw Exceptions.AuthorizationInvalideAccessToken(context: context); } if (claimsPrincipal.GetDeviceId() != context.DeviceId) { throw Exceptions.AuthorizationInvalideDeviceId(context: context); } long userId = claimsPrincipal.GetUserId().GetValueOrDefault(); if (userId <= 0) { throw Exceptions.AuthorizationInvalideUserId(context: context); } //SignInToken 验证 User? user; SignInToken? signInToken = null; TransactionContext transactionContext = await _transaction.BeginTransactionAsync <SignInToken>().ConfigureAwait(false); try { signInToken = await _signInTokenRepo.GetByConditionAsync( claimsPrincipal.GetSignInTokenId().GetValueOrDefault(), context.RefreshToken, context.DeviceId, userId, transactionContext).ConfigureAwait(false); if (signInToken == null || signInToken.Blacked) { throw Exceptions.AuthorizationNoTokenInStore(cause: "Refresh token error. signInToken not saved in db. "); } //验证SignInToken过期问题 if (signInToken.ExpireAt < TimeUtil.UtcNow) { throw Exceptions.AuthorizationRefreshTokenExpired(); } // User 信息变动验证 user = await _userRepo.GetByIdAsync(userId, transactionContext).ConfigureAwait(false); if (user == null || user.SecurityStamp != claimsPrincipal.GetUserSecurityStamp()) { throw Exceptions.AuthorizationUserSecurityStampChanged(cause: "Refresh token error. User SecurityStamp Changed."); } // 更新SignInToken signInToken.RefreshCount++; await _signInTokenRepo.UpdateAsync(signInToken, lastUser, transactionContext).ConfigureAwait(false); // 发布新的AccessToken string accessToken = await ConstructJwtAsync(user, signInToken, claimsPrincipal.GetAudience(), transactionContext).ConfigureAwait(false); await _transaction.CommitAsync(transactionContext).ConfigureAwait(false); return(new UserAccessResult(accessToken, context.RefreshToken, user)); } catch { await _transaction.RollbackAsync(transactionContext).ConfigureAwait(false); if (signInToken != null) { await _signInTokenRepo.DeleteAsync(signInToken, lastUser, null).ConfigureAwait(false); } throw; } }
//TODO: 做好详细的历史纪录,各个阶段都要打log。一有风吹草动,就立马删除SignInToken /// <returns>新的AccessToken</returns> public async Task <string> RefreshAccessTokenAsync(RefreshContext context, string lastUser) where User : User, new() where TUserClaim : UserClaim, new() where TRole : Role, new() where TRoleOfUser : RoleOfUser, new() { ThrowIf.NotValid(context); //解决并发涌入 using IDistributedLock distributedLock = await _lockManager.NoWaitLockAsync( nameof (RefreshAccessTokenAsync) + context.DeviceId, _options.RefreshIntervalTimeSpan).ConfigureAwait(false); if (!distributedLock.IsAcquired) { throw new AuthorizationException(ErrorCode.AuthorizationTooFrequent, $"Context:{SerializeUtil.ToJson(context)}"); } //AccessToken, Claims 验证 ClaimsPrincipal?claimsPrincipal = null; try { claimsPrincipal = ValidateTokenWithoutLifeCheck(context); } catch (Exception ex) { throw new AuthorizationException(ErrorCode.AuthorizationInvalideAccessToken, $"Context: {SerializeUtil.ToJson(context)}", ex); } //TODO: 这里缺DeviceId验证. 放在了StartupUtil.cs中 if (claimsPrincipal == null) { //TODO: Black concern SigninToken by RefreshToken throw new AuthorizationException(ErrorCode.AuthorizationInvalideAccessToken, $"Context: {SerializeUtil.ToJson(context)}"); } if (claimsPrincipal.GetDeviceId() != context.DeviceId) { throw new AuthorizationException(ErrorCode.AuthorizationInvalideDeviceId, $"Context: {SerializeUtil.ToJson(context)}"); } string?userGuid = claimsPrincipal.GetUserGuid(); if (string.IsNullOrEmpty(userGuid)) { throw new AuthorizationException(ErrorCode.AuthorizationInvalideUserGuid, $"Context: {SerializeUtil.ToJson(context)}"); } //SignInToken 验证 User? user; SignInToken?signInToken; //TransactionContext transactionContext = await _transaction.BeginTransactionAsync<SignInToken>().ConfigureAwait(false); try { signInToken = await _signInTokenBiz.GetAsync( claimsPrincipal.GetSignInTokenGuid(), context.RefreshToken, context.DeviceId, userGuid, ).ConfigureAwait(false); if (signInToken == null || signInToken.Blacked) { //await _database.RollbackAsync(transactionContext).ConfigureAwait(false); throw new AuthorizationException(ErrorCode.AuthorizationNoTokenInStore, $"Refresh token error. signInToken not saved in db. "); } //验证SignInToken过期问题 if (signInToken.ExpireAt < DateTimeOffset.UtcNow) { //await _transaction.RollbackAsync(transactionContext).ConfigureAwait(false); await BlackSignInTokenAsync(signInToken, lastUser).ConfigureAwait(false); throw new AuthorizationException(ErrorCode.AuthorizationRefreshTokenExpired, $"Refresh Token Expired."); } // User 信息变动验证 user = await _identityService.GetUserByUserGuidAsync(userGuid).ConfigureAwait(false); if (user == null || user.SecurityStamp != claimsPrincipal.GetUserSecurityStamp()) { //await _transaction.RollbackAsync(transactionContext).ConfigureAwait(false); await BlackSignInTokenAsync(signInToken, lastUser).ConfigureAwait(false); throw new AuthorizationException(ErrorCode.AuthorizationUserSecurityStampChanged, $"Refresh token error. User SecurityStamp Changed."); } // 更新SignInToken signInToken.RefreshCount++; await _signInTokenBiz.UpdateAsync(signInToken, lastUser, transactionContext).ConfigureAwait(false); await _transaction.CommitAsync(transactionContext).ConfigureAwait(false); } catch { await _transaction.RollbackAsync(transactionContext).ConfigureAwait(false); throw; } // 发布新的AccessToken return(await _jwtBuilder.BuildJwtAsync(user, signInToken, claimsPrincipal.GetAudience()).ConfigureAwait(false)); }
public async Task <SignInResult> SignInAsync(SignInContext context, string lastUser) { ThrowIf.NotValid(context); switch (context.SignInType) { case SignInType.ByMobileAndPassword: ThrowIf.NullOrEmpty(context.Mobile, "SignInContext.Mobile"); ThrowIf.NullOrEmpty(context.Password, "SignInContext.Password"); break; case SignInType.BySms: ThrowIf.NullOrEmpty(context.Mobile, "SignInContext.Mobile"); break; case SignInType.ByLoginNameAndPassword: ThrowIf.NullOrEmpty(context.LoginName, "SignInContext.LoginName"); ThrowIf.NullOrEmpty(context.Password, "SignInContext.Password"); break; default: break; } TransactionContext transactionContext = await _transaction.BeginTransactionAsync <SignInToken>().ConfigureAwait(false); try { //查询用户 User?user = context.SignInType switch { SignInType.ByLoginNameAndPassword => await _identityService.GetUserByLoginNameAsync(context.LoginName !).ConfigureAwait(false), SignInType.BySms => await _identityService.GetUserByMobileAsync(context.Mobile !).ConfigureAwait(false), SignInType.ByMobileAndPassword => await _identityService.GetUserByMobileAsync(context.Mobile !).ConfigureAwait(false), _ => null }; //不存在,则新建用户 bool newUserCreated = false; if (user == null && context.SignInType == SignInType.BySms) { user = await _identityService.CreateUserAsync( mobile : context.Mobile !, email : null, loginName : context.LoginName, password : context.Password, mobileConfirmed : true, emailConfirmed : false, lastUser : lastUser).ConfigureAwait(false); newUserCreated = true; } if (user == null) { throw new AuthorizationException(ErrorCode.AuthorizationNotFound, $"SignInContext:{SerializeUtil.ToJson(context)}"); } //密码检查 if (context.SignInType == SignInType.ByMobileAndPassword || context.SignInType == SignInType.ByLoginNameAndPassword) { if (!PassowrdCheck(user, context.Password !)) { await OnPasswordCheckFailedAsync(user, lastUser).ConfigureAwait(false); throw new AuthorizationException(ErrorCode.AuthorizationPasswordWrong, $"SignInContext:{SerializeUtil.ToJson(context)}"); } } //其他检查 await PreSignInCheckAsync(user, lastUser).ConfigureAwait(false); //注销其他客户端 await _signInTokenBiz.DeleteByLogOffTypeAsync(user.Guid, context.DeviceInfos.Idiom, context.LogOffType, context.DeviceInfos.Name, transactionContext).ConfigureAwait(false); //创建Token SignInToken userToken = await _signInTokenBiz.CreateAsync( user.Guid, context.DeviceId, context.DeviceInfos, context.DeviceVersion, //context.DeviceAddress, context.DeviceIp, context.RememberMe?_options.SignInOptions.RefreshTokenLongExpireTimeSpan : _options.SignInOptions.RefreshTokenShortExpireTimeSpan, lastUser, transactionContext).ConfigureAwait(false); //构造 Jwt IEnumerable <Claim> claims = GetClaims(user, transactionContext); SignInResult result = new SignInResult ( accessToken: await JwtHelper.BuildJwt(user, userToken, context.SignToWhere).ConfigureAwait(false), refreshToken: userToken.RefreshToken, newUserCreated: newUserCreated, currentUser: user ); await _transaction.CommitAsync(transactionContext).ConfigureAwait(false); return(result); } catch { await _transaction.RollbackAsync(transactionContext).ConfigureAwait(false); throw; } }
/// <summary> /// SignInAsync /// </summary> /// <param name="context"></param> /// <returns></returns> /// <exception cref="HB.Framework.Common.ValidateErrorException"></exception> /// <exception cref="HB.Component.Authorization.AuthorizationException"></exception> /// <exception cref="DatabaseException"></exception> public async Task <SignInResult> SignInAsync <TUser, TUserClaim, TRole, TRoleOfUser>(SignInContext context) where TUser : IdenityUser, new() where TUserClaim : IdentityUserClaim, new() where TRole : IdentityRole, new() where TRoleOfUser : IdentityRoleOfUser, new() { ThrowIf.NotValid(context); switch (context.SignInType) { case SignInType.ByMobileAndPassword: ThrowIf.NullOrEmpty(context.Mobile, "SignInContext.Mobile"); ThrowIf.NullOrEmpty(context.Password, "SignInContext.Password"); break; case SignInType.BySms: ThrowIf.NullOrEmpty(context.Mobile, "SignInContext.Mobile"); break; case SignInType.ByLoginNameAndPassword: ThrowIf.NullOrEmpty(context.LoginName, "SignInContext.LoginName"); ThrowIf.NullOrEmpty(context.Password, "SignInContext.Password"); break; default: break; } TransactionContext transactionContext = await _database.BeginTransactionAsync <SignInToken>(IsolationLevel.ReadCommitted).ConfigureAwait(false); try { //查询用户 TUser?user = context.SignInType switch { SignInType.ByLoginNameAndPassword => await _identityService.GetUserByLoginNameAsync <TUser>(context.LoginName !).ConfigureAwait(false), SignInType.BySms => await _identityService.GetUserByMobileAsync <TUser>(context.Mobile !).ConfigureAwait(false), SignInType.ByMobileAndPassword => await _identityService.GetUserByMobileAsync <TUser>(context.Mobile !).ConfigureAwait(false), _ => null }; //不存在,则新建用户 bool newUserCreated = false; if (user == null && context.SignInType == SignInType.BySms) { user = await _identityService.CreateUserByMobileAsync <TUser>(context.Mobile !, context.LoginName, context.Password, true).ConfigureAwait(false); newUserCreated = true; } if (user == null) { throw new AuthorizationException(ErrorCode.AuthorizationNotFound, $"SignInContext:{SerializeUtil.ToJson(context)}"); } //密码检查 if (context.SignInType == SignInType.ByMobileAndPassword || context.SignInType == SignInType.ByLoginNameAndPassword) { if (!PassowrdCheck(user, context.Password !)) { await OnPasswordCheckFailedAsync(user).ConfigureAwait(false); throw new AuthorizationException(ErrorCode.AuthorizationPasswordWrong, $"SignInContext:{SerializeUtil.ToJson(context)}"); } } //其他检查 await PreSignInCheckAsync(user).ConfigureAwait(false); //注销其他客户端 DeviceType clientType = DeviceTypeChecker.Check(context.DeviceType); if (clientType != DeviceType.Web && _signInOptions.AllowOnlyOneAppClient) { await _signInTokenBiz.DeleteAppClientTokenByUserGuidAsync(user.Guid, transactionContext).ConfigureAwait(false); } //创建Token SignInToken userToken = await _signInTokenBiz.CreateAsync( user.Guid, context.DeviceId, clientType.ToString(), context.DeviceVersion, //context.DeviceAddress, context.DeviceIp, context.RememberMe?_signInOptions.RefreshTokenLongExpireTimeSpan : _signInOptions.RefreshTokenShortExpireTimeSpan, transactionContext).ConfigureAwait(false); await _database.CommitAsync(transactionContext).ConfigureAwait(false); //构造 Jwt SignInResult result = new SignInResult ( accessToken: await _jwtBuilder.BuildJwtAsync <TUserClaim, TRole, TRoleOfUser>(user, userToken, context.SignToWhere).ConfigureAwait(false), refreshToken: userToken.RefreshToken, newUserCreated: newUserCreated, currentUser: user ); return(result); } catch { await _database.RollbackAsync(transactionContext).ConfigureAwait(false); throw; } }