百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

Asp-Net-Core开发笔记:使用ActionFilterAttribute实现非侵入式的参数校验

yuyutoo 2024-10-26 16:12 6 浏览 0 评论

前言

在现代应用开发中,确保API的安全性和可靠性至关重要。

面向切面编程(AOP)通过将横切关注点(如验证、日志记录、异常处理)与核心业务逻辑分离,极大地提升了代码的模块化和可维护性。

在ASP.NET Core中,利用ActionFilterAttribute可以方便地实现AOP的理念,能够以简洁、高效的方式进行自定义验证。

本文将分享如何通过创建ValidateClientAttribute来验证客户端ID,并探讨这种方法如何体现AOP的诸多优势。

使用场景

本文使用场景是在我之前开发的单点认证项目中,当时的项目名称是 IdentityServerLite ,作为参考 IdentityServer4 设计的一个轻量级单点认证解决方案,不过我做得还不是很完善,而且属于是边学习 OAuth2.0 和 OpenID Connect 边做的,代码比较乱,关于这个单点认证项目,我后续可能会写一篇文章单独介绍,并且目前有一个重构后开源的计划。

在单点认证项目中,像登录、获取 AccessToken 、请求 Token 等操作都需要验证用户传入的 Client ID 参数是否有效,这部分逻辑是有些重复的,于是我就像使用一种更高效的方式来实现这个功能。

正好上次使用 AOP 的思想来实现非侵入性的审计日志功能,这次同样利用这种思想来实现这个校验功能。

ActionFilterAttribute

我发现之前那俩篇关于审计日志的实现文章没有怎么介绍这个东西

回顾一下:

  • Asp-Net-Core开发笔记:实现动态审计日志功能
  • Asp-Net-Core开发笔记:进一步实现非侵入性审计日志功能

现在再赘述一下~

ActionFilterAttribute 是 ASP.NET Core 提供的一个方便工具,用于在控制器的操作方法执行之前或之后添加自定义逻辑。这种机制使得我们可以在不改变操作方法本身的情况下,插入额外的处理逻辑,如验证、日志记录、异常处理等。这种特性体现了面向切面编程(AOP)的理念,能够有效地分离关注点,提高代码的模块化和可维护性。

通过继承 ActionFilterAttribute,可以重写 OnActionExecutingOnActionExecuted 方法,分别在操作方法执行前后执行自定义逻辑。例如,验证输入参数的有效性、记录请求的执行时间、处理异常等。

理清思路

要实现的功能

  • 根据配置,校验传入的 Client ID 参数是否有效(参数名和参数所在位置都不确定,需要配置)
  • 校验不通过的话返回错误信息
  • 校验通过的话,接口里需要能访问到对应的 Client 对象

如何实现?

首先是确定了这个功能是使用 Attribute 的形式来添加到接口的外边,然后覆盖 ActionFilterAttributeOnActionExecutionAsync方法来实现具体的校验逻辑。

之后还需要把从数据库里查找到的 Client 对象保存到 HttpContext 里,方便接口中使用这个对象。

HttpContext 是 ASP.NET Core 中用于封装 HTTP 请求和响应的对象。它提供了一种访问 HTTP 特定信息的统一方式,包括请求的详细信息、响应的内容、用户信息、会话数据、请求头和响应头等。每次 HTTP 请求对应一个 HttpContext 实例,该实例贯穿请求处理的整个生命周期。

这里我们利用 HttpContext 提供的 Items 这个键值对集合(用于在请求的不同中间件和组件之间共享数据)来共享 Client 对象。

var client = HttpContext.Items["client"] as Client;

开始写代码

ClientIdSource Enum

Client ID 所在的位置不确定,需要在使用的时候配置

定义一个枚举

public enum ClientIdSource {
Query,
Body,
Route,
Header
}

ValidateClientAttribute 实现

Filters 目录中创建 ValidateClientAttribute.cs 文件

根据配置,从指定的位置根据指定的参数名称读取 Client ID ,然后在数据库中查询。

public class ValidateClientAttribute(
ClientIdSource source = ClientIdSource.Query
) : ActionFilterAttribute
{
/// <summary>
/// 客户端ID的参数名称,注意是 DTO 里的属性名称,不是请求体JSON的字段名
/// </summary>
public string ParameterName { get; set; } = "client_id";

/// <summary>
/// 设置验证成功之后,存储在 `HttpContext.Items` 对象中的 `Client` 对象的 key
/// </summary>
public string ClientItemKey { get; set; } = "client";

public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
var clientId = "";

switch (source) {
case ClientIdSource.Query:
clientId = context.HttpContext.Request.Query[ParameterName];
break;
case ClientIdSource.Body:
// 使用反射从请求体中读取 client_id
// 这里读取到的 body 是 Controller 下 Action 方法的第一个参数,通常是请求体中的 JSON 数据模型绑定转换为对应 DTO 实例
var body = context.ActionArguments.Values.FirstOrDefault();
if (body != ) {
var clientProp = body.GetType().GetProperty(ParameterName);
if (clientProp != ) {
clientId = clientProp.GetValue(body) as string;
}
}
break;
case ClientIdSource.Route:
clientId = context.RouteData.Values[ParameterName] as string;
break;
case ClientIdSource.Header:
clientId = context.HttpContext.Request.Headers[ParameterName];
break;
}

if (string.IsOrWhiteSpace(clientId)) {
throw new ArgumentException(ParameterName);
}

var clientRepo = context.HttpContext.RequestServices.GetRequiredService<IBaseRepository<Client>>();
var client = await clientRepo.Select.Where(a => a.ClientId == clientId).FirstAsync();

if (client != ) {
context.HttpContext.Items["client"] = client;
await next();
}
else {
context.Result = new NotFoundObjectResult(
new ApiResponse { Message = $"client with id {clientId} not found" });
}
}
}

