第八章:安全授权与身份认证
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("账号已被禁用");
}
// 生成 Token
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
{
{ "UserId", user.Id },
{ "UserName", user.UserName },
{ "Email", user.Email },
{ ClaimTypes.Role, user.RoleName } // 角色信息
});
// 生成刷新 Token
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken);
// 设置响应头返回 Token
return 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("用户不存在或已被禁用");
}
// 生成新的 Token
var 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);
}
}