using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using Model; using Model.Dto.System; using Model.Entity.System; using Model.Mapper; using ORM; using Service.Interface; using SqlSugar; namespace Service.Implement { /// /// 认证授权 服务实现:登录校验 → 加载角色/权限 → 签发 JWT(权限编码写入 Claims) /// public class AuthService : IAuthService { private readonly IConfiguration _configuration; private readonly ICurrentUser _currentUser; private readonly ILogger _logger; public AuthService(IConfiguration configuration, ICurrentUser currentUser, ILogger logger) { _configuration = configuration; _currentUser = currentUser; _logger = logger; } public async Task> LoginAsync(LoginDto dto, string? ip) { if (dto == null || string.IsNullOrWhiteSpace(dto.UserName) || string.IsNullOrWhiteSpace(dto.Password)) return Result.Error("用户名和密码不能为空"); try { var user = await SqlSugarContext.DbContext.Queryable() .Where(x => x.UserName == dto.UserName && x.IsDel == 0).FirstAsync(); if (user == null) return Result.Error("用户名或密码错误"); if (user.IsEnabled == 0) return Result.Error("账号已被禁用,请联系管理员"); if (!PasswordHelper.Verify(dto.Password, user.PasswordHash)) return Result.Error("用户名或密码错误"); // 加载角色与权限 var (roleIds, roleNames, permissionCodes, dataScope) = await LoadUserAuthAsync(user.Id); // 签发 JWT var token = GenerateJwt(user, roleIds, roleNames, permissionCodes, dataScope); // 更新最后登录时间 await SqlSugarContext.DbContext.Updateable() .SetColumns(x => x.LastLoginTime == DateTime.Now) .Where(x => x.Id == user.Id).ExecuteCommandAsync(); // 登录审计 await WriteAuditAsync(user.Id, user.UserName, roleIds.FirstOrDefault(), roleNames.FirstOrDefault(), "Login", "User", user.Id, 0, null, null, ip, $"用户 {user.UserName} 登录系统"); var userDto = user.ToDto(); userDto.RoleIds = roleIds.Select(r => r.ToString()).ToList(); userDto.RoleNames = string.Join(",", roleNames); return Result.Success(new LoginResultDto { Token = token, RefreshToken = token, User = userDto, Permissions = permissionCodes }); } catch (Exception ex) { _logger.LogError(ex, "登录异常"); return Result.Error("登录失败", ex); } } public async Task LogoutAsync() { if (!_currentUser.IsAuthenticated) return Result.Success(); try { await WriteAuditAsync(_currentUser.UserId, _currentUser.UserName, _currentUser.RoleIds.FirstOrDefault(), _currentUser.RoleNames.FirstOrDefault(), "Logout", "User", _currentUser.UserId, 0, null, null, null, $"用户 {_currentUser.UserName} 退出登录"); return Result.Success(); } catch (Exception ex) { _logger.LogError(ex, "退出登录记录审计失败"); return Result.Success(); // 退出本身不应失败 } } public async Task> GetMeAsync() { if (!_currentUser.IsAuthenticated) return Result.Error("未登录或 Token 已失效"); try { var user = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Id == _currentUser.UserId && x.IsDel == 0).FirstAsync(); if (user == null) return Result.Error("用户不存在或已被删除"); var userDto = user.ToDto(); userDto.RoleIds = _currentUser.RoleIds.Select(r => r.ToString()).ToList(); userDto.RoleNames = string.Join(",", _currentUser.RoleNames); return Result.Success(new LoginResultDto { Token = "", RefreshToken = "", User = userDto, Permissions = _currentUser.Permissions }); } catch (Exception ex) { return Result.Error("获取当前用户信息失败", ex); } } public async Task ChangePasswordAsync(ChangePasswordDto dto) { if (dto == null || string.IsNullOrWhiteSpace(dto.OldPassword) || string.IsNullOrWhiteSpace(dto.NewPassword)) return Result.Error("原密码和新密码不能为空"); if (dto.NewPassword.Length < 6) return Result.Error("新密码长度不能少于 6 位"); if (!_currentUser.IsAuthenticated) return Result.Error("未登录或 Token 已失效"); try { var user = await SqlSugarContext.DbContext.Queryable() .Where(x => x.Id == _currentUser.UserId && x.IsDel == 0).FirstAsync(); if (user == null) return Result.Error("用户不存在或已被删除"); if (!PasswordHelper.Verify(dto.OldPassword, user.PasswordHash)) return Result.Error("原密码错误"); await SqlSugarContext.DbContext.Updateable() .SetColumns(x => x.PasswordHash == PasswordHelper.Hash(dto.NewPassword)) .Where(x => x.Id == user.Id).ExecuteCommandAsync(); await WriteAuditAsync(user.Id, user.UserName, _currentUser.RoleIds.FirstOrDefault(), _currentUser.RoleNames.FirstOrDefault(), "Update", "User", user.Id, 0, null, null, null, $"用户 {user.UserName} 修改了自己的密码"); return Result.Success(); } catch (Exception ex) { return Result.Error("修改密码失败", ex); } } /// /// 加载用户的角色Id/角色名/权限编码/数据范围(取所有角色的并集,数据范围取最宽) /// public static async Task<(List roleIds, List roleNames, List permissions, byte dataScope)> LoadUserAuthAsync(long userId) { var db = SqlSugarContext.DbContext; var roleIds = await db.Queryable() .Where(x => x.UserId == userId) .Select(x => x.RoleId) .ToListAsync(); var roleList = new List(); if (roleIds.Count > 0) roleList = await db.Queryable() .Where(x => roleIds.Contains(x.Id) && x.IsDel == 0).ToListAsync(); var permissionCodes = new List(); if (roleIds.Count > 0) { var permIds = await db.Queryable() .Where(x => roleIds.Contains(x.RoleId)) .Select(x => x.PermissionId) .ToListAsync(); if (permIds.Count > 0) { var perms = await db.Queryable() .Where(x => permIds.Contains(x.Id) && x.IsDel == 0).ToListAsync(); permissionCodes = perms.Select(p => p.Code).Distinct().ToList(); } } // 数据范围取最宽:任一角色是"全部"即为全部 var dataScope = roleList.Count > 0 ? roleList.Min(r => r.DataScope) : (byte)2; return (roleIds, roleList.Select(r => r.Name).ToList(), permissionCodes, dataScope); } /// 生成 JWT:把用户身份 + 角色 + 权限编码全部写入 Claims(授权过滤器免查库) private string GenerateJwt(UserEntity user, List roleIds, List roleNames, List permissions, byte dataScope) { var jwt = _configuration.GetSection("Jwt"); var secret = jwt["SecretKey"]!; var issuer = jwt["Issuer"] ?? "IOT-API"; var audience = jwt["Audience"] ?? "IOT-Web"; var expireMinutes = double.TryParse(jwt["ExpireMinutes"], out var m) ? m : 480; var claims = new List { new(JwtRegisteredClaimNames.Sub, user.Id.ToString()), new(ClaimTypes.NameIdentifier, user.Id.ToString()), new(ClaimTypes.Name, user.UserName), new("roleIds", string.Join(",", roleIds)), new("roleNames", string.Join(",", roleNames)), new("permissions", string.Join(",", permissions)), new("dataScope", dataScope.ToString()), new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N")) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken(issuer, audience, claims, expires: DateTime.Now.AddMinutes(expireMinutes), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } /// 写一条审计日志 private static async Task WriteAuditAsync(long userId, string? userName, long? roleId, string? roleName, string operationType, string operationTarget, long targetId, long deviceId, string? oldValue, string? newValue, string? ip, string? description) { var now = DateTime.Now; await SqlSugarContext.DbContext.Insertable(new AuditLogEntity { UserId = userId, UserName = userName ?? "", RoleId = roleId ?? 0, RoleName = roleName ?? "", OperationType = operationType, OperationTarget = operationTarget, TargetId = targetId, DeviceId = deviceId, OldValue = oldValue, NewValue = newValue, Ip = ip, Description = description, OperateTime = now, CreateTime = now }).ExecuteCommandAsync(); } } }