有几点需要注意的,下面介绍一下

通过反射获取 request body 的参数

其他几个参数位置还好,获取都比较容易

如果是 POST 或者 PUT 方法,一般都是把数据以 JSON 的形式放在 Request Body 里

这个时候,我们可以去读取这个 Body 的值,但读取完之后得自己解析 JSON,还得把 Stream 写回去,有点麻烦。而且如果 Body 是 XML 形式,还要用其他的解析方式。

这里我使用了反射的方式,让 AspNetCore 框架去处理这个 Request Body ,然后我直接用反射,根据参数名去读取 Client ID

使用

这是几个使用例子

参数在 Body 里

然后 DTO 里的参数名是 ClientId

public class PwdLoginDto : LoginDto {
[Required]
[JsonPropertyName("username")]
public string Username { get; set; }

[Required]
[JsonPropertyName("password")]
public string Password { get; set; }
}

在接口中使用

[HttpPost("login/password")]
[ValidateClient(ClientIdSource.Body, ParameterName = "ClientId")]
public async Task<IActionResult> LoginByPassword(PwdLoginDto dto) {

}

参数在 Query Params 里

参数名称是 client_id

[HttpGet("authorize/url")]
[ValidateClient(ClientIdSource.Query, ParameterName = "client_id")]
public ApiResponse<string> GetAuthorizeUrl([FromQuery] AuthorizeInput input) {
return new ApiResponse<string>(GenerateAuthorizeUrl(input));
}

参考资料

  • https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters
  • https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context


相关推荐

ETCD 故障恢复(etc常见故障)

概述Kubernetes集群外部ETCD节点故障,导致kube-apiserver无法启动。...

在Ubuntu 16.04 LTS服务器上安装FreeRADIUS和Daloradius的方法

FreeRADIUS为AAARadiusLinux下开源解决方案,DaloRadius为图形化web管理工具。...

如何排查服务器被黑客入侵的迹象(黑客 抓取服务器数据)

---排查服务器是否被黑客入侵需要系统性地检查多个关键点,以下是一份详细的排查指南,包含具体命令、工具和应对策略:---###**一、快速初步检查**####1.**检查异常登录记录**...

使用 Fail Ban 日志分析 SSH 攻击行为

通过分析`fail2ban`日志可以识别和应对SSH暴力破解等攻击行为。以下是详细的操作流程和关键分析方法:---###**一、Fail2ban日志位置**Fail2ban的日志路径因系统配置...

《5 个实用技巧,提升你的服务器安全性,避免被黑客盯上!》

服务器的安全性至关重要,特别是在如今网络攻击频繁的情况下。如果你的服务器存在漏洞,黑客可能会利用这些漏洞进行攻击,甚至窃取数据。今天我们就来聊聊5个实用技巧,帮助你提升服务器的安全性,让你的系统更...

聊聊Spring AI Alibaba的YuQueDocumentReader

序本文主要研究一下SpringAIAlibaba的YuQueDocumentReaderYuQueDocumentReader...

Mac Docker环境,利用Canal实现MySQL同步ES

Canal的使用使用docker环境安装mysql、canal、elasticsearch,基于binlog利用canal实现mysql的数据同步到elasticsearch中,并在springboo...

RustDesk:开源远程控制工具的技术架构与全场景部署实战

一、开源远程控制领域的革新者1.1行业痛点与解决方案...

长安汽车一代CS75Plus2020款安装高德地图7.5

不用破解原车机,一代CS75Plus2020款,安装车机版高德地图7.5,有红绿灯读秒!废话不多讲,安装步骤如下:一、在拨号状态输入:在电话拨号界面,输入:*#518200#*(进入安卓设置界面,...

Zookeeper使用详解之常见操作篇(zookeeper ui)

一、Zookeeper的数据结构对于ZooKeeper而言,其存储结构类似于文件系统,也是一个树形目录服务,并通过Key-Value键值对的形式进行数据存储。其中,Key由斜线间隔的路径元素构成。对...

zk源码—4.会话的实现原理一(会话层的基本功能是什么)

大纲1.创建会话...

Zookeeper 可观测性最佳实践(zookeeper能够确保)

Zookeeper介绍ZooKeeper是一个开源的分布式协调服务,用于管理和协调分布式系统中的节点。它提供了一种高效、可靠的方式来解决分布式系统中的常见问题,如数据同步、配置管理、命名服务和集群...

服务器密码错误被锁定怎么解决(服务器密码错几次锁)

#服务器密码错误被锁定解决方案当服务器因多次密码错误导致账户被锁定时,可以按照以下步骤进行排查和解决:##一、确认锁定状态###1.检查账户锁定状态(Linux)```bash#查看账户锁定...

zk基础—4.zk实现分布式功能(分布式zk的使用)

大纲1.zk实现数据发布订阅...

《死神魂魄觉醒》卡死问题终极解决方案:从原理到实战的深度解析

在《死神魂魄觉醒》的斩魄刀交锋中,游戏卡死犹如突现的虚圈屏障,阻断玩家与尸魂界的连接。本文将从技术架构、解决方案、预防策略三个维度,深度剖析卡死问题的成因与应对之策,助力玩家突破次元壁障,畅享灵魂共鸣...

取消回复欢迎 发表评论: