当前位置: 首页 > news >正文

第八章-安全授权与身份认证

第八章:安全授权与身份认证

8.1 身份认证概述

身份认证(Authentication)和授权(Authorization)是 Web 应用安全体系的两大核心组件。身份认证用于验证"你是谁",授权用于控制"你能做什么"。Furion 框架基于 ASP.NET Core 的安全体系,提供了 JWT 认证、角色授权、策略授权等多种安全机制的便捷集成。

核心概念对比:

概念 英文 说明 类比
认证 Authentication 确认用户身份 出示身份证
授权 Authorization 确认用户权限 检查门禁权限

Furion 安全体系支持的功能:

功能 说明
JWT 认证 基于 JSON Web Token 的无状态认证
角色授权 基于角色的访问控制(RBAC)
策略授权 基于策略的灵活授权方式
自定义授权 支持自定义授权处理器
OAuth 2.0 支持第三方登录集成
CORS 配置 跨域资源共享配置

8.2 JWT 认证配置与使用

8.2.1 JWT 原理简介

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT 由三部分组成:

  • Header(头部):包含令牌类型和签名算法
  • Payload(载荷):包含声明(Claims),即用户信息
  • Signature(签名):用于验证令牌的完整性

8.2.2 配置 JWTSettings

appsettings.json 中配置 JWT 参数:

{"JWTSettings": {"ValidateIssuerSigningKey": true,"IssuerSigningKey": "YourSuperSecretKeyThatShouldBeAtLeast32Characters","ValidateIssuer": true,"ValidIssuer": "FurionApp","ValidateAudience": true,"ValidAudience": "FurionClient","ValidateLifetime": true,"ExpiredTime": 1440,"ClockSkew": 5,"Algorithm": "HS256"}
}

配置参数说明:

参数 类型 说明
ValidateIssuerSigningKey bool 是否验证签名密钥
IssuerSigningKey string 签名密钥(至少32位)
ValidateIssuer bool 是否验证颁发者
ValidIssuer string 颁发者标识
ValidateAudience bool 是否验证受众
ValidAudience string 受众标识
ValidateLifetime bool 是否验证过期时间
ExpiredTime int 过期时间(分钟)
ClockSkew int 时钟偏差(分钟)
Algorithm string 加密算法

8.2.3 注册 JWT 服务

// Program.cs
var builder = WebApplication.CreateBuilder(args);// 添加 JWT 认证
builder.Services.AddJwt<JwtHandler>();var app = builder.Build();// 使用认证和授权中间件
app.UseAuthentication();
app.UseAuthorization();app.Run();

8.2.4 实现 JWT 处理器

using Furion.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;/// <summary>
/// JWT 授权处理器
/// </summary>
public class JwtHandler : AppAuthorizeHandler
{/// <summary>/// 验证管道 - 每次请求时执行/// </summary>public override async Task<bool> PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext){// 获取当前用户信息var userId = context.User.FindFirstValue("UserId");if (string.IsNullOrEmpty(userId)){return false;}// 可以在此处进行额外验证// 例如:检查用户是否被禁用、Token 是否在黑名单中等// var userService = httpContext.RequestServices.GetService<IUserService>();// var isValid = await userService.IsUserActiveAsync(int.Parse(userId));return true;}/// <summary>/// 重写 Token 验证失败处理/// </summary>public override Task HandleAsync(AuthorizationHandlerContext context){// 调用基类处理return base.HandleAsync(context);}
}

8.2.5 Token 生成

using Furion.DataEncryption;
using Furion.DynamicApiController;/// <summary>
/// 认证服务
/// </summary>
public class AuthService : IDynamicApiController
{private readonly IRepository<User> _userRepo;public AuthService(IRepository<User> userRepo){_userRepo = userRepo;}/// <summary>/// 用户登录/// </summary>[AllowAnonymous]public async Task<LoginOutput> Login(LoginInput input){// 验证用户var user = await _userRepo.FirstOrDefaultAsync(u => u.UserName == input.UserName);if (user == null || !VerifyPassword(input.Password, user.PasswordHash)){throw Oops.Oh("用户名或密码错误");}if (user.IsDisabled){throw Oops.Oh("账号已被禁用");}// 生成 Tokenvar accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>{{ "UserId", user.Id },{ "UserName", user.UserName },{ "Email", user.Email },{ ClaimTypes.Role, user.RoleName }   // 角色信息});// 生成刷新 Tokenvar refreshToken = JWTEncryption.GenerateRefreshToken(accessToken);// 设置响应头返回 Tokenreturn new LoginOutput{AccessToken = accessToken,RefreshToken = refreshToken,ExpiresIn = 1440  // 分钟};}private bool VerifyPassword(string inputPassword, string storedHash){// 使用 BCrypt 或 MD5 等方式验证密码return BCrypt.Net.BCrypt.Verify(inputPassword, storedHash);}
}/// <summary>
/// 登录输入
/// </summary>
public class LoginInput
{[Required(ErrorMessage = "用户名不能为空")]public string UserName { get; set; }[Required(ErrorMessage = "密码不能为空")]public string Password { get; set; }
}/// <summary>
/// 登录输出
/// </summary>
public class LoginOutput
{/// <summary>/// 访问令牌/// </summary>public string AccessToken { get; set; }/// <summary>/// 刷新令牌/// </summary>public string RefreshToken { get; set; }/// <summary>/// 过期时间(分钟)/// </summary>public int ExpiresIn { get; set; }
}

8.3 自定义 JWT Claims

8.3.1 添加自定义声明

