using Azure.Core; using CloudBuilder.Core.Authorization; using CloudBuilder.Core.DatabaseAccessor; using CloudBuilder.Core.DatabaseAccessor.Entity; using CloudBuilder.Core.DependencyInjection.EventBuses; using CloudBuilder.Core.DependencyInjection.Request; using CloudBuilder.Core.Policy; using CloudBuilder.Core.Service; using CloudBuilder.Security.Data; using CloudBuilder.Security.Entity; using CloudBuilder.Security.Policy; using CloudBuilder.Security.Publisher; using CloudBuilder.Security.Service.Authorization; using CloudBuilder.Security.Service.Publisher; using DocumentFormat.OpenXml.Spreadsheet; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Runtime; using System.Text; using System.Text.RegularExpressions; namespace CloudBuilder.Security.Service { public class AuthService : IAuthService { private readonly IApplicationService service; private readonly IHttpContextAccessor httpContextAccessor; private readonly ILocaleMessageManager localeMessageManager; private readonly IContext context; private IRepository repository; public AuthService(IApplicationService service, IHttpContextAccessor httpContextAccessor, ILocaleMessageManager localeMessageManager, IContext context) { this.service = service; this.httpContextAccessor = httpContextAccessor; this.localeMessageManager = localeMessageManager; this.context = context; repository = service.GetRepository>(); } [AllowAnonymous] [UnitOfWork] public LoginProfile Login(LoginData data) { context.SetCulture(data.Culture); string certification = localeMessageManager.ComposeMessage(typeof(AuthService).Name, "certification"); SecurityUserEntity securityUserEntity = repository.DetachedEntities.Where(x => (x.UserName == data.UserMobile || x.UserMobile == data.UserMobile) && x.UserIsValidIndc == YesNoPolicy.YES).FirstOrDefault()!; if (securityUserEntity == null) { throw new ValidatedException(certification); } var descKey = service.Configuration[SecuritySettingsPolicy.SecuritySettings_DescKey]; var privateKey = service.Configuration[SecuritySettingsPolicy.SecuritySettings_RsaPrivateKey]; var rsa = new RSA(privateKey, true); var psd = rsa.DecodeOrNull(data.RsaUserPassword); if (string.IsNullOrEmpty(psd)) throw new ValidatedException(certification); //052636a8-b146-9efd-f5dc-0034a3c5bb34+password var guid = psd.Substring(0, 36); if (guid != data.Guid) { throw new ValidatedException(certification); } if (securityUserEntity.IsAccountsLockedIndc == YesNoPolicy.YES && securityUserEntity.AccountsLockedDatetime.HasValue && securityUserEntity.AccountsLockedDatetime.Value.Subtract(DateTime.Now).TotalMinutes > 0) { throw new ValidatedException(localeMessageManager.ComposeMessage("AuthService", "locked")); } data.UserPassword = psd.Substring(36, psd.Length - 36); if (securityUserEntity.UserPassword != data.UserPassword) { securityUserEntity.ErrorLoginCount += 1; //输错多少次后,账号锁定 if (securityUserEntity.ErrorLoginCount > SecurityUserPolicy.ERROR_LOGIN_COUNT) { securityUserEntity.IsAccountsLockedIndc = YesNoPolicy.YES; securityUserEntity.AccountsLockedDatetime = DateTime.Now.AddDays(1); } repository.UpdateNow(securityUserEntity); throw new ValidatedException(certification); } JwtGenerate jwtGenerate = service.ServiceProvider.GetService()!; string clainmName = Guid.NewGuid().ToString(); Dictionary claims = new Dictionary(); claims.Add(ClaimPolicy.CLAINM_NAME, clainmName); claims.Add(ClaimPolicy.CLAINM_ACCOUNT, securityUserEntity.UserMobile!); claims.Add(ClaimPolicy.CLAINM_USERID, securityUserEntity.UserName); claims.Add(ClaimPolicy.CLAINM_LOCALE, data.Culture); var accessToken = jwtGenerate.Generate(claims); if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization")) httpContextAccessor.HttpContext.Request.Headers.Remove("Authorization"); httpContextAccessor.HttpContext.Request.Headers.Add("Authorization", "Bearer " + accessToken); if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey(ClaimPolicy.HEADER_LOCALE)) httpContextAccessor.HttpContext.Request.Headers.Remove(ClaimPolicy.HEADER_LOCALE); httpContextAccessor.HttpContext.Request.Headers.Add(ClaimPolicy.HEADER_LOCALE, data.Culture); service.UserId = securityUserEntity.UserName; securityUserEntity.ErrorLoginCount = 0; securityUserEntity.IsAccountsLockedIndc = YesNoPolicy.NO; securityUserEntity.AccountsLockedDatetime = null; //更新最后登录日期 securityUserEntity.LastLoginDatetime = DateTime.Now; repository.UpdateNow(securityUserEntity); IRepository repositoryLog = service.GetRepository>(); SecurityUserWebLoginEntity webLogin = new SecurityUserWebLoginEntity { Username = securityUserEntity.UserName, SessionId = clainmName, LoginTime = DateTime.Now, HostIp = httpContextAccessor.HttpContext.Connection.RemoteIpAddress?.MapToIPv4()?.ToString(), HostUserAgent = this.httpContextAccessor?.HttpContext?.Request.Headers.UserAgent.ToString(), RefreshToken = jwtGenerate.GenerateRefreshToken(), RefreshTokenRefreshedTime = DateTime.Now, }; repositoryLog.InsertNow(webLogin); PermissionContext permissionContext = new PermissionContext(service); return new LoginProfile { Token = new JwtInfo() { AccessToken = accessToken, RefreshToken = webLogin.RefreshToken, CreatedTime = webLogin.RefreshTokenRefreshedTime }, Username = securityUserEntity.UserName, UserType = securityUserEntity.UserType, UserLocalName = securityUserEntity.UserLocalName, UserEngName = securityUserEntity.UserEngName, UserMobile = securityUserEntity.UserMobile!, PermissionInfo = permissionContext.GetUserPermissionInfo() }; } [PermissionOperate(name: SecurityPermissionPolicy.SecurityUser, operate: "LoginAgent")] [UnitOfWork] public LoginProfile LoginAgent(LoginData data) { LoginProfile loginProfile = Login(data); SecurityUserEntity agent = repository.DetachedEntities.Where(x => (x.UserMobile == data.AgentUserName && x.UserIsValidIndc == YesNoPolicy.YES)).FirstOrDefault()!; if (agent == null) { throw new ValidatedException("代理用户不存在."); } JwtGenerate jwtGenerate = service.ServiceProvider.GetService()!; Dictionary claims = new Dictionary(); claims.Add(ClaimPolicy.CLAINM_NAME, agent.UserName); claims.Add(ClaimPolicy.CLAINM_ACCOUNT, agent.UserMobile!); claims.Add(ClaimPolicy.CLAINM_USERID, agent.UserName); claims.Add(ClaimPolicy.CLAINM_LOCALE, data.Culture); var accessToken = jwtGenerate.Generate(claims); if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization")) httpContextAccessor.HttpContext.Request.Headers.Remove("Authorization"); httpContextAccessor.HttpContext.Request.Headers.Add("Authorization", "Bearer " + accessToken); if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey(ClaimPolicy.HEADER_LOCALE)) httpContextAccessor.HttpContext.Request.Headers.Remove(ClaimPolicy.HEADER_LOCALE); httpContextAccessor.HttpContext.Request.Headers.Add(ClaimPolicy.HEADER_LOCALE, data.Culture); SecurityUserWebLoginEntity webLogin = new SecurityUserWebLoginEntity { Username = agent.UserName, SessionId = Guid.NewGuid().ToString(), LoginTime = DateTime.Now, HostIp = loginProfile.Username, HostUserAgent = loginProfile.UserMobile!, RefreshToken = jwtGenerate.GenerateRefreshToken(), RefreshTokenRefreshedTime = DateTime.Now, }; IRepository repositoryLog = service.GetRepository>(); repositoryLog.InsertNow(webLogin); service.UserId = agent.UserName; PermissionContext permissionContext = new PermissionContext(service); return new LoginProfile { Token = new JwtInfo() { AccessToken = accessToken, RefreshToken = webLogin.RefreshToken, CreatedTime = webLogin.RefreshTokenRefreshedTime }, Username = agent.UserName, UserType = agent.UserType, UserLocalName = agent.UserLocalName, UserEngName = agent.UserEngName, UserMobile = agent.UserMobile!, PermissionInfo = permissionContext.GetUserPermissionInfo() }; } [AllowAnonymous] [UnitOfWork] public string Register(RegisterData data) { SecurityUserEntity securityUser = repository.DetachedEntities.Where(x => x.UserMobile == data.UserMobile).FirstOrDefault()!; if (securityUser != null) { throw new ValidatedException("手机号已注册."); } securityUser = new SecurityUserEntity(); if (string.IsNullOrEmpty(data.UserMobile) || string.IsNullOrEmpty(data.RsaUserPassword)) return null; ISecurityGeneratorNumberService securityGeneratorNumberService = service.ServiceProvider.GetService()!; string userName = securityGeneratorNumberService.GetNo(SecurityPrefixPolicy.SECURITY_USER, SecurityPrefixPolicy.SECURITY_USER_USER_NAME, null, YesNoPolicy.NO, SecurityPrefixPolicy.SECURITY_USER_FORMAT); var privateKey = service.Configuration[SecuritySettingsPolicy.SecuritySettings_RsaPrivateKey]; var rsa = new System.RSA(privateKey, true); securityUser.UserPassword = rsa.DecodeOrNull(data.RsaUserPassword); //052636a8-b146-9efd-f5dc-0034a3c5bb34+password(MD5) string guid = securityUser.UserPassword.Substring(0, 36); securityUser.UserPassword = securityUser.UserPassword.Substring(36, securityUser.UserPassword.Length - 36); securityUser.UserName = userName; securityUser.UserMobile = data.UserMobile; securityUser.UserLocalName = "用户" + securityUser.UserName; securityUser.UserIsValidIndc = YesNoPolicy.YES; JwtGenerate jwtGenerate = service.ServiceProvider.GetService()!; Dictionary claims = new Dictionary(); claims.Add(ClaimPolicy.CLAINM_NAME, securityUser.UserName); claims.Add(ClaimPolicy.CLAINM_ACCOUNT, securityUser.UserName); claims.Add(ClaimPolicy.CLAINM_USERID, securityUser.UserName); claims.Add(ClaimPolicy.CLAINM_LOCALE, CulturePolicy.ZH_CN); service.UserId = securityUser.UserName; var accessToken = jwtGenerate.Generate(claims); if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization")) httpContextAccessor.HttpContext.Request.Headers.Remove("Authorization"); httpContextAccessor.HttpContext.Request.Headers.Add("Authorization", "Bearer " + accessToken); repository.InsertNow(securityUser); //发布注册的事务 EventData eventData = new EventData(); ISecurityUserRegisterPublisher publisher = service.ServiceProvider.GetService()!; eventData.EventSource = securityUser; eventData.EventId = typeof(SecurityUserRegisterPublisher).Name; publisher.Publish(eventData); return securityUser.UserMobile; } [PermissionOperate(name: SecurityPermissionPolicy.SecurityUser, operate: Operate.Update)] [UnitOfWork] public LoginProfile AmendPassword(LoginData data) { SecurityUserEntity securityUserEntity = repository.DetachedEntities.Where(x => x.UserName == service.UserId && x.UserIsValidIndc == YesNoPolicy.YES).FirstOrDefault()!; if (securityUserEntity == null) { throw new ValidatedException("认证不通过."); } var descKey = service.Configuration[SecuritySettingsPolicy.SecuritySettings_DescKey]; var privateKey = service.Configuration[SecuritySettingsPolicy.SecuritySettings_RsaPrivateKey]; var rsa = new RSA(privateKey, true); var psd = rsa.DecodeOrNull(data.RsaUserPassword); if (string.IsNullOrEmpty(psd)) throw new ValidatedException("认证不通过."); //052636a8-b146-9efd-f5dc-0034a3c5bb34+password var guid = psd.Substring(0, 36); if (guid != data.UserName) { throw new ValidatedException("认证不通过."); } if (securityUserEntity.IsAccountsLockedIndc == YesNoPolicy.YES && securityUserEntity.AccountsLockedDatetime.HasValue && securityUserEntity.AccountsLockedDatetime.Value.Subtract(DateTime.Now).TotalMinutes > 0) { throw new ValidatedException("用户账号已锁定."); } if (securityUserEntity.UserPassword != psd.Substring(36, psd.Length - 36)) { securityUserEntity.ErrorLoginCount += 1; //输错多少次后,账号锁定 if (securityUserEntity.ErrorLoginCount > SecurityUserPolicy.ERROR_LOGIN_COUNT) { securityUserEntity.IsAccountsLockedIndc = YesNoPolicy.YES; securityUserEntity.AccountsLockedDatetime = DateTime.Now.AddDays(1); } repository.UpdateNow(securityUserEntity); throw new ValidatedException("认证不通过."); } JwtGenerate jwtGenerate = service.ServiceProvider.GetService()!; string clainmName = Guid.NewGuid().ToString(); Dictionary claims = new Dictionary(); claims.Add(ClaimPolicy.CLAINM_NAME, clainmName); claims.Add(ClaimPolicy.CLAINM_ACCOUNT, securityUserEntity.UserMobile!); claims.Add(ClaimPolicy.CLAINM_USERID, securityUserEntity.UserName); claims.Add(ClaimPolicy.CLAINM_LOCALE, data.Culture ?? CulturePolicy.ZH_CN); var accessToken = jwtGenerate.Generate(claims); if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization")) httpContextAccessor.HttpContext.Request.Headers.Remove("Authorization"); httpContextAccessor.HttpContext.Request.Headers.Add("Authorization", "Bearer " + accessToken); securityUserEntity.ErrorLoginCount = 0; securityUserEntity.IsAccountsLockedIndc = YesNoPolicy.NO; securityUserEntity.AccountsLockedDatetime = null; //更新最后登录日期 securityUserEntity.LastLoginDatetime = DateTime.Now; psd = rsa.DecodeOrNull(data.UserPassword); if (string.IsNullOrEmpty(psd)) throw new ValidatedException("认证不通过."); securityUserEntity.UserPassword = psd.Substring(36, psd.Length - 36); repository.UpdateNow(securityUserEntity); IRepository repositoryLog = service.GetRepository>(); SecurityUserWebLoginEntity webLogin = new SecurityUserWebLoginEntity { Username = securityUserEntity.UserName, SessionId = clainmName, LoginTime = DateTime.Now, HostIp = httpContextAccessor.HttpContext.Connection.RemoteIpAddress?.MapToIPv4()?.ToString(), HostUserAgent = this.httpContextAccessor?.HttpContext?.Request.Headers.UserAgent.ToString(), RefreshToken = jwtGenerate.GenerateRefreshToken(), RefreshTokenRefreshedTime = DateTime.Now, }; repositoryLog.InsertNow(webLogin); PermissionContext permissionContext = new PermissionContext(service); return new LoginProfile { Token = new JwtInfo() { AccessToken = accessToken, RefreshToken = webLogin.RefreshToken, CreatedTime = webLogin.RefreshTokenRefreshedTime }, Username = securityUserEntity.UserName, UserType = securityUserEntity.UserType, UserLocalName = securityUserEntity.UserLocalName, UserEngName = securityUserEntity.UserEngName, UserMobile = securityUserEntity.UserMobile!, PermissionInfo = permissionContext.GetUserPermissionInfo() }; } [AllowAnonymous] [HttpGet] public string GetRsaPublicKey() { if (string.IsNullOrEmpty(service.Configuration["SecuritySettings:RsaPublicKey"])) throw new ValidatedException("[SecuritySettings:RsaPublicKey]"); return service.Configuration["SecuritySettings:RsaPublicKey"]!; } [AllowAnonymous] public JwtInfo RefreshToken(JwtInfo jwtInfo) { if (jwtInfo == null || string.IsNullOrEmpty(jwtInfo.AccessToken) || string.IsNullOrEmpty(jwtInfo.RefreshToken)) throw new ServiceErrorException("无效的令牌,请重新登录."); TokenValidationParameters parameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, //获取或设置要使用的Microsoft.IdentityModel.Tokens.SecurityKey用于签名验证。 IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(service.CloudBuilder.CloudBuilderOptions.Jwt.Secret)), //获取或设置一个System.String,它表示将使用的有效发行者检查代币的发行者。 ValidIssuer = service.CloudBuilder.CloudBuilderOptions.Jwt.Issuer, //获取或设置一个字符串,该字符串表示将用于检查的有效受众反对令牌的观众。 ValidAudience = service.CloudBuilder.CloudBuilderOptions.Jwt.Audience, ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = false,//不验证过期时间 }; // 验证 Token var tokenHandler = new JwtSecurityTokenHandler(); SecurityToken vTocken; string token = jwtInfo.AccessToken.Replace("Bearer ", ""); try { var tokenValidationResult = tokenHandler.ValidateToken(token, parameters, out vTocken); if (tokenValidationResult == null || !tokenValidationResult.Identities.Any() || !tokenValidationResult.Identities.First().IsAuthenticated) throw new ServiceErrorException("无效的令牌,请重新登录."); } catch (Exception) { throw new ServiceErrorException("无效的令牌,请重新登录."); } string clainmName = ClaimPolicy.GetClaimValue(token, ClaimPolicy.CLAINM_NAME); string username = ClaimPolicy.GetClaimValue(token, ClaimPolicy.CLAINM_USERID); string userMobile = ClaimPolicy.GetClaimValue(token, ClaimPolicy.CLAINM_ACCOUNT); string locale = ClaimPolicy.GetClaimValue(token, ClaimPolicy.CLAINM_LOCALE); if (string.IsNullOrEmpty(clainmName) || string.IsNullOrEmpty(username)) throw new ServiceErrorException("无效的令牌,请重新登录."); IRepository repositorySecurityUser = service.GetRepository>(); SecurityUserEntity user = repositorySecurityUser.DetachedEntities.Where(x => x.UserName == username && x.UserIsValidIndc == YesNoPolicy.YES).FirstOrDefault(); if (user == null) throw new ServiceErrorException("无效的令牌,请重新登录."); IRepository repositoryLog = service.GetRepository>(); SecurityUserWebLoginEntity log = repositoryLog.DetachedEntities.Where(x => x.Username == username && x.SessionId == clainmName).FirstOrDefault()!; //不是最后登录的令牌失效 DateTime? LoginTime = repositoryLog.DetachedEntities.Where(x => x.Username == username).Max(x => x.LoginTime); if (LoginTime.HasValue && log.LoginTime < LoginTime.Value) throw new ServiceErrorException("无效的令牌,请重新登录."); if (log == null || log.RefreshToken != jwtInfo.RefreshToken || log.LogoutTime.HasValue || log.RefreshToken != jwtInfo.RefreshToken || log.RefreshTokenRefreshedTime.AddMinutes(Convert.ToInt32(service.CloudBuilder.CloudBuilderOptions.Jwt.RefreshExpireMins)) < DateTime.Now) throw new ServiceErrorException("无效的令牌,请重新登录."); JwtGenerate jwtGenerate = service.ServiceProvider.GetService()!; Dictionary claims = new Dictionary(); claims.Add(ClaimPolicy.CLAINM_NAME, clainmName); claims.Add(ClaimPolicy.CLAINM_ACCOUNT, userMobile); claims.Add(ClaimPolicy.CLAINM_USERID, username); claims.Add(ClaimPolicy.CLAINM_LOCALE, locale??CulturePolicy.ZH_CN); var accessToken = jwtGenerate.Generate(claims); log.RefreshToken = jwtGenerate.GenerateRefreshToken(); log.RefreshTokenRefreshedTime = DateTime.Now; if (httpContextAccessor.HttpContext.Request.Headers.ContainsKey("Authorization")) httpContextAccessor.HttpContext.Request.Headers.Remove("Authorization"); httpContextAccessor.HttpContext.Request.Headers.Add("Authorization", "Bearer " + accessToken); repositoryLog.UpdateNow(log); jwtInfo.AccessToken = accessToken; jwtInfo.RefreshToken = log.RefreshToken; jwtInfo.CreatedTime = log.RefreshTokenRefreshedTime; return jwtInfo; } [AllowAnonymous] public void Logout() { string username = ClaimPolicy.GetClaimValue(httpContextAccessor, ClaimPolicy.CLAINM_USERID); string clainmName = ClaimPolicy.GetClaimValue(httpContextAccessor, ClaimPolicy.CLAINM_NAME); if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(clainmName)) { return; } IRepository repositoryLog = service.GetRepository>(); SecurityUserWebLoginEntity log = repositoryLog.DetachedEntities.Where(x => x.Username == username && x.SessionId == clainmName).FirstOrDefault()!; if (log == null || log.LogoutTime.HasValue) { return; } log.LogoutTime = DateTime.Now; repositoryLog.UpdateNow(log); } } }