这篇文章我们讲解权限中最关键的地方:访问权限中间件。这个中间件可以帮助我们在请求到达Controller前进行权限验证,并且在不具备权限时通知客户端。
一、需求
我们先来可以下需求:
编号 | 功能 | 描述 |
---|---|---|
1 | 权限验证 | 根据角色和请求的URL来验证是否有访问URL的权限,如果没有则返回401状态码,并告知客户端,反之允许客户端使用URL |
二、编写代码
首先,我们需要在ISysRoleUrlServer
接口和SysRoleUrlImp
实现类中增加IsRoleUseUrl
方法,代码如下:
//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
,代码如下:
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;}//解析tokenstring token = httpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();if (token == null){httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;await httpContext.Response.WriteAsync("权限不足:用户无权访问此资源。");}else{// 验证并解析 Tokenvar 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);// 访问 Claimsvar 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
类中把前面我们编写的中间件注入到项目中:
app.UseMiddleware<PermissionsMiddleware>();
到此,我们的中间件就完成了。