/// <summary>
/// 生成包含丰富用户信息的 Token
/// </summary>
public string GenerateToken(User user, List<string> permissions)
{var claims = new Dictionary<string, object>{// 标准声明{ "UserId", user.Id },{ "UserName", user.UserName },{ ClaimTypes.Email, user.Email },{ ClaimTypes.Role, user.RoleName },// 自定义声明{ "DepartmentId", user.DepartmentId },{ "TenantId", user.TenantId },{ "Permissions", string.Join(",", permissions) },{ "LoginTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }};return JWTEncryption.Encrypt(claims);
}

8.3.2 读取 Claims 信息

using System.Security.Claims;public class UserContextService : IDynamicApiController
{private readonly IHttpContextAccessor _httpContextAccessor;public UserContextService(IHttpContextAccessor httpContextAccessor){_httpContextAccessor = httpContextAccessor;}/// <summary>/// 获取当前登录用户ID/// </summary>public int GetCurrentUserId(){var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirstValue("UserId");return int.Parse(userIdClaim ?? "0");}/// <summary>/// 获取当前登录用户名/// </summary>public string GetCurrentUserName(){return _httpContextAccessor.HttpContext?.User.FindFirstValue("UserName") ?? string.Empty;}/// <summary>/// 获取当前用户角色/// </summary>public string GetCurrentUserRole(){return _httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.Role) ?? string.Empty;}/// <summary>/// 获取当前用户所有权限/// </summary>public List<string> GetCurrentUserPermissions(){var permissions = _httpContextAccessor.HttpContext?.User.FindFirstValue("Permissions") ?? string.Empty;return permissions.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();}/// <summary>/// 获取当前用户信息/// </summary>public UserInfo GetCurrentUser(){var user = _httpContextAccessor.HttpContext?.User;if (user == null) return null;return new UserInfo{UserId = int.Parse(user.FindFirstValue("UserId") ?? "0"),UserName = user.FindFirstValue("UserName"),Email = user.FindFirstValue(ClaimTypes.Email),Role = user.FindFirstValue(ClaimTypes.Role),TenantId = int.Parse(user.FindFirstValue("TenantId") ?? "0")};}
}public class UserInfo
{public int UserId { get; set; }public string UserName { get; set; }public string Email { get; set; }public string Role { get; set; }public int TenantId { get; set; }
}

8.4 Token 刷新机制

/// <summary>
/// Token 刷新服务
/// </summary>
public class TokenRefreshService : IDynamicApiController
{private readonly IRepository<User> _userRepo;public TokenRefreshService(IRepository<User> userRepo){_userRepo = userRepo;}/// <summary>/// 刷新 Token/// </summary>[AllowAnonymous]public async Task<LoginOutput> RefreshToken(string accessToken, string refreshToken){// 验证刷新 Token 的有效性var (isValid, tokenObj) = JWTEncryption.Validate(refreshToken);if (!isValid){throw Oops.Oh("刷新令牌无效或已过期,请重新登录");}// 验证访问 Token(允许已过期)var claims = JWTEncryption.ReadJwtToken(accessToken);var userId = claims?.Claims.FirstOrDefault(c => c.Type == "UserId")?.Value;if (string.IsNullOrEmpty(userId)){throw Oops.Oh("无效的访问令牌");}// 验证用户状态var user = await _userRepo.FindAsync(int.Parse(userId));if (user == null || user.IsDisabled){throw Oops.Oh("用户不存在或已被禁用");}// 生成新的 Tokenvar newAccessToken = JWTEncryption.Encrypt(new Dictionary<string, object>{{ "UserId", user.Id },{ "UserName", user.UserName },{ "Email", user.Email },{ ClaimTypes.Role, user.RoleName }});var newRefreshToken = JWTEncryption.GenerateRefreshToken(newAccessToken);return new LoginOutput{AccessToken = newAccessToken,RefreshToken = newRefreshToken,ExpiresIn = 1440};}
}

8.5 基于角色的授权(Role-based)

8.5.1 角色授权配置

using Microsoft.AspNetCore.Authorization;
using Furion.DynamicApiController;/// <summary>
/// 用户管理服务 - 基于角色的授权
/// </summary>
public class UserManageService : IDynamicApiController
{private readonly IRepository<User> _userRepo;public UserManageService(IRepository<User> userRepo){_userRepo = userRepo;}/// <summary>/// 获取所有用户 - 仅管理员可访问/// </summary>[Authorize(Roles = "admin")]public async Task<List<User>> GetAllUsers(){return await _userRepo.Entities.ToListAsync();}/// <summary>/// 删除用户 - 仅超级管理员可操作/// </summary>[Authorize(Roles = "superadmin")]public async Task DeleteUser(int id){var user = await _userRepo.FindAsync(id);if (user == null) throw Oops.Oh("用户不存在");await _userRepo.DeleteAsync(user);await _userRepo.SaveNowAsync();}/// <summary>/// 查看用户详情 - 管理员或经理可访问/// </summary>[Authorize(Roles = "admin,manager")]public async Task<User> GetUserDetail(int id){return await _userRepo.FindAsync(id);}/// <summary>/// 获取个人信息 - 任何已认证用户/// </summary>[Authorize]public async Task<User> GetMyInfo(){var userId = App.User.FindFirstValue("UserId");return await _userRepo.FindAsync(int.Parse(userId));}/// <summary>/// 公开接口 - 无需认证/// </summary>[AllowAnonymous]public string GetPublicInfo(){return "这是一个公开接口";}
}

8.5.2 角色层级关系

/// <summary>
/// 角色枚举
/// </summary>
public enum RoleType
{/// <summary>/// 普通用户/// </summary>User = 1,/// <summary>/// 编辑/// </summary>Editor = 2,/// <summary>/// 经理/// </summary>Manager = 3,/// <summary>/// 管理员/// </summary>Admin = 4,/// <summary>/// 超级管理员/// </summary>SuperAdmin = 5
}

8.6 基于策略的授权(Policy-based)

8.6.1 定义授权策略

// Program.cs 中配置授权策略
builder.Services.AddAuthorization(options =>
{// 管理员策略options.AddPolicy("AdminPolicy", policy =>{policy.RequireRole("admin", "superadmin");});// 年龄策略options.AddPolicy("AdultPolicy", policy =>{policy.RequireClaim("Age");policy.RequireAssertion(context =>{var ageClaim = context.User.FindFirst("Age");return ageClaim != null && int.Parse(ageClaim.Value) >= 18;});});// 部门策略options.AddPolicy("ITDepartmentPolicy", policy =>{policy.RequireClaim("DepartmentId", "1", "2"); // 技术部门ID});// 多因素策略options.AddPolicy("SensitiveOperation", policy =>{policy.RequireAuthenticatedUser();policy.RequireRole("admin");policy.RequireClaim("MfaVerified", "true");});// 自定义需求策略options.AddPolicy("MinimumExperience", policy =>{policy.Requirements.Add(new MinimumExperienceRequirement(3));});
});

8.6.2 使用授权策略

public class SystemService : IDynamicApiController
{/// <summary>/// 系统配置 - 需要管理员策略/// </summary>[Authorize(Policy = "AdminPolicy")]public async Task<object> GetSystemConfig(){return new { AppName = "Furion App", Version = "1.0.0" };}/// <summary>/// 敏感操作 - 需要多因素验证策略/// </summary>[Authorize(Policy = "SensitiveOperation")]public async Task ResetDatabase(){// 危险操作...}
}

8.7 自定义授权处理器

8.7.1 创建自定义授权需求和处理器

using Microsoft.AspNetCore.Authorization;/// <summary>
/// 最低工作年限需求
/// </summary>
public class MinimumExperienceRequirement : IAuthorizationRequirement
{public int MinimumYears { get; }public MinimumExperienceRequirement(int minimumYears){MinimumYears = minimumYears;}
}/// <summary>
/// 最低工作年限授权处理器
/// </summary>
public class MinimumExperienceHandler : AuthorizationHandler<MinimumExperienceRequirement>
{protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,MinimumExperienceRequirement requirement){var experienceClaim = context.User.FindFirst("ExperienceYears");if (experienceClaim != null &&int.TryParse(experienceClaim.Value, out var years) &&years >= requirement.MinimumYears){context.Succeed(requirement);}return Task.CompletedTask;}
}/// <summary>
/// 权限授权需求
/// </summary>
public class PermissionRequirement : IAuthorizationRequirement
{public string Permission { get; }public PermissionRequirement(string permission){Permission = permission;}
}/// <summary>
/// 权限授权处理器
/// </summary>
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{private readonly IRepository<UserPermission> _permissionRepo;public PermissionHandler(IRepository<UserPermission> permissionRepo){_permissionRepo = permissionRepo;}protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,PermissionRequirement requirement){var userId = context.User.FindFirstValue("UserId");if (string.IsNullOrEmpty(userId)){return;}// 查询用户权限var hasPermission = await _permissionRepo.AnyAsync(p => p.UserId == int.Parse(userId) &&p.PermissionCode == requirement.Permission);if (hasPermission){context.Succeed(requirement);}}
}// 注册授权处理器
builder.Services.AddScoped<IAuthorizationHandler, MinimumExperienceHandler>();
builder.Services.AddScoped<IAuthorizationHandler, PermissionHandler>();

8.7.2 动态权限验证

/// <summary>
/// 权限验证特性
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class PermissionAttribute : AuthorizeAttribute
{public PermissionAttribute(string permission) : base(){Policy = $"Permission:{permission}";}
}/// <summary>
/// 动态权限策略提供程序
/// </summary>
public class PermissionPolicyProvider : IAuthorizationPolicyProvider
{private readonly DefaultAuthorizationPolicyProvider _fallbackPolicyProvider;public PermissionPolicyProvider(IOptions<AuthorizationOptions> options){_fallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);}public Task<AuthorizationPolicy> GetDefaultPolicyAsync(){return _fallbackPolicyProvider.GetDefaultPolicyAsync();}public Task<AuthorizationPolicy> GetFallbackPolicyAsync(){return _fallbackPolicyProvider.GetFallbackPolicyAsync();}public Task<AuthorizationPolicy> GetPolicyAsync(string policyName){if (policyName.StartsWith("Permission:")){var permission = policyName.Substring("Permission:".Length);var policy = new AuthorizationPolicyBuilder().AddRequirements(new PermissionRequirement(permission)).Build();return Task.FromResult(policy);}return _fallbackPolicyProvider.GetPolicyAsync(policyName);}
}// 注册动态策略提供程序
builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();

8.8 [SecurityDefine] 安全定义

