This commit is contained in:
owenchen committed 2026-05-12 10:09:31 +08:00
1 parent 31fbe199c1
commit 1c0c83af5f
469 files changed
+62544 -5

No files matched your search

@@ -0,0 +1,488 @@
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<SecurityUserEntity> 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<IRepository<SecurityUserEntity>>();
}
[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<JwtGenerate>()!;
string clainmName = Guid.NewGuid().ToString();
Dictionary<string, string> claims = new Dictionary<string, string>();
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<SecurityUserWebLoginEntity> repositoryLog = service.GetRepository<IRepository<SecurityUserWebLoginEntity>>();
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<JwtGenerate>()!;
Dictionary<string, string> claims = new Dictionary<string, string>();
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<SecurityUserWebLoginEntity> repositoryLog = service.GetRepository<IRepository<SecurityUserWebLoginEntity>>();
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<ISecurityGeneratorNumberService>()!;
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<JwtGenerate>()!;
Dictionary<string, string> claims = new Dictionary<string, string>();
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<ISecurityUserRegisterPublisher>()!;
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<JwtGenerate>()!;
string clainmName = Guid.NewGuid().ToString();
Dictionary<string, string> claims = new Dictionary<string, string>();
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<SecurityUserWebLoginEntity> repositoryLog = service.GetRepository<IRepository<SecurityUserWebLoginEntity>>();
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<SecurityUserEntity> repositorySecurityUser = service.GetRepository<IRepository<SecurityUserEntity>>();
SecurityUserEntity user = repositorySecurityUser.DetachedEntities.Where(x => x.UserName == username && x.UserIsValidIndc == YesNoPolicy.YES).FirstOrDefault();
if (user == null) throw new ServiceErrorException("无效的令牌,请重新登录.");
IRepository<SecurityUserWebLoginEntity> repositoryLog = service.GetRepository<IRepository<SecurityUserWebLoginEntity>>();
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<JwtGenerate>()!;
Dictionary<string, string> claims = new Dictionary<string, string>();
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<SecurityUserWebLoginEntity> repositoryLog = service.GetRepository<IRepository<SecurityUserWebLoginEntity>>();
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);
}
}
}