live-forum/server/webapi/LiveForum/LiveForum.Service/Auth/LoginService.cs
zpc 442f32a043
All checks were successful
continuous-integration/drone/push Build is passing
feat(deployment): add v1.2.0 database scripts and improve Docker/auth configuration
- Add v1.2.0 complete database upgrade scripts for business and admin databases
- Enhance WeChat login logic to handle OpenId reassociation when user logs in via phone number
- Add UpdatedAt timestamp update in WeChat login record updates
- Configure Docker container permissions for AgileConfig cache file writes
- Add Caddy image tagging to CI-CD deployment documentation
- Update frontend Config.js for deployment configuration management
2026-03-26 22:10:57 +08:00

778 lines
32 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using FreeSql;
using LiveForum.Code.ExceptionExtend;
using LiveForum.Code.JwtInfrastructure;
using LiveForum.Code.JwtInfrastructure.Interface;
using LiveForum.Code.Utility;
using LiveForum.IService.Auth;
using LiveForum.IService.Users;
using LiveForum.Model;
using LiveForum.Model.Dto.Others;
using LiveForum.Model.Enum;
using LiveForum.Model.Enum.Users;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using SystemConvert = System.Convert;
namespace LiveForum.Service.Auth
{
/// <summary>
/// 登录服务
/// </summary>
public class LoginService : ILoginService
{
private readonly IJwtAuthManager _jwtAuthManager;
private readonly IBaseRepository<T_Users> _userRepository;
private readonly IBaseRepository<T_UserTokens> _userTokensRepository;
private readonly IBaseRepository<T_WechatMiniProgramLogins> _wechatMiniProgramLoginsRepository;
private readonly IBaseRepository<T_AccountPasswordLogins> _accountPasswordLoginsRepository;
private readonly JwtUserInfoModel _userInfoModel;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IOptionsSnapshot<AppSettings> _appSettingsSnapshot;
private readonly IUserInfoService _userInfoService;
private readonly IBaseRepository<T_IdentityGroups> _identityGroupsRepository;
private readonly IBaseRepository<T_UserIdentityGroups> _userIdentityGroupsRepository;
/// <summary>
/// 注意,要先创建私有对象,在构造函数中进行赋值
/// </summary>
/// <param name="jwtAuthManager">jwt管理对象</param>
/// <param name="userRepository">用户仓储类</param>
/// <param name="userTokensRepository">用户token仓储类</param>
/// <param name="wechatMiniProgramLoginsRepository">微信小程序登录仓储类</param>
/// <param name="accountPasswordLoginsRepository">账号密码登录仓储类</param>
/// <param name="userInfoModel">在验证jwt的时候进行</param>
/// <param name="httpContextAccessor">HTTP上下文访问器</param>
/// <param name="appSettingsSnapshot">应用配置快照(支持配置热更新)</param>
/// <param name="userInfoService">用户信息服务</param>
/// <param name="identityGroupsRepository">身份组仓储类</param>
/// <param name="userIdentityGroupsRepository">用户身份组关联仓储类</param>
public LoginService(IJwtAuthManager jwtAuthManager,
IBaseRepository<T_Users> userRepository,
IBaseRepository<T_UserTokens> userTokensRepository,
IBaseRepository<T_WechatMiniProgramLogins> wechatMiniProgramLoginsRepository,
IBaseRepository<T_AccountPasswordLogins> accountPasswordLoginsRepository,
JwtUserInfoModel userInfoModel,
IHttpContextAccessor httpContextAccessor,
IOptionsSnapshot<AppSettings> appSettingsSnapshot,
IUserInfoService userInfoService,
IBaseRepository<T_IdentityGroups> identityGroupsRepository,
IBaseRepository<T_UserIdentityGroups> userIdentityGroupsRepository)
{
_jwtAuthManager = jwtAuthManager;
_userRepository = userRepository;
_userTokensRepository = userTokensRepository;
_wechatMiniProgramLoginsRepository = wechatMiniProgramLoginsRepository;
_accountPasswordLoginsRepository = accountPasswordLoginsRepository;
_userInfoModel = userInfoModel;
_httpContextAccessor = httpContextAccessor;
_appSettingsSnapshot = appSettingsSnapshot;
_userInfoService = userInfoService;
_identityGroupsRepository = identityGroupsRepository;
_userIdentityGroupsRepository = userIdentityGroupsRepository;
}
/// <summary>
/// 微信小程序登录
/// 实现步奏:
/// 1. 根据 openId 去查询T_WechatMiniProgramLogins用户是否存在
/// 2. 如果存在获取对应的用户信息生成JwtAccessToken返回生成的jwt要看一下JwtUserInfoModel实体是否能满足
/// 3. 如果不存在创建用户并保存T_WechatMiniProgramLogins记录然后生成JwtAccessToken返回
/// 4. 将生成的token保存到T_UserTokens中
/// 5. 返回JwtAccessToken
/// </summary>
/// <param name="openId"></param>
/// <param name="sessionKey"></param>
/// <param name="unionId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<JwtAccessToken> WechatMpLogin(string openId, string sessionKey = "", string unionId = "")
{
if (string.IsNullOrEmpty(openId))
{
throw new ArgumentNullException(nameof(openId), "openId 不能为空");
}
var now = DateTime.Now;
var clientIp = _httpContextAccessor.GetClientIpAddress();
T_WechatMiniProgramLogins wechatLogin;
T_Users user;
// 1. 根据 openId 查询微信小程序登录记录
wechatLogin = await _wechatMiniProgramLoginsRepository.Select
.Where(x => x.OpenId == openId)
.FirstAsync();
if (wechatLogin != null)
{
// 2. 如果存在,获取对应的用户信息
user = await _userRepository.Select
.Where(x => x.Id == wechatLogin.UserId)
.FirstAsync();
if (user == null)
{
// 用户已被删除(如管理后台删除),重新创建用户
var appSettings1 = _appSettingsSnapshot.Value;
var defaultNickName1 = string.IsNullOrEmpty(appSettings1.UserDefaultName)
? "用户"
: appSettings1.UserDefaultName;
var defaultAvatar1 = string.IsNullOrEmpty(appSettings1.UserDefaultIcon)
? ""
: appSettings1.UserDefaultIcon;
user = new T_Users
{
NickName = $"{defaultNickName1}_{openId.Substring(0, 8)}",
Avatar = defaultAvatar1,
Experience = 0,
LevelId = 1,
Status = UserStatusEnum.Normal,
CertifiedType = 0, // 0表示未认证
CertifiedStatus = CertifiedStatusEnum.,
IsCertified = false,
IsVip = false,
UID = await _userInfoService.GenerateUniqueUIDAsync(),
CreatedAt = now,
UpdatedAt = now,
RegisterIp = clientIp,
LastLoginIp = clientIp,
LastLoginTime = now
};
await _userRepository.InsertAsync(user);
// 新用户自动关联默认身份组
await AssignDefaultIdentityGroupAsync(user.Id);
// 更新微信登录记录关联到新用户
wechatLogin.UserId = user.Id;
}
// 更新最后登录时间和IP
wechatLogin.LastLoginTime = now;
wechatLogin.LastLoginIp = clientIp;
await _wechatMiniProgramLoginsRepository.UpdateAsync(wechatLogin);
// 更新用户最后登录信息
user.LastLoginTime = now;
user.LastLoginIp = clientIp;
await _userRepository.UpdateAsync(user);
}
else
{
// 3. 如果不存在,创建新用户
// 使用配置中的默认头像和昵称(从 IOptionsSnapshot 获取最新配置)
var appSettings = _appSettingsSnapshot.Value;
var defaultNickName = string.IsNullOrEmpty(appSettings.UserDefaultName)
? "用户"
: appSettings.UserDefaultName;
var defaultAvatar = string.IsNullOrEmpty(appSettings.UserDefaultIcon)
? ""
: appSettings.UserDefaultIcon;
user = new T_Users
{
NickName = $"{defaultNickName}_{openId.Substring(0, 8)}",
Avatar = defaultAvatar,
Experience = 0,
LevelId = 1,
Status = UserStatusEnum.Normal,
CertifiedType = 0, // 0表示未认证
CertifiedStatus = CertifiedStatusEnum.,
IsCertified = false,
IsVip = false,
UID = await _userInfoService.GenerateUniqueUIDAsync(),
CreatedAt = now,
UpdatedAt = now,
RegisterIp = clientIp,
LastLoginIp = clientIp,
LastLoginTime = now
};
await _userRepository.InsertAsync(user);
// 新用户自动关联默认身份组
await AssignDefaultIdentityGroupAsync(user.Id);
// 创建微信登录记录
wechatLogin = new T_WechatMiniProgramLogins
{
OpenId = openId,
SessionKey = sessionKey,
UnionId = unionId ?? "",
UserId = user.Id,
LastLoginIp = clientIp,
LastLoginTime = now,
CreatedAt = now,
UpdatedAt = now,
};
await _wechatMiniProgramLoginsRepository.InsertAsync(wechatLogin);
}
// 4. 生成JWT Token
var claims = new List<Claim>
{
new Claim("userId", user.Id.ToString()),
new Claim("loginType",((int)LoginTypeEnum.WechatLogin).ToString())
};
var jwtToken = await _jwtAuthManager.GenerateTokensAsync(user.Id.ToString(), claims, now);
// 3. 删除T_UserTokens中的token信息
await _userTokensRepository.Select
.Where(x => x.UserId == user.Id).ToDelete().ExecuteDeletedAsync();
// 5. 保存Token到数据库
var userToken = new T_UserTokens
{
UserId = user.Id,
AccessToken = jwtToken.Token,
LoginType = LoginTypeEnum.WechatLogin,
LoginId = wechatLogin.Id,
ClientIp = clientIp,
UserAgent = _httpContextAccessor.GetUserAgent(),
ExpiresAt = jwtToken.ExpireAt,
TokenType = "Bearer",
CreatedAt = now,
UpdatedAt = now
};
await _userTokensRepository.InsertAsync(userToken);
return jwtToken;
}
/// <summary>
/// 刷新token
/// 1.根据注入的userInfoModel对象获取当前用户信息
/// 2.生成新的JwtAccessToken
/// 3.更新T_UserTokens中的token信息
/// 4.旧的token作废,去除redis缓存RemoveTokenByUserNameAsync
/// 5.返回新的JwtAccessToken
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<JwtAccessToken> RefreshToken()
{
if (_userInfoModel == null || string.IsNullOrEmpty(_userInfoModel.UserName))
{
throw new Exception("用户未登录");
}
var now = DateTime.Now;
// 1. 根据注入的userInfoModel对象获取当前用户信息
if (!long.TryParse(_userInfoModel.UserName, out long userId))
{
throw new Exception("无效的用户ID");
}
var user = await _userRepository.Select
.Where(x => x.Id == userId)
.FirstAsync();
if (user == null)
{
throw new Exception("用户不存在");
}
//if (_userInfoModel.ExpireAt < DateTime.Now.AddDays(3))
//{
//}
// 2. 生成新的JwtAccessToken
var claims = new List<Claim>
{
new Claim("userId", user.Id.ToString()),
new Claim("loginType",_userInfoModel.LoginType.ToString())
};
var newJwtToken = await _jwtAuthManager.GenerateTokensAsync(user.Id.ToString(), claims, now);
// 3. 删除T_UserTokens中的token信息
await _userTokensRepository.Select
.Where(x => x.UserId == userId).ToDelete().ExecuteDeletedAsync();
// 3. 更新T_UserTokens中的token信息将旧的token标记为无效插入新token
//var oldTokens = await _userTokensRepository.Select
// .Where(x => x.UserId == userId)
// .ToListAsync();
//foreach (var oldToken in oldTokens)
//{
// oldToken.UpdatedAt = now;
// await _userTokensRepository.UpdateAsync(oldToken);
//}
// 插入新的token记录
var clientIp = _httpContextAccessor.GetClientIpAddress();
var newUserToken = new T_UserTokens
{
UserId = user.Id,
AccessToken = newJwtToken.Token,
LoginType = (LoginTypeEnum)_userInfoModel.LoginType, // 可以根据实际情况调整
ClientIp = clientIp,
UserAgent = _httpContextAccessor.GetUserAgent(),
ExpiresAt = newJwtToken.ExpireAt,
TokenType = "Bearer",
CreatedAt = now,
UpdatedAt = now,
TokenMD5 = newJwtToken.Token.ToMD5()
};
await _userTokensRepository.InsertAsync(newUserToken);
// 4. 旧的token作废,去除redis缓存 生成的时候好像会覆盖掉
//await _jwtAuthManager.RemoveTokenByUserNameAsync(_userInfoModel.UserName);
// 5. 返回新的JwtAccessToken
return newJwtToken;
}
/// <summary>
/// 账号密码登录
/// </summary>
/// <param name="account">账号(用户名/手机号/邮箱)</param>
/// <param name="password">密码</param>
/// <param name="accountType">账号类型1-用户名2-手机号3-邮箱</param>
/// <returns></returns>
public async Task<JwtAccessToken> AccountLogin(string account, string password, int accountType = 1)
{
// 1. 根据账号查找登录记录
var loginRecord = await _accountPasswordLoginsRepository.Select
.Where(x => x.LoginAccount == account && x.AccountType == accountType && !x.IsDeleted)
.FirstAsync();
if (loginRecord == null)
{
throw new LoginErrorException("账号不存在或密码错误");
}
// 2. 验证密码
if (!VerifyPassword(password, loginRecord.PasswordHash, loginRecord.PasswordSalt))
{
throw new LoginErrorException("账号不存在或密码错误");
}
// 3. 获取用户信息
var user = await _userRepository.Select.Where(x => x.Id == loginRecord.UserId).FirstAsync();
if (user == null || user.Status != UserStatusEnum.Normal)
{
throw new LoginErrorException("用户状态异常");
}
// 4. 更新登录信息
var clientIp = _httpContextAccessor.GetClientIpAddress(); ;
loginRecord.LastLoginTime = DateTime.Now;
loginRecord.LastLoginIp = clientIp;
await _accountPasswordLoginsRepository.UpdateAsync(loginRecord);
// 5. 更新用户最后登录信息
user.LastLoginTime = DateTime.Now;
user.LastLoginIp = clientIp;
await _userRepository.UpdateAsync(user);
// 6. 生成JWT Token
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.NickName),
new Claim("loginType", "2"), // 2-账号密码登录
new Claim("userId", loginRecord.Id.ToString())
};
var jwtToken = await _jwtAuthManager.GenerateTokensAsync(user.Id.ToString(), claims.ToList(), DateTime.Now);
// 3. 删除T_UserTokens中的token信息
await _userTokensRepository.Select
.Where(x => x.UserId == user.Id).ToDelete().ExecuteDeletedAsync();
// 7. 保存Token到数据库
var userToken = new T_UserTokens
{
UserId = user.Id,
AccessToken = jwtToken.Token,
TokenType = "Bearer",
ExpiresAt = jwtToken.ExpireAt,
LoginType = LoginTypeEnum.AccountPasswordLogin, // 2-账号密码登录
LoginId = loginRecord.Id,
ClientIp = clientIp,
UserAgent = _httpContextAccessor.GetUserAgent(),
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
TokenMD5 = jwtToken.Token.ToMD5()
};
await _userTokensRepository.InsertAsync(userToken);
return jwtToken;
}
/// <summary>
/// 账号密码注册
/// </summary>
/// <param name="loginAccount">登录账号(用户名/手机号/邮箱)</param>
/// <param name="accountType">账号类型1-用户名2-手机号3-邮箱</param>
/// <param name="password">密码</param>
/// <param name="nickName">昵称</param>
/// <param name="verifyCode">验证码(手机号/邮箱注册时必填)</param>
/// <returns></returns>
public async Task<JwtAccessToken> AccountRegister(string loginAccount, int accountType, string password, string nickName, string verifyCode = "")
{
// 1. 验证账号是否已存在
var existingLogin = await _accountPasswordLoginsRepository.Select
.Where(x => x.LoginAccount == loginAccount && x.AccountType == accountType && !x.IsDeleted)
.FirstAsync();
if (existingLogin != null)
{
throw new LoginErrorException("该账号已存在");
}
// 2. 验证验证码(如果需要)
if (accountType == 2 || accountType == 3) // 手机号或邮箱注册
{
if (string.IsNullOrEmpty(verifyCode))
{
throw new LoginErrorException("验证码不能为空");
}
// TODO: 实现验证码验证逻辑
}
// 3. 密码加密
var (passwordHash, passwordSalt) = HashPassword(password);
// 4. 创建用户记录
var clientIp = _httpContextAccessor.GetClientIpAddress(); ;
var now = DateTime.Now;
// 使用配置中的默认头像(从 IOptionsSnapshot 获取最新配置)
var appSettings = _appSettingsSnapshot.Value;
var defaultAvatar = string.IsNullOrEmpty(appSettings.UserDefaultIcon)
? ""
: appSettings.UserDefaultIcon;
var user = new T_Users
{
NickName = nickName,
Avatar = defaultAvatar,
LevelId = 1, // 默认等级
Experience = 0,
Status = UserStatusEnum.Normal,
CertifiedType = 0, // 0表示未认证
CertifiedStatus = CertifiedStatusEnum.,
IsCertified = false,
IsVip = false,
UID = await _userInfoService.GenerateUniqueUIDAsync(),
RegisterIp = clientIp,
LastLoginIp = clientIp,
LastLoginTime = now,
CreatedAt = now,
UpdatedAt = now
};
await _userRepository.InsertAsync(user);
// 新用户自动关联默认身份组
await AssignDefaultIdentityGroupAsync(user.Id);
// 5. 创建登录记录
var loginRecord = new T_AccountPasswordLogins
{
UserId = user.Id,
LoginAccount = loginAccount,
AccountType = (byte)accountType,
PasswordHash = passwordHash,
PasswordSalt = passwordSalt,
IsPrimary = true, // 第一个登录方式为主登录方式
CreatedAt = now,
UpdatedAt = now
};
await _accountPasswordLoginsRepository.InsertAsync(loginRecord);
// 6. 生成JWT Token
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.NickName),
new Claim("loginType", "2"), // 2-账号密码登录
new Claim("userId", user.Id.ToString())
};
var jwtToken = await _jwtAuthManager.GenerateTokensAsync(user.Id.ToString(), claims.ToList(), DateTime.Now);
// 7. 保存Token到数据库
var userToken = new T_UserTokens
{
UserId = user.Id,
AccessToken = jwtToken.Token,
TokenType = "Bearer",
ExpiresAt = jwtToken.ExpireAt,
LoginType = LoginTypeEnum.AccountPasswordLogin, // 2-账号密码登录
LoginId = loginRecord.Id,
ClientIp = clientIp,
UserAgent = _httpContextAccessor.GetUserAgent(),
CreatedAt = now,
UpdatedAt = now,
TokenMD5 = jwtToken.Token.ToMD5()
};
await _userTokensRepository.InsertAsync(userToken);
return jwtToken;
}
/// <summary>
/// 微信手机号登录
/// 用户匹配逻辑:手机号 → openId → 新用户
/// </summary>
public async Task<JwtAccessToken> WechatPhoneLogin(string openId, string phoneNumber, string sessionKey = "", string unionId = "")
{
if (string.IsNullOrEmpty(openId)) throw new ArgumentNullException(nameof(openId));
if (string.IsNullOrEmpty(phoneNumber)) throw new ArgumentNullException(nameof(phoneNumber));
var now = DateTime.Now;
var clientIp = _httpContextAccessor.GetClientIpAddress();
T_Users user = null;
T_WechatMiniProgramLogins wechatLogin = null;
// 1. 根据手机号查询用户
user = await _userRepository.Select
.Where(x => x.PhoneNumber == phoneNumber)
.FirstAsync();
if (user != null)
{
// 找到手机号用户,确保绑定/更新 OpenID
// 先按 UserId 查当前用户的微信记录
wechatLogin = await _wechatMiniProgramLoginsRepository.Select
.Where(x => x.UserId == user.Id)
.FirstAsync();
if (wechatLogin == null)
{
// 当前用户没有微信记录,检查该 OpenId 是否已被其他用户占用
var existingByOpenId = await _wechatMiniProgramLoginsRepository.Select
.Where(x => x.OpenId == openId)
.FirstAsync();
if (existingByOpenId != null)
{
// OpenId 已存在,将其重新关联到当前手机号用户
existingByOpenId.UserId = user.Id;
existingByOpenId.SessionKey = sessionKey;
existingByOpenId.LastLoginTime = now;
existingByOpenId.LastLoginIp = clientIp;
existingByOpenId.UpdatedAt = now;
await _wechatMiniProgramLoginsRepository.UpdateAsync(existingByOpenId);
wechatLogin = existingByOpenId;
}
else
{
wechatLogin = new T_WechatMiniProgramLogins
{
OpenId = openId,
SessionKey = sessionKey,
UnionId = unionId ?? "",
UserId = user.Id,
LastLoginIp = clientIp,
LastLoginTime = now,
CreatedAt = now,
UpdatedAt = now,
};
await _wechatMiniProgramLoginsRepository.InsertAsync(wechatLogin);
}
}
else
{
wechatLogin.OpenId = openId;
wechatLogin.SessionKey = sessionKey;
wechatLogin.LastLoginTime = now;
wechatLogin.LastLoginIp = clientIp;
wechatLogin.UpdatedAt = now;
await _wechatMiniProgramLoginsRepository.UpdateAsync(wechatLogin);
}
}
else
{
// 2. 根据 OpenID 查询
wechatLogin = await _wechatMiniProgramLoginsRepository.Select
.Where(x => x.OpenId == openId)
.FirstAsync();
if (wechatLogin != null)
{
user = await _userRepository.Select
.Where(x => x.Id == wechatLogin.UserId)
.FirstAsync();
if (user != null)
{
// 绑定手机号
user.PhoneNumber = phoneNumber;
await _userRepository.UpdateAsync(user);
wechatLogin.SessionKey = sessionKey;
wechatLogin.LastLoginTime = now;
wechatLogin.LastLoginIp = clientIp;
await _wechatMiniProgramLoginsRepository.UpdateAsync(wechatLogin);
}
}
}
// 3. 新用户注册
if (user == null)
{
var appSettings = _appSettingsSnapshot.Value;
var defaultNickName = string.IsNullOrEmpty(appSettings.UserDefaultName) ? "用户" : appSettings.UserDefaultName;
var defaultAvatar = string.IsNullOrEmpty(appSettings.UserDefaultIcon) ? "" : appSettings.UserDefaultIcon;
user = new T_Users
{
NickName = $"{defaultNickName}_{openId.Substring(0, Math.Min(8, openId.Length))}",
Avatar = defaultAvatar,
PhoneNumber = phoneNumber,
Experience = 0,
LevelId = 1,
Status = UserStatusEnum.Normal,
CertifiedType = 0,
CertifiedStatus = CertifiedStatusEnum.,
IsCertified = false,
IsVip = false,
UID = await _userInfoService.GenerateUniqueUIDAsync(),
CreatedAt = now,
UpdatedAt = now,
RegisterIp = clientIp,
LastLoginIp = clientIp,
LastLoginTime = now
};
await _userRepository.InsertAsync(user);
// 新用户自动关联默认身份组
await AssignDefaultIdentityGroupAsync(user.Id);
wechatLogin = new T_WechatMiniProgramLogins
{
OpenId = openId,
SessionKey = sessionKey,
UnionId = unionId ?? "",
UserId = user.Id,
LastLoginIp = clientIp,
LastLoginTime = now,
CreatedAt = now,
UpdatedAt = now,
};
await _wechatMiniProgramLoginsRepository.InsertAsync(wechatLogin);
}
// 更新用户最后登录信息
user.LastLoginTime = now;
user.LastLoginIp = clientIp;
await _userRepository.UpdateAsync(user);
// 生成JWT Token
var claims = new List<Claim>
{
new Claim("userId", user.Id.ToString()),
new Claim("loginType", ((int)LoginTypeEnum.WechatLogin).ToString())
};
var jwtToken = await _jwtAuthManager.GenerateTokensAsync(user.Id.ToString(), claims, now);
await _userTokensRepository.Select
.Where(x => x.UserId == user.Id).ToDelete().ExecuteDeletedAsync();
var userToken = new T_UserTokens
{
UserId = user.Id,
AccessToken = jwtToken.Token,
LoginType = LoginTypeEnum.WechatLogin,
LoginId = wechatLogin.Id,
ClientIp = clientIp,
UserAgent = _httpContextAccessor.GetUserAgent(),
ExpiresAt = jwtToken.ExpireAt,
TokenType = "Bearer",
CreatedAt = now,
UpdatedAt = now
};
await _userTokensRepository.InsertAsync(userToken);
return jwtToken;
}
/// <summary>
/// 为新用户自动关联默认身份组
/// </summary>
/// <param name="userId">新用户ID</param>
private async Task AssignDefaultIdentityGroupAsync(long userId)
{
var defaultGroup = await _identityGroupsRepository.Select
.Where(x => x.IsDefault == true)
.FirstAsync();
if (defaultGroup != null)
{
await _userIdentityGroupsRepository.InsertAsync(new T_UserIdentityGroups
{
UserId = userId,
IdentityGroupId = defaultGroup.Id,
CreatedAt = DateTime.Now
});
}
}
/// <summary>
/// 密码加密
/// </summary>
/// <param name="password">原始密码</param>
/// <returns>加密后的密码哈希和盐值</returns>
private (string hash, string salt) HashPassword(string password)
{
// 生成随机盐值
var saltBytes = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(saltBytes);
}
var salt = SystemConvert.ToBase64String(saltBytes);
// 使用PBKDF2进行密码哈希
using (var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, 10000, HashAlgorithmName.SHA256))
{
var hashBytes = pbkdf2.GetBytes(32);
var hash = SystemConvert.ToBase64String(hashBytes);
return (hash, salt);
}
}
/// <summary>
/// 验证密码
/// </summary>
/// <param name="password">原始密码</param>
/// <param name="hash">存储的密码哈希</param>
/// <param name="salt">存储的盐值</param>
/// <returns>密码是否正确</returns>
private bool VerifyPassword(string password, string hash, string salt)
{
try
{
var saltBytes = SystemConvert.FromBase64String(salt);
using (var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, 10000, HashAlgorithmName.SHA256))
{
var hashBytes = pbkdf2.GetBytes(32);
var computedHash = SystemConvert.ToBase64String(hashBytes);
return computedHash == hash;
}
}
catch
{
return false;
}
}
}
}