AuthService.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. using System;
  2. using System.Diagnostics;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Security.Claims;
  6. using System.Threading.Tasks;
  7. using StackExchange.Profiling;
  8. using Microsoft.AspNetCore.Authorization;
  9. using Microsoft.AspNetCore.Cors;
  10. using Microsoft.AspNetCore.Mvc;
  11. using Microsoft.AspNetCore.Mvc.ModelBinding;
  12. using Microsoft.IdentityModel.Tokens;
  13. using Microsoft.IdentityModel.JsonWebTokens;
  14. using ZhonTai.Admin.Core.Auth;
  15. using ZhonTai.Admin.Core.Attributes;
  16. using ZhonTai.Admin.Core.Configs;
  17. using ZhonTai.Admin.Core.Consts;
  18. using ZhonTai.Admin.Core.Dto;
  19. using ZhonTai.Admin.Domain.Permission;
  20. using ZhonTai.Admin.Domain.User;
  21. using ZhonTai.Admin.Domain.Tenant;
  22. using ZhonTai.Admin.Services.Auth.Dto;
  23. using ZhonTai.Admin.Domain.RolePermission;
  24. using ZhonTai.Admin.Domain.UserRole;
  25. using ZhonTai.Admin.Tools.Captcha;
  26. using ZhonTai.Admin.Services.LoginLog.Dto;
  27. using ZhonTai.Admin.Services.LoginLog;
  28. using ZhonTai.Admin.Services.User;
  29. using ZhonTai.Common.Extensions;
  30. using ZhonTai.Common.Helpers;
  31. using ZhonTai.DynamicApi;
  32. using ZhonTai.DynamicApi.Attributes;
  33. using FreeSql;
  34. using Microsoft.Extensions.DependencyInjection;
  35. using ZhonTai.Admin.Domain.TenantPermission;
  36. using ZhonTai.Admin.Core.Db;
  37. using System.Collections.Generic;
  38. namespace ZhonTai.Admin.Services.Auth;
  39. /// <summary>
  40. /// 认证授权服务
  41. /// </summary>
  42. [DynamicApi(Area = AdminConsts.AreaName)]
  43. public class AuthService : BaseService, IAuthService, IDynamicApi
  44. {
  45. private readonly AppConfig _appConfig;
  46. private readonly JwtConfig _jwtConfig;
  47. private readonly IPermissionRepository _permissionRepository;
  48. private readonly IUserRepository _userRepository;
  49. private readonly ITenantRepository _tenantRepository;
  50. private readonly ICaptchaTool _captchaTool;
  51. public AuthService(
  52. AppConfig appConfig,
  53. JwtConfig jwtConfig,
  54. IUserRepository userRepository,
  55. IPermissionRepository permissionRepository,
  56. ITenantRepository tenantRepository,
  57. ICaptchaTool captchaTool
  58. )
  59. {
  60. _appConfig = appConfig;
  61. _jwtConfig = jwtConfig;
  62. _userRepository = userRepository;
  63. _permissionRepository = permissionRepository;
  64. _tenantRepository = tenantRepository;
  65. _captchaTool = captchaTool;
  66. }
  67. /// <summary>
  68. /// 获得token
  69. /// </summary>
  70. /// <param name="user">用户信息</param>
  71. /// <returns></returns>
  72. private string GetToken(AuthLoginOutput user)
  73. {
  74. if (user == null)
  75. {
  76. return string.Empty;
  77. }
  78. var token = LazyGetRequiredService<IUserToken>().Create(new[]
  79. {
  80. new Claim(ClaimAttributes.UserId, user.Id.ToString(), ClaimValueTypes.Integer64),
  81. new Claim(ClaimAttributes.UserName, user.UserName),
  82. new Claim(ClaimAttributes.Name, user.Name),
  83. new Claim(ClaimAttributes.UserType, user.Type.ToInt().ToString(), ClaimValueTypes.Integer32),
  84. new Claim(ClaimAttributes.TenantId, user.TenantId.ToString(), ClaimValueTypes.Integer64),
  85. new Claim(ClaimAttributes.TenantType, user.TenantType.ToInt().ToString(), ClaimValueTypes.Integer32),
  86. new Claim(ClaimAttributes.DbKey, user.DbKey??"")
  87. });
  88. return token;
  89. }
  90. /// <summary>
  91. /// 查询密钥
  92. /// </summary>
  93. /// <returns></returns>
  94. [HttpGet]
  95. [AllowAnonymous]
  96. [NoOprationLog]
  97. public async Task<IResultOutput> GetPasswordEncryptKeyAsync()
  98. {
  99. //写入Redis
  100. var guid = Guid.NewGuid().ToString("N");
  101. var key = CacheKeys.PassWordEncrypt + guid;
  102. var encyptKey = StringHelper.GenerateRandom(8);
  103. await Cache.SetAsync(key, encyptKey, TimeSpan.FromMinutes(5));
  104. var data = new { key = guid, encyptKey };
  105. return ResultOutput.Ok(data);
  106. }
  107. /// <summary>
  108. /// 查询用户信息
  109. /// </summary>
  110. /// <returns></returns>
  111. [Login]
  112. public async Task<IResultOutput> GetUserInfoAsync()
  113. {
  114. if (!(User?.Id > 0))
  115. {
  116. return ResultOutput.NotOk("未登录");
  117. }
  118. using (_userRepository.DataFilter.Disable(FilterNames.Self, FilterNames.Data))
  119. {
  120. var authGetUserInfoOutput = new AuthGetUserInfoOutput
  121. {
  122. //用户信息
  123. User = await _userRepository.GetAsync<AuthUserProfileDto>(User.Id)
  124. };
  125. IFreeSql db = _permissionRepository.Orm;
  126. if (User.TenantAdmin)
  127. {
  128. var cloud = ServiceProvider.GetRequiredService<FreeSqlCloud>();
  129. db = cloud.Use(DbKeys.MasterDb);
  130. }
  131. var permissionRepository = db.GetRepositoryBase<PermissionEntity>();
  132. var menuSelect = permissionRepository.Select;
  133. var dotSelect = permissionRepository.Select.Where(a => a.Type == PermissionType.Dot);
  134. if (!User.PlatformAdmin)
  135. {
  136. if (User.TenantAdmin)
  137. {
  138. menuSelect = menuSelect.Where(a =>
  139. db.Select<TenantPermissionEntity>()
  140. .Where(b => b.PermissionId == a.Id && b.TenantId == User.TenantId)
  141. .Any()
  142. );
  143. dotSelect = dotSelect.Where(a =>
  144. db.Select<TenantPermissionEntity>()
  145. .Where(b => b.PermissionId == a.Id && b.TenantId == User.TenantId)
  146. .Any()
  147. );
  148. }
  149. else
  150. {
  151. menuSelect = menuSelect.Where(a =>
  152. db.Select<RolePermissionEntity>()
  153. .InnerJoin<UserRoleEntity>((b, c) => b.RoleId == c.RoleId && c.UserId == User.Id)
  154. .Where(b => b.PermissionId == a.Id)
  155. .Any()
  156. );
  157. dotSelect = dotSelect.Where(a =>
  158. db.Select<RolePermissionEntity>()
  159. .InnerJoin<UserRoleEntity>((b, c) => b.RoleId == c.RoleId && c.UserId == User.Id)
  160. .Where(b => b.PermissionId == a.Id)
  161. .Any()
  162. );
  163. }
  164. menuSelect = menuSelect.AsTreeCte(up: true);
  165. }
  166. var menuList = await menuSelect
  167. .Where(a => new[] { PermissionType.Group, PermissionType.Menu }.Contains(a.Type))
  168. .ToListAsync(a => new AuthUserMenuDto { ViewPath = a.View.Path });
  169. //用户菜单
  170. authGetUserInfoOutput.Menus = menuList.DistinctBy(a => a.Id).OrderBy(a => a.ParentId).ThenBy(a => a.Sort).ToList();
  171. //用户权限点
  172. authGetUserInfoOutput.Permissions = await dotSelect.ToListAsync(a => a.Code);
  173. return ResultOutput.Ok(authGetUserInfoOutput);
  174. }
  175. }
  176. /// <summary>
  177. /// 登录
  178. /// </summary>
  179. /// <param name="input"></param>
  180. /// <returns></returns>
  181. [HttpPost]
  182. [AllowAnonymous]
  183. [NoOprationLog]
  184. public async Task<IResultOutput> LoginAsync(AuthLoginInput input)
  185. {
  186. using (_userRepository.DataFilter.Disable(FilterNames.Tenant, FilterNames.Self, FilterNames.Data))
  187. {
  188. var sw = new Stopwatch();
  189. sw.Start();
  190. #region 验证码校验
  191. if (_appConfig.VarifyCode.Enable)
  192. {
  193. input.Captcha.DeleteCache = true;
  194. input.Captcha.CaptchaKey = CacheKeys.Captcha;
  195. var isOk = await _captchaTool.CheckAsync(input.Captcha);
  196. if (!isOk)
  197. {
  198. return ResultOutput.NotOk("安全验证不通过,请重新登录");
  199. }
  200. }
  201. #endregion
  202. #region 密码解密
  203. if (input.PasswordKey.NotNull())
  204. {
  205. var passwordEncryptKey = CacheKeys.PassWordEncrypt + input.PasswordKey;
  206. var existsPasswordKey = await Cache.ExistsAsync(passwordEncryptKey);
  207. if (existsPasswordKey)
  208. {
  209. var secretKey = await Cache.GetAsync(passwordEncryptKey);
  210. if (secretKey.IsNull())
  211. {
  212. return ResultOutput.NotOk("解密失败");
  213. }
  214. input.Password = DesEncrypt.Decrypt(input.Password, secretKey);
  215. await Cache.DelAsync(passwordEncryptKey);
  216. }
  217. else
  218. {
  219. return ResultOutput.NotOk("解密失败!");
  220. }
  221. }
  222. #endregion
  223. #region 登录
  224. var password = MD5Encrypt.Encrypt32(input.Password);
  225. var user = await _userRepository.Select.Where(a => a.UserName == input.UserName && a.Password == password).ToOneAsync();
  226. if (!(user?.Id > 0))
  227. {
  228. return ResultOutput.NotOk("用户名或密码错误");
  229. }
  230. if (user.Status == UserStatus.Disabled)
  231. {
  232. return ResultOutput.NotOk("禁止登录,请联系管理员");
  233. }
  234. #endregion
  235. #region 获得token
  236. var authLoginOutput = Mapper.Map<AuthLoginOutput>(user);
  237. if (_appConfig.Tenant)
  238. {
  239. var tenant = await _tenantRepository.Select.WhereDynamic(user.TenantId).ToOneAsync(a => new { a.TenantType, a.DbKey });
  240. authLoginOutput.TenantType = tenant.TenantType;
  241. authLoginOutput.DbKey = tenant.DbKey;
  242. }
  243. string token = GetToken(authLoginOutput);
  244. #endregion
  245. sw.Stop();
  246. #region 添加登录日志
  247. var loginLogAddInput = new LoginLogAddInput
  248. {
  249. TenantId = authLoginOutput.TenantId,
  250. Name = authLoginOutput.Name,
  251. ElapsedMilliseconds = sw.ElapsedMilliseconds,
  252. Status = true,
  253. CreatedUserId = authLoginOutput.Id,
  254. CreatedUserName = input.UserName,
  255. };
  256. await LazyGetRequiredService<ILoginLogService>().AddAsync(loginLogAddInput);
  257. #endregion 添加登录日志
  258. return ResultOutput.Ok(new { token });
  259. }
  260. }
  261. /// <summary>
  262. /// 刷新Token
  263. /// 以旧换新
  264. /// </summary>
  265. /// <param name="token"></param>
  266. /// <returns></returns>
  267. [HttpGet]
  268. [AllowAnonymous]
  269. public async Task<IResultOutput> Refresh([BindRequired] string token)
  270. {
  271. var jwtSecurityToken = LazyGetRequiredService<IUserToken>().Decode(token);
  272. var userClaims = jwtSecurityToken?.Claims?.ToArray();
  273. if (userClaims == null || userClaims.Length == 0)
  274. {
  275. return ResultOutput.NotOk();
  276. }
  277. var refreshExpires = userClaims.FirstOrDefault(a => a.Type == ClaimAttributes.RefreshExpires)?.Value;
  278. if (refreshExpires.IsNull())
  279. {
  280. return ResultOutput.NotOk();
  281. }
  282. if (refreshExpires.ToLong() <= DateTime.Now.ToTimestamp())
  283. {
  284. return ResultOutput.NotOk("登录信息已过期");
  285. }
  286. var userId = userClaims.FirstOrDefault(a => a.Type == ClaimAttributes.UserId)?.Value;
  287. if (userId.IsNull())
  288. {
  289. return ResultOutput.NotOk("登录信息已失效");
  290. }
  291. //验签
  292. var securityKey = _jwtConfig.SecurityKey;
  293. var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(securityKey)), SecurityAlgorithms.HmacSha256);
  294. var input = jwtSecurityToken.RawHeader + "." + jwtSecurityToken.RawPayload;
  295. if (jwtSecurityToken.RawSignature != JwtTokenUtilities.CreateEncodedSignature(input, signingCredentials))
  296. {
  297. return ResultOutput.NotOk("验签失败");
  298. }
  299. var output = await LazyGetRequiredService<IUserService>().GetLoginUserAsync(userId.ToLong());
  300. string newToken = GetToken(output?.Data);
  301. return ResultOutput.Ok(new { token = newToken });
  302. }
  303. /// <summary>
  304. /// 获取验证数据
  305. /// </summary>
  306. /// <returns></returns>
  307. [HttpGet]
  308. [AllowAnonymous]
  309. [NoOprationLog]
  310. [EnableCors(AdminConsts.AllowAnyPolicyName)]
  311. public async Task<IResultOutput> GetCaptcha()
  312. {
  313. using (MiniProfiler.Current.Step("获取滑块验证"))
  314. {
  315. var data = await _captchaTool.GetAsync(CacheKeys.Captcha);
  316. return ResultOutput.Ok(data);
  317. }
  318. }
  319. /// <summary>
  320. /// 检查验证数据
  321. /// </summary>
  322. /// <returns></returns>
  323. [HttpGet]
  324. [AllowAnonymous]
  325. [NoOprationLog]
  326. [EnableCors(AdminConsts.AllowAnyPolicyName)]
  327. public async Task<IResultOutput> CheckCaptcha([FromQuery] CaptchaInput input)
  328. {
  329. input.CaptchaKey = CacheKeys.Captcha;
  330. var result = await _captchaTool.CheckAsync(input);
  331. return ResultOutput.Result(result);
  332. }
  333. }