【.NET 8 实战--孢子记账--从单体到微服务】--简易权限--访问权限中间件

这篇文章我们讲解权限中最关键的地方:访问权限中间件。这个中间件可以帮助我们在请求到达Controller前进行权限验证,并且在不具备权限时通知客户端。

一、需求

我们先来可以下需求:

编号 功能 描述
1 权限验证 根据角色和请求的URL来验证是否有访问URL的权限,如果没有则返回401状态码,并告知客户端,反之允许客户端使用URL

二、编写代码

首先,我们需要在ISysRoleUrlServer接口和SysRoleUrlImp实现类中增加IsRoleUseUrl方法,代码如下:

csharp 复制代码
//ISysRoleUrlServer
/// <summary>
/// 角色是否可以访问URL
/// </summary>
/// <param name="roleId"></param>
/// <param name="url"></param>
/// <returns></returns>
bool IsRoleUseUrl(string roleId, string url);

//SysRoleUrlImp
/// <summary>
/// 角色是否可以访问URL
/// </summary>
/// <param name="roleId"></param>
/// <param name="url"></param>
/// <returns></returns>
public bool IsRoleUseUrl(string roleId, string url)
{
    try
    {
        return _dbContext.SysRoleUrls.Any(x => x.RoleId == roleId && x.Url.Url == url);
    }
    catch (Exception e)
    {
        throw;
    }
}

代码很简单,我们就不多讲解了。

然后,我们在项目中新建Middlewares 文件夹,在这个文件夹中新建权限验证中间件类PermissionsMiddleware,代码如下:

csharp 复制代码
using Microsoft.IdentityModel.Tokens;
using SporeAccounting.Server.Interface;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace SporeAccounting.Middlewares;

/// <summary>
/// 权限中间件
/// </summary>
public class PermissionsMiddleware
{
    private readonly RequestDelegate _next;

    public PermissionsMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    /// <summary>
    /// 权限中间件
    /// </summary>
    /// <param name="httpContext"></param>
    /// <param name="sysRoleUrlServer"></param>
    /// <param name="configuration"></param>
    public async Task Invoke(HttpContext httpContext, ISysRoleUrlServer sysRoleUrlServer, IConfiguration configuration)
    {
        //请求的路径
        string requestPath = httpContext.Request.Path.Value;
        //如果是登录、注册、找回密码接口,直接放行
        if (requestPath.Contains("/api/SysUser/Login") ||
           requestPath.Contains("/api/SysUser/Register") ||
           requestPath.Contains("/api/SysUser/RetrievePassword"))
        {
            await _next(httpContext);
            return;
        }
        //解析token
        string token = httpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
        if (token == null)
        {
            httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
            await httpContext.Response.WriteAsync("权限不足:用户无权访问此资源。");
        }
        else
        {
            // 验证并解析 Token
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.UTF8.GetBytes(configuration["JWT:IssuerSigningKey"]);
            try
            {
                var claimsPrincipal = tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidIssuer = configuration["JWT:ValidIssuer"],
                    ValidAudience = configuration["JWT:ValidAudience"],
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateLifetime = true
                }, out SecurityToken validatedToken);
                // 访问 Claims
                var userId = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
                var roleId = claimsPrincipal.FindFirst(ClaimTypes.Role)?.Value;
                // 在上下文中存储用户信息
                httpContext.Items["UserId"] = userId;
                string pathUrlNotParam = string.Join("/", requestPath.Split("/").Take(3));
                bool isUse = sysRoleUrlServer.IsRoleUseUrl(roleId, pathUrlNotParam);
                if (isUse)
                {
                    httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
                    await httpContext.Response.WriteAsync("权限不足:用户无权访问此资源。");
                    return;
                }
                await _next(httpContext);
            }
            catch (Exception)
            {
                // Token 无效
                httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
                await httpContext.Response.WriteAsync("无效token");
                return;
            }
        }
    }
}

在上面的代码中基于 JWT 验证用户身份和角色权限,以确保用户只能访问授权的资源。中间件通过构造函数接收 RequestDelegate 以便在权限验证通过后继续处理请求。在 Invoke 方法中,首先获取请求路径,并检查是否是免验证的接口(如登录、注册、找回密码),对于这些接口直接放行。然后从请求头中提取 Token,并使用 JwtSecurityTokenHandler 验证 Token 的有效性。验证成功后,通过 ClaimsPrincipal 提取用户的 ID 和角色信息,将其存入 HttpContext.Items 中。接着,调用自定义的权限服务 sysRoleUrlServer.IsRoleUseUrl 判断用户角色是否有权访问当前路径。如果权限不足则返回 403 状态码并提示"权限不足",若 Token 无效则返回 401 状态码提示"无效token"。验证通过后调用 _next(httpContext),将请求传递至下一个中间件或控制器。

最后,我们在Program类中把前面我们编写的中间件注入到项目中:

csharp 复制代码
app.UseMiddleware<PermissionsMiddleware>();

到此,我们的中间件就完成了。

三、总结

这篇文章我们大家一起编写了孢子记账的第一个中间件,后续我们还有更多的中间件编写。

相关推荐
_codemonster7 小时前
30分钟快速搭建 Spring Cloud Alibaba 微服务实战(一)
微服务·架构·毕业设计·课程设计
Dongwoo Jeong9 小时前
微服务架构(MSA)是如何诞生的?
微服务·云原生·架构
半旧夜夏10 小时前
【保姆级】微服务组件环境搭建(Docker Compose版)
java·linux·spring cloud·微服务·云原生·容器
AI行业学习12 小时前
.NET Framework 3.5 SP1 完整离线包【2026.5.31】
.net
西凉的悲伤17 小时前
Spring Boot 、Spring Cloud 微服务架构认证授权方案
spring boot·spring cloud·微服务·架构·认证授权
学以智用18 小时前
.NET Core 完整特性速查表(终极版)
后端·.net
j_xxx404_19 小时前
Linux 线程同步硬核解析:从条件变量、阻塞队列到信号量环形队列
linux·运维·服务器·c++·人工智能·ai·中间件
苏渡苇19 小时前
Seata 番外篇:使用 docker-compose 部署 Seata Server(TC)及 K8S 部署 Seata 高可用
spring boot·docker·微服务·容器·kubernetes·seata·springcloud
cdbqss119 小时前
VB2026 动态生成工具栏类 BqGetToolStrip
数据库·oracle·开源·.net·学习方法·教育电商·basic
宝桥南山20 小时前
Microsoft Agent Framework(MAF) - 如何将workflow或者A2A client转换成一个AI Agent
microsoft·ai·微软·aigc·.net·.netcore