AuthService.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  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 Microsoft.AspNetCore.Authorization;
  8. using Microsoft.AspNetCore.Mvc;
  9. using Microsoft.AspNetCore.Mvc.ModelBinding;
  10. using Microsoft.IdentityModel.Tokens;
  11. using Microsoft.IdentityModel.JsonWebTokens;
  12. using ZhonTai.Admin.Core.Auth;
  13. using ZhonTai.Admin.Core.Attributes;
  14. using ZhonTai.Admin.Core.Configs;
  15. using ZhonTai.Admin.Core.Consts;
  16. using ZhonTai.Admin.Core.Dto;
  17. using ZhonTai.Admin.Domain.Permission;
  18. using ZhonTai.Admin.Domain.User;
  19. using ZhonTai.Admin.Domain.Tenant;
  20. using ZhonTai.Admin.Services.Auth.Dto;
  21. using ZhonTai.Admin.Domain.RolePermission;
  22. using ZhonTai.Admin.Domain.UserRole;
  23. using ZhonTai.Admin.Services.LoginLog.Dto;
  24. using ZhonTai.Admin.Services.LoginLog;
  25. using ZhonTai.Admin.Services.User;
  26. using ZhonTai.Common.Extensions;
  27. using ZhonTai.Common.Helpers;
  28. using ZhonTai.DynamicApi;
  29. using ZhonTai.DynamicApi.Attributes;
  30. using FreeSql;
  31. using ZhonTai.Admin.Domain.TenantPermission;
  32. using Microsoft.AspNetCore.Identity;
  33. using System.Collections.Generic;
  34. using ZhonTai.Admin.Core.Captcha;
  35. using Newtonsoft.Json;
  36. using Lazy.SlideCaptcha.Core.Validator;
  37. using static Lazy.SlideCaptcha.Core.ValidateResult;
  38. using ZhonTai.Admin.Domain.PkgPermission;
  39. using ZhonTai.Admin.Domain.TenantPkg;
  40. namespace ZhonTai.Admin.Services.Auth;
  41. /// <summary>
  42. /// 认证授权服务
  43. /// </summary>
  44. [DynamicApi(Area = AdminConsts.AreaName)]
  45. public class AuthService : BaseService, IAuthService, IDynamicApi
  46. {
  47. private readonly AppConfig _appConfig;
  48. private readonly JwtConfig _jwtConfig;
  49. private readonly IPermissionRepository _permissionRepository;
  50. private readonly IUserRepository _userRepository;
  51. private readonly ITenantRepository _tenantRepository;
  52. private IPasswordHasher<UserEntity> _passwordHasher => LazyGetRequiredService<IPasswordHasher<UserEntity>>();
  53. private ISlideCaptcha _captcha => LazyGetRequiredService<ISlideCaptcha>();
  54. public AuthService(
  55. AppConfig appConfig,
  56. JwtConfig jwtConfig,
  57. IUserRepository userRepository,
  58. IPermissionRepository permissionRepository,
  59. ITenantRepository tenantRepository
  60. )
  61. {
  62. _appConfig = appConfig;
  63. _jwtConfig = jwtConfig;
  64. _userRepository = userRepository;
  65. _permissionRepository = permissionRepository;
  66. _tenantRepository = tenantRepository;
  67. }
  68. /// <summary>
  69. /// 获得token
  70. /// </summary>
  71. /// <param name="user">用户信息</param>
  72. /// <returns></returns>
  73. private string GetToken(AuthLoginOutput user)
  74. {
  75. if (user == null)
  76. {
  77. return string.Empty;
  78. }
  79. var token = LazyGetRequiredService<IUserToken>().Create(new[]
  80. {
  81. new Claim(ClaimAttributes.UserId, user.Id.ToString(), ClaimValueTypes.Integer64),
  82. new Claim(ClaimAttributes.UserName, user.UserName),
  83. new Claim(ClaimAttributes.Name, user.Name),
  84. new Claim(ClaimAttributes.UserType, user.Type.ToInt().ToString(), ClaimValueTypes.Integer32),
  85. new Claim(ClaimAttributes.TenantId, user.TenantId.ToString(), ClaimValueTypes.Integer64),
  86. new Claim(ClaimAttributes.TenantType, user.Tenant?.TenantType.ToInt().ToString(), ClaimValueTypes.Integer32),
  87. new Claim(ClaimAttributes.DbKey, user.Tenant?.DbKey??"")
  88. });
  89. return token;
  90. }
  91. /// <summary>
  92. /// 查询密钥
  93. /// </summary>
  94. /// <returns></returns>
  95. [HttpGet]
  96. [AllowAnonymous]
  97. [NoOprationLog]
  98. public async Task<AuthGetPasswordEncryptKeyOutput> GetPasswordEncryptKeyAsync()
  99. {
  100. //写入Redis
  101. var guid = Guid.NewGuid().ToString("N");
  102. var key = CacheKeys.PassWordEncrypt + guid;
  103. var encyptKey = StringHelper.GenerateRandom(8);
  104. await Cache.SetAsync(key, encyptKey, TimeSpan.FromMinutes(5));
  105. return new AuthGetPasswordEncryptKeyOutput { Key = guid, EncyptKey = encyptKey };
  106. }
  107. /// <summary>
  108. /// 查询用户个人信息
  109. /// </summary>
  110. /// <returns></returns>
  111. [Login]
  112. public async Task<AuthUserProfileDto> GetUserProfileAsync()
  113. {
  114. if (!(User?.Id > 0))
  115. {
  116. throw ResultOutput.Exception("未登录");
  117. }
  118. using (_userRepository.DataFilter.Disable(FilterNames.Self, FilterNames.Data))
  119. {
  120. var profile = await _userRepository.GetAsync<AuthUserProfileDto>(User.Id);
  121. return profile;
  122. }
  123. }
  124. /// <summary>
  125. /// 查询用户菜单列表
  126. /// </summary>
  127. /// <returns></returns>
  128. [Login]
  129. public async Task<List<AuthUserMenuDto>> GetUserMenusAsync()
  130. {
  131. if (!(User?.Id > 0))
  132. {
  133. throw ResultOutput.Exception("未登录");
  134. }
  135. using (_userRepository.DataFilter.Disable(FilterNames.Self, FilterNames.Data))
  136. {
  137. var menuSelect = _permissionRepository.Select;
  138. if (!User.PlatformAdmin)
  139. {
  140. var db = _permissionRepository.Orm;
  141. if (User.TenantAdmin)
  142. {
  143. menuSelect = menuSelect.Where(a =>
  144. db.Select<TenantPermissionEntity>()
  145. .Where(b => b.PermissionId == a.Id && b.TenantId == User.TenantId)
  146. .Any()
  147. ||
  148. db.Select<TenantPkgEntity, PkgPermissionEntity>()
  149. .Where((b, c) => b.PkgId == c.PkgId && b.TenantId == User.TenantId && c.PermissionId == a.Id)
  150. .Any()
  151. );
  152. }
  153. else
  154. {
  155. menuSelect = menuSelect.Where(a =>
  156. db.Select<RolePermissionEntity>()
  157. .InnerJoin<UserRoleEntity>((b, c) => b.RoleId == c.RoleId && c.UserId == User.Id)
  158. .Where(b => b.PermissionId == a.Id)
  159. .Any()
  160. );
  161. }
  162. menuSelect = menuSelect.AsTreeCte(up: true);
  163. }
  164. var menuList = await menuSelect
  165. .Where(a => new[] { PermissionType.Group, PermissionType.Menu }.Contains(a.Type))
  166. .ToListAsync(a => new AuthUserMenuDto { ViewPath = a.View.Path });
  167. return menuList.DistinctBy(a => a.Id).OrderBy(a => a.ParentId).ThenBy(a => a.Sort).ToList();
  168. }
  169. }
  170. /// <summary>
  171. /// 查询用户权限列表
  172. /// </summary>
  173. /// <returns></returns>
  174. [Login]
  175. public async Task<AuthGetUserPermissionsOutput> GetUserPermissionsAsync()
  176. {
  177. if (!(User?.Id > 0))
  178. {
  179. throw ResultOutput.Exception("未登录");
  180. }
  181. using (_userRepository.DataFilter.Disable(FilterNames.Self, FilterNames.Data))
  182. {
  183. var authGetUserPermissionsOutput = new AuthGetUserPermissionsOutput
  184. {
  185. //用户信息
  186. User = await _userRepository.GetAsync<AuthUserProfileDto>(User.Id)
  187. };
  188. var dotSelect = _permissionRepository.Select.Where(a => a.Type == PermissionType.Dot);
  189. if (!User.PlatformAdmin)
  190. {
  191. var db = _permissionRepository.Orm;
  192. if (User.TenantAdmin)
  193. {
  194. dotSelect = dotSelect.Where(a =>
  195. db.Select<TenantPermissionEntity>()
  196. .Where(b => b.PermissionId == a.Id && b.TenantId == User.TenantId)
  197. .Any()
  198. ||
  199. db.Select<TenantPkgEntity, PkgPermissionEntity>()
  200. .Where((b, c) => b.PkgId == c.PkgId && b.TenantId == User.TenantId && c.PermissionId == a.Id)
  201. .Any()
  202. );
  203. }
  204. else
  205. {
  206. dotSelect = dotSelect.Where(a =>
  207. db.Select<RolePermissionEntity>()
  208. .InnerJoin<UserRoleEntity>((b, c) => b.RoleId == c.RoleId && c.UserId == User.Id)
  209. .Where(b => b.PermissionId == a.Id)
  210. .Any()
  211. );
  212. }
  213. }
  214. //用户权限点
  215. authGetUserPermissionsOutput.Permissions = await dotSelect.ToListAsync(a => a.Code);
  216. return authGetUserPermissionsOutput;
  217. }
  218. }
  219. /// <summary>
  220. /// 查询用户信息
  221. /// </summary>
  222. /// <returns></returns>
  223. [Login]
  224. public async Task<AuthGetUserInfoOutput> GetUserInfoAsync()
  225. {
  226. if (!(User?.Id > 0))
  227. {
  228. throw ResultOutput.Exception("未登录");
  229. }
  230. using (_userRepository.DataFilter.Disable(FilterNames.Self, FilterNames.Data))
  231. {
  232. var authGetUserInfoOutput = new AuthGetUserInfoOutput
  233. {
  234. //用户信息
  235. User = await _userRepository.GetAsync<AuthUserProfileDto>(User.Id)
  236. };
  237. var menuSelect = _permissionRepository.Select;
  238. var dotSelect = _permissionRepository.Select.Where(a => a.Type == PermissionType.Dot);
  239. if (!User.PlatformAdmin)
  240. {
  241. var db = _permissionRepository.Orm;
  242. if (User.TenantAdmin)
  243. {
  244. menuSelect = menuSelect.Where(a =>
  245. db.Select<TenantPermissionEntity>()
  246. .Where(b => b.PermissionId == a.Id && b.TenantId == User.TenantId)
  247. .Any()
  248. ||
  249. db.Select<TenantPkgEntity, PkgPermissionEntity>()
  250. .Where((b, c) => b.PkgId == c.PkgId && b.TenantId == User.TenantId && c.PermissionId == a.Id)
  251. .Any()
  252. );
  253. dotSelect = dotSelect.Where(a =>
  254. db.Select<TenantPermissionEntity>()
  255. .Where(b => b.PermissionId == a.Id && b.TenantId == User.TenantId)
  256. .Any()
  257. ||
  258. db.Select<TenantPkgEntity, PkgPermissionEntity>()
  259. .Where((b, c) => b.PkgId == c.PkgId && b.TenantId == User.TenantId && c.PermissionId == a.Id)
  260. .Any()
  261. );
  262. }
  263. else
  264. {
  265. menuSelect = menuSelect.Where(a =>
  266. db.Select<RolePermissionEntity>()
  267. .InnerJoin<UserRoleEntity>((b, c) => b.RoleId == c.RoleId && c.UserId == User.Id)
  268. .Where(b => b.PermissionId == a.Id)
  269. .Any()
  270. );
  271. dotSelect = dotSelect.Where(a =>
  272. db.Select<RolePermissionEntity>()
  273. .InnerJoin<UserRoleEntity>((b, c) => b.RoleId == c.RoleId && c.UserId == User.Id)
  274. .Where(b => b.PermissionId == a.Id)
  275. .Any()
  276. );
  277. }
  278. menuSelect = menuSelect.AsTreeCte(up: true);
  279. }
  280. var menuList = await menuSelect
  281. .Where(a => new[] { PermissionType.Group, PermissionType.Menu }.Contains(a.Type))
  282. .ToListAsync(a => new AuthUserMenuDto { ViewPath = a.View.Path });
  283. //用户菜单
  284. authGetUserInfoOutput.Menus = menuList.DistinctBy(a => a.Id).OrderBy(a => a.ParentId).ThenBy(a => a.Sort).ToList();
  285. //用户权限点
  286. authGetUserInfoOutput.Permissions = await dotSelect.ToListAsync(a => a.Code);
  287. return authGetUserInfoOutput;
  288. }
  289. }
  290. /// <summary>
  291. /// 登录
  292. /// </summary>
  293. /// <param name="input"></param>
  294. /// <returns></returns>
  295. [HttpPost]
  296. [AllowAnonymous]
  297. [NoOprationLog]
  298. public async Task<dynamic> LoginAsync(AuthLoginInput input)
  299. {
  300. using (_userRepository.DataFilter.DisableAll())
  301. {
  302. var sw = new Stopwatch();
  303. sw.Start();
  304. #region 验证码校验
  305. if (_appConfig.VarifyCode.Enable)
  306. {
  307. if(input.CaptchaId.IsNull() || input.CaptchaData.IsNull())
  308. {
  309. throw ResultOutput.Exception("请完成安全验证");
  310. }
  311. var validateResult = _captcha.Validate(input.CaptchaId, JsonConvert.DeserializeObject<SlideTrack>(input.CaptchaData));
  312. if (validateResult.Result != ValidateResultType.Success)
  313. {
  314. throw ResultOutput.Exception($"安全{validateResult.Message},请重新登录");
  315. }
  316. }
  317. #endregion
  318. #region 密码解密
  319. if (input.PasswordKey.NotNull())
  320. {
  321. var passwordEncryptKey = CacheKeys.PassWordEncrypt + input.PasswordKey;
  322. var existsPasswordKey = await Cache.ExistsAsync(passwordEncryptKey);
  323. if (existsPasswordKey)
  324. {
  325. var secretKey = await Cache.GetAsync(passwordEncryptKey);
  326. if (secretKey.IsNull())
  327. {
  328. throw ResultOutput.Exception("解密失败");
  329. }
  330. input.Password = DesEncrypt.Decrypt(input.Password, secretKey);
  331. await Cache.DelAsync(passwordEncryptKey);
  332. }
  333. else
  334. {
  335. throw ResultOutput.Exception("解密失败");
  336. }
  337. }
  338. #endregion
  339. #region 登录
  340. var user = await _userRepository.Select.Where(a => a.UserName == input.UserName).ToOneAsync();
  341. var valid = user?.Id > 0;
  342. if(valid)
  343. {
  344. if (user.PasswordEncryptType == PasswordEncryptType.PasswordHasher)
  345. {
  346. var passwordVerificationResult = _passwordHasher.VerifyHashedPassword(user, user.Password, input.Password);
  347. valid = passwordVerificationResult == PasswordVerificationResult.Success || passwordVerificationResult == PasswordVerificationResult.SuccessRehashNeeded;
  348. }
  349. else
  350. {
  351. var password = MD5Encrypt.Encrypt32(input.Password);
  352. valid = user.Password == password;
  353. }
  354. }
  355. if(!valid)
  356. {
  357. throw ResultOutput.Exception("用户名或密码错误");
  358. }
  359. if (!user.Enabled)
  360. {
  361. throw ResultOutput.Exception("账号已停用,禁止登录");
  362. }
  363. #endregion
  364. #region 获得token
  365. var authLoginOutput = Mapper.Map<AuthLoginOutput>(user);
  366. if (_appConfig.Tenant)
  367. {
  368. var tenant = await _tenantRepository.Select.WhereDynamic(user.TenantId).ToOneAsync<AuthLoginTenantDto>();
  369. if (!(tenant != null && tenant.Enabled))
  370. {
  371. throw ResultOutput.Exception("企业已停用,禁止登录");
  372. }
  373. authLoginOutput.Tenant = tenant;
  374. }
  375. string token = GetToken(authLoginOutput);
  376. #endregion
  377. sw.Stop();
  378. #region 添加登录日志
  379. var loginLogAddInput = new LoginLogAddInput
  380. {
  381. TenantId = authLoginOutput.TenantId,
  382. Name = authLoginOutput.Name,
  383. ElapsedMilliseconds = sw.ElapsedMilliseconds,
  384. Status = true,
  385. CreatedUserId = authLoginOutput.Id,
  386. CreatedUserName = user.UserName,
  387. };
  388. await LazyGetRequiredService<ILoginLogService>().AddAsync(loginLogAddInput);
  389. #endregion 添加登录日志
  390. return new { token };
  391. }
  392. }
  393. /// <summary>
  394. /// 手机号登录
  395. /// </summary>
  396. /// <param name="input"></param>
  397. /// <returns></returns>
  398. [HttpPost]
  399. [AllowAnonymous]
  400. [NoOprationLog]
  401. public async Task<dynamic> MobileLoginAsync(AuthMobileLoginInput input)
  402. {
  403. using (_userRepository.DataFilter.DisableAll())
  404. {
  405. var sw = new Stopwatch();
  406. sw.Start();
  407. #region 短信验证码验证
  408. var codeKey = CacheKeys.GetSmsCode(input.Mobile, input.CodeId);
  409. if (await Cache.ExistsAsync(codeKey))
  410. {
  411. var code = await Cache.GetAsync(codeKey);
  412. if (code != input.Code)
  413. {
  414. throw ResultOutput.Exception("验证码输入有误,请重新输入");
  415. }
  416. await Cache.DelAsync(codeKey);
  417. }
  418. else
  419. {
  420. throw ResultOutput.Exception("验证码不存在,请重新发送");
  421. }
  422. #endregion
  423. #region 登录
  424. var user = await _userRepository.Select.Where(a => a.Mobile == input.Mobile).ToOneAsync();
  425. if (!(user?.Id > 0))
  426. {
  427. throw ResultOutput.Exception("账号不存在");
  428. }
  429. if (!user.Enabled)
  430. {
  431. throw ResultOutput.Exception("账号已停用,禁止登录");
  432. }
  433. #endregion
  434. #region 获得token
  435. var authLoginOutput = Mapper.Map<AuthLoginOutput>(user);
  436. if (_appConfig.Tenant)
  437. {
  438. var tenant = await _tenantRepository.Select.WhereDynamic(user.TenantId).ToOneAsync<AuthLoginTenantDto>();
  439. if (!(tenant != null && tenant.Enabled))
  440. {
  441. throw ResultOutput.Exception("企业已停用,禁止登录");
  442. }
  443. authLoginOutput.Tenant = tenant;
  444. }
  445. string token = GetToken(authLoginOutput);
  446. #endregion
  447. sw.Stop();
  448. #region 添加登录日志
  449. var loginLogAddInput = new LoginLogAddInput
  450. {
  451. TenantId = authLoginOutput.TenantId,
  452. Name = authLoginOutput.Name,
  453. ElapsedMilliseconds = sw.ElapsedMilliseconds,
  454. Status = true,
  455. CreatedUserId = authLoginOutput.Id,
  456. CreatedUserName = user.UserName,
  457. };
  458. await LazyGetRequiredService<ILoginLogService>().AddAsync(loginLogAddInput);
  459. #endregion 添加登录日志
  460. return new { token };
  461. }
  462. }
  463. /// <summary>
  464. /// 刷新Token
  465. /// 以旧换新
  466. /// </summary>
  467. /// <param name="token"></param>
  468. /// <returns></returns>
  469. [HttpGet]
  470. [AllowAnonymous]
  471. public async Task<dynamic> Refresh([BindRequired] string token)
  472. {
  473. var jwtSecurityToken = LazyGetRequiredService<IUserToken>().Decode(token);
  474. var userClaims = jwtSecurityToken?.Claims?.ToArray();
  475. if (userClaims == null || userClaims.Length == 0)
  476. {
  477. throw ResultOutput.Exception("无法解析token");
  478. }
  479. var refreshExpires = userClaims.FirstOrDefault(a => a.Type == ClaimAttributes.RefreshExpires)?.Value;
  480. if (refreshExpires.IsNull() || refreshExpires.ToLong() <= DateTime.Now.ToTimestamp())
  481. {
  482. throw ResultOutput.Exception("登录信息已过期");
  483. }
  484. var userId = userClaims.FirstOrDefault(a => a.Type == ClaimAttributes.UserId)?.Value;
  485. if (userId.IsNull())
  486. {
  487. throw ResultOutput.Exception("登录信息已失效");
  488. }
  489. //验签
  490. var securityKey = _jwtConfig.SecurityKey;
  491. var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(securityKey)), SecurityAlgorithms.HmacSha256);
  492. var input = jwtSecurityToken.RawHeader + "." + jwtSecurityToken.RawPayload;
  493. if (jwtSecurityToken.RawSignature != JwtTokenUtilities.CreateEncodedSignature(input, signingCredentials))
  494. {
  495. throw ResultOutput.Exception("验签失败");
  496. }
  497. var user = await LazyGetRequiredService<IUserService>().GetLoginUserAsync(userId.ToLong());
  498. if(!(user?.Id > 0))
  499. {
  500. throw ResultOutput.Exception("账号不存在");
  501. }
  502. if (!user.Enabled)
  503. {
  504. throw ResultOutput.Exception("账号已停用,禁止登录");
  505. }
  506. if (_appConfig.Tenant)
  507. {
  508. if (!(user.Tenant != null && user.Tenant.Enabled))
  509. {
  510. throw ResultOutput.Exception("企业已停用,禁止登录");
  511. }
  512. }
  513. string newToken = GetToken(user);
  514. return new { token = newToken };
  515. }
  516. /// <summary>
  517. /// 是否开启验证码
  518. /// </summary>
  519. /// <returns></returns>
  520. [HttpGet]
  521. [AllowAnonymous]
  522. [NoOprationLog]
  523. public bool IsCaptcha()
  524. {
  525. return _appConfig.VarifyCode.Enable;
  526. }
  527. }