【.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>();

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

三、总结

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

相关推荐
步步为营DotNet24 分钟前
深度探究Span:.NET内存布局与零拷贝原理及实践
.net
码界奇点2 小时前
基于微服务架构的分布式量化交易系统设计与实现
分布式·微服务·架构·车载系统·毕业设计·源代码管理
Haooog2 小时前
微服务保护学习
java·学习·微服务·sentinel
GIOTTO情2 小时前
技术深度:Infoseek 媒体发布系统的微服务架构与二次开发实战
微服务·架构·媒体
拾忆,想起2 小时前
Dubbo服务调用幂等性深度解析:彻底解决重复请求的终极方案
微服务·性能优化·服务发现·dubbo
拾忆,想起2 小时前
Dubbo深度解析:从零到一,高性能RPC框架如何重塑微服务架构
网络协议·微服务·云原生·性能优化·rpc·架构·dubbo
专注VB编程开发20年2 小时前
多线程解压安装ZIP,EXE分析-微软的MSI安装包和 .NET SDK EXE
linux·运维·服务器·microsoft·.net
安当加密3 小时前
动态脱敏在微服务网关中的实现原理
微服务·云原生·架构
脸大是真的好~3 小时前
黑马消息队列-rabbitMQ2-生产者重连机制-生产者确认机制-数据持久化-LazyQueue-消费者确认机制-失败重试机制-重试耗尽告警手动处理-
java·缓存·中间件
Hernon15 小时前
微服务架构设计 - 可降级设计
微服务·云原生·架构