Furion 提供了 [SecurityDefine] 特性来定义接口的安全要求:

using Furion.Authorization;
using Furion.DynamicApiController;public class ResourceService : IDynamicApiController
{/// <summary>/// 使用 SecurityDefine 定义安全要求/// </summary>[SecurityDefine("resource:read")]public async Task<List<Resource>> GetResources(){// 获取资源列表return new List<Resource>();}[SecurityDefine("resource:create")]public async Task CreateResource(ResourceInput input){// 创建资源}[SecurityDefine("resource:update")]public async Task UpdateResource(int id, ResourceInput input){// 更新资源}[SecurityDefine("resource:delete")]public async Task DeleteResource(int id){// 删除资源}
}

在 JWT 处理器中验证安全定义:

public class JwtHandler : AppAuthorizeHandler
{public override async Task<bool> PipelineAsync(AuthorizationHandlerContext context,DefaultHttpContext httpContext){// 获取当前请求的安全定义var securityDefine = httpContext.GetMetadata<SecurityDefineAttribute>();if (securityDefine == null){return true; // 没有安全定义,默认通过}// 获取用户权限列表var permissions = context.User.FindFirstValue("Permissions")?.Split(',') ?? Array.Empty<string>();// 验证权限return permissions.Contains(securityDefine.ResourceId);}
}

8.9 接口权限控制

8.9.1 完整的权限控制方案

/// <summary>
/// 权限常量定义
/// </summary>
public static class Permissions
{// 用户管理public const string UserView = "system:user:view";public const string UserCreate = "system:user:create";public const string UserEdit = "system:user:edit";public const string UserDelete = "system:user:delete";// 角色管理public const string RoleView = "system:role:view";public const string RoleCreate = "system:role:create";public const string RoleEdit = "system:role:edit";public const string RoleDelete = "system:role:delete";// 订单管理public const string OrderView = "order:view";public const string OrderCreate = "order:create";public const string OrderEdit = "order:edit";public const string OrderDelete = "order:delete";public const string OrderExport = "order:export";
}/// <summary>
/// 用户管理接口 - 权限控制
/// </summary>
public class UserAdminService : IDynamicApiController
{private readonly IRepository<User> _userRepo;public UserAdminService(IRepository<User> userRepo){_userRepo = userRepo;}[SecurityDefine(Permissions.UserView)]public async Task<PagedList<User>> GetUsers(int pageIndex = 1, int pageSize = 20){return await _userRepo.Entities.OrderByDescending(u => u.Id).ToPagedListAsync(pageIndex, pageSize);}[SecurityDefine(Permissions.UserCreate)]public async Task<User> CreateUser(CreateUserInput input){var user = new User{UserName = input.UserName,Email = input.Email};var result = await _userRepo.InsertNowAsync(user);return result.Entity;}[SecurityDefine(Permissions.UserDelete)]public async Task DeleteUser(int id){await _userRepo.DeleteAsync(id);await _userRepo.SaveNowAsync();}
}

8.10 OAuth 2.0 集成

// Program.cs 配置 OAuth 2.0
builder.Services.AddAuthentication(options =>
{options.DefaultScheme = "Cookies";options.DefaultChallengeScheme = "OAuth";
})
.AddCookie("Cookies")
.AddOAuth("OAuth", options =>
{options.ClientId = "your_client_id";options.ClientSecret = "your_client_secret";options.AuthorizationEndpoint = "https://provider.com/oauth/authorize";options.TokenEndpoint = "https://provider.com/oauth/token";options.UserInformationEndpoint = "https://provider.com/api/user";options.CallbackPath = "/signin-oauth";options.SaveTokens = true;// 映射用户信息options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
});// 微信登录集成示例
builder.Services.AddAuthentication().AddWeChat(options =>{options.AppId = "your_wechat_appid";options.AppSecret = "your_wechat_appsecret";});// GitHub 登录集成示例
builder.Services.AddAuthentication().AddGitHub(options =>{options.ClientId = "your_github_client_id";options.ClientSecret = "your_github_client_secret";options.Scope.Add("user:email");});

8.11 跨域(CORS)配置

// Program.cs
var builder = WebApplication.CreateBuilder(args);// 配置 CORS
builder.Services.AddCorsAccessor();// 或者使用自定义 CORS 策略
builder.Services.AddCors(options =>
{// 默认策略 - 允许所有options.AddDefaultPolicy(policy =>{policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();});// 生产环境策略 - 限制来源options.AddPolicy("Production", policy =>{policy.WithOrigins("https://www.example.com","https://admin.example.com").AllowAnyMethod().AllowAnyHeader().AllowCredentials().SetMaxAge(TimeSpan.FromHours(1));});// API 策略options.AddPolicy("ApiPolicy", policy =>{policy.WithOrigins("https://api-client.example.com").WithMethods("GET", "POST", "PUT", "DELETE").WithHeaders("Authorization", "Content-Type").AllowCredentials();});
});var app = builder.Build();// 使用 CORS(必须在 UseRouting 之后,UseAuthorization 之前)
app.UseCors();
// 或使用指定策略
// app.UseCors("Production");

8.12 安全最佳实践

8.12.1 安全配置清单

安全项 建议 重要性
JWT 密钥 使用强密钥(至少256位),不要硬编码 ⭐⭐⭐⭐⭐
Token 过期 设置合理的过期时间(建议15-30分钟) ⭐⭐⭐⭐⭐
密码存储 使用 BCrypt 或 Argon2 加密存储 ⭐⭐⭐⭐⭐
HTTPS 生产环境强制使用 HTTPS ⭐⭐⭐⭐⭐
CORS 生产环境限制允许的来源 ⭐⭐⭐⭐
速率限制 对登录接口设置请求频率限制 ⭐⭐⭐⭐
输入验证 对所有用户输入进行严格验证 ⭐⭐⭐⭐
SQL注入 使用参数化查询或 ORM ⭐⭐⭐⭐⭐
XSS 防护 对输出内容进行编码转义 ⭐⭐⭐⭐
CSRF 防护 使用 Anti-Forgery Token ⭐⭐⭐

8.12.2 密码安全示例

using BCrypt.Net;/// <summary>
/// 密码安全工具类
/// </summary>
public static class PasswordHelper
{/// <summary>/// 加密密码/// </summary>public static string HashPassword(string password){return BCrypt.Net.BCrypt.HashPassword(password, BCrypt.Net.BCrypt.GenerateSalt(12));}/// <summary>/// 验证密码/// </summary>public static bool VerifyPassword(string password, string hashedPassword){return BCrypt.Net.BCrypt.Verify(password, hashedPassword);}/// <summary>/// 验证密码强度/// </summary>public static (bool IsValid, string Message) ValidatePasswordStrength(string password){if (string.IsNullOrEmpty(password))return (false, "密码不能为空");if (password.Length < 8)return (false, "密码长度不能少于8位");if (!password.Any(char.IsUpper))return (false, "密码必须包含至少一个大写字母");if (!password.Any(char.IsLower))return (false, "密码必须包含至少一个小写字母");if (!password.Any(char.IsDigit))return (false, "密码必须包含至少一个数字");if (!password.Any(c => !char.IsLetterOrDigit(c)))return (false, "密码必须包含至少一个特殊字符");return (true, "密码强度合格");}
}

8.12.3 防止暴力破解

using Microsoft.Extensions.Caching.Memory;/// <summary>
/// 登录限流服务
/// </summary>
public class LoginRateLimitService
{private readonly IMemoryCache _cache;private const int MaxAttempts = 5;private const int LockoutMinutes = 30;public LoginRateLimitService(IMemoryCache cache){_cache = cache;}/// <summary>/// 检查是否被锁定/// </summary>public bool IsLockedOut(string userName){var key = $"login_attempts:{userName}";if (_cache.TryGetValue(key, out int attempts)){return attempts >= MaxAttempts;}return false;}/// <summary>/// 记录失败登录/// </summary>public void RecordFailedAttempt(string userName){var key = $"login_attempts:{userName}";var attempts = _cache.GetOrCreate(key, entry =>{entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(LockoutMinutes);return 0;});_cache.Set(key, attempts + 1, TimeSpan.FromMinutes(LockoutMinutes));}/// <summary>/// 重置登录计数/// </summary>public void ResetAttempts(string userName){var key = $"login_attempts:{userName}";_cache.Remove(key);}
}
http://www.jsqmd.com/news/366497/

相关文章:

  • 适配注液设备:2026优选超声波流量计品牌推荐 - 品牌2025
  • ue的资源管理
  • 2026知识库部署方案商实力推荐清单 - 品牌2025
  • 2026年可靠的制造业ISO27001认证,简易ISO27001认证公司采购优选榜单 - 品牌鉴赏师
  • 2026年GEO服务新趋势:DeepSeek GEO合作伙伴推荐全解析 - 品牌2025
  • 第十章-缓存管理
  • 2026年专业的企业诚信管理体系认证,职业健康安全管理体系认证公司采购优选名录 - 品牌鉴赏师
  • 编写语言交友APP,根据用户想学习的语言(英语/韩语/日语/法语),匹配母语者/学习者,支持在线语音/文字交流,互相学习语言,结识外国朋友。
  • TikTok海外营销推广服务商推荐:助力制造业与三轮车、摩托车行业突破欧美市场 - 品牌2025
  • 第六章-数据库操作与EF-Core集成
  • 第十一章-事件总线
  • 2026年制造业海外社媒代运营服务商精选(涵盖 Facebook、LinkedIn 领英、TikTok 、Google、INS营销推广获客解决方案) - 品牌2025
  • 真心不骗你!千笔AI,断层领先的降AI率工具
  • 2026年南美(巴西、哥伦比亚、智利)海外名义雇主(EOR)服务商推荐:合规拓展拉美市场的关键伙伴 - 品牌2025
  • 2026年AI营销工具选择指南:如何选择适合企业的GEO解决方案 - 品牌策略主理人
  • AI4Science的前沿公司:Polymathic AI
  • 转型AI运维工程师·Day 7:构建“数据飞轮” —— 每一句对话都是资产
  • 第1章 程序点滴程序点滴-1.1 程序≠软件(1)
  • 北京春节收茅台酒的公司,认准京城亚南,专业鉴定不压价 - 品牌排行榜单
  • Java计算机毕设之基于springboot的软件测试管理系统的设计与实现(完整前后端代码+说明文档+LW,调试定制等)
  • 入职 Web3 运维日记 · 第 10 日:终极考核 —— 混沌工程 (Chaos Engineering) 与区域级容灾
  • 【开题答辩全过程】以 篮球技能培训管理系统为例,包含答辩的问题和答案
  • 潍坊本地生活代运营服务商综合测评及实力排行榜 - 野榜数据排行
  • 2026年2月服务认证咨询机构推荐,专业认证实力与口碑测评 - 品牌鉴赏师
  • 论文AIGC率从90%降到6%!2026最新降AI工具横向测评(含免费)+3招物理“去AI味”大法稳过检测!
  • 电影个性化推荐与分析系统 | Python Django 协同过滤 Echarts 词云分析 大数据 人工智能 deepseek 毕业设计源码(建议收藏)✅
  • 波峰焊低流量测量优选:2026精选超声波流量计品牌推荐 - 品牌2025
  • 收藏级|大模型规划组件3年进化史:从线性推理到反思式Agent,小白也能看懂的实操进阶指南
  • 喷涂工艺流量精准测量:超声波流量计品牌推荐 - 品牌2025
  • Java毕设项目推荐-基于springboot的企业人事管理系统员工信息管理、考勤管理、薪酬管理和部门管理【附源码+文档,调试定制服务】