惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tor Project blog
T
The Blog of Author Tim Ferriss
WordPress大学
WordPress大学
H
Help Net Security
酷 壳 – CoolShell
酷 壳 – CoolShell
宝玉的分享
宝玉的分享
博客园 - 司徒正美
Jina AI
Jina AI
雷峰网
雷峰网
博客园_首页
博客园 - 三生石上(FineUI控件)
G
Google Developers Blog
MongoDB | Blog
MongoDB | Blog
Hugging Face - Blog
Hugging Face - Blog
The Cloudflare Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Forbes - Security
Forbes - Security
T
Threatpost
V
Visual Studio Blog
A
Arctic Wolf
Microsoft Azure Blog
Microsoft Azure Blog
Security Latest
Security Latest
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
P
Privacy & Cybersecurity Law Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Spread Privacy
Spread Privacy
N
News and Events Feed by Topic
Vercel News
Vercel News
爱范儿
爱范儿
The GitHub Blog
The GitHub Blog
NISL@THU
NISL@THU
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
PCI Perspectives
PCI Perspectives
博客园 - 【当耐特】
IT之家
IT之家
S
Secure Thoughts
Engineering at Meta
Engineering at Meta
阮一峰的网络日志
阮一峰的网络日志
Cisco Talos Blog
Cisco Talos Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
月光博客
月光博客
Latest news
Latest news
小众软件
小众软件
Attack and Defense Labs
Attack and Defense Labs
I
Intezer
Y
Y Combinator Blog
The Last Watchdog
The Last Watchdog
大猫的无限游戏
大猫的无限游戏

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第八章:安全授权与身份认证
我才是银古 · 2026-06-21 · via 博客园 - 我才是银古

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

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);
    }
}

上一章: 第07章-数据验证与异常处理 | 返回目录 | 下一章: 第09章-日志与审计