前面我们通过三篇文章在网关中集成了认证功能,这样我们编写的通用中间件ApplicationMiddleware
的功能就和网关的认证功能就重复了,因此我们就需要修改它的功能。那么,我们开始吧。
一、修改通用中间件的功能
目前ApplicationMiddleware
的功能是首先从请求头中获取 Authorization
,如果是 Bearer
格式则解析出 JWT Token;然后提取其中的 UserId
、UserName
等声明(Claims)。若 HttpContext.User
尚无身份信息,则创建新的 ClaimsPrincipal
,否则将缺失的用户声明合并到现有身份中。若 Token 不存在或解析失败,则抛出 UnauthorizedException
表示未登录。最后调用 _next(context)
将请求传递给下一个中间件。
我们在重构ApplicationMiddleware
后的功能如下:
- 验证网关签名(X-Gateway-Signature),确保请求来自网关且在5分钟内有效
- 支持匿名访问(X-Anonymous)
- 从请求头获取用户信息(X-User-Id等)并创建身份认证
- 将请求传递给下一个中间件
这样既保证了系统的安全性和可靠性,又简化了身份认证流程,同时提供了更灵活的访问控制机制,使整个系统更加健壮和易于维护。具体实现代码如下:
csharp
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using SP.Common.ExceptionHandling.Exceptions;
namespace SP.Common.Middleware;
/// <summary>
/// 应用程序中间件,所有微服务都要引入
/// </summary>
public class ApplicationMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ApplicationMiddleware> _logger;
private readonly IConfiguration _configuration;
/// <summary>
/// 应用程序中间件构造函数
/// </summary>
/// <param name="next">下一个中间件</param>
/// <param name="logger">日志记录器</param>
/// <param name="configuration">配置</param>
public ApplicationMiddleware(RequestDelegate next, ILogger<ApplicationMiddleware> logger, IConfiguration configuration)
{
_next = next;
_logger = logger;
_configuration = configuration;
}
/// <summary>
/// 中间件处理请求
/// </summary>
/// <param name="context">HTTP上下文</param>
/// <returns>异步任务</returns>
public async Task InvokeAsync(HttpContext context)
{
// 验证网关签名
if (!ValidateGatewaySignature(context))
{
_logger.LogWarning("检测到未授权的直接访问,IP: {IP}", context.Connection.RemoteIpAddress);
throw new UnauthorizedException("未授权的访问");
}
if (context.Request.Headers.ContainsKey("X-Anonymous"))
{
await _next(context);
return;
}
// 从header中获取用户信息
var userId = context.Request.Headers["X-User-Id"].FirstOrDefault();
var username= context.Request.Headers["X-User-Name"].FirstOrDefault();
var email = context.Request.Headers["X-User-Email"].FirstOrDefault();
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(email))
{
_logger.LogError("请求头中缺少用户信息");
throw new UnauthorizedException("未登录");
}
// 创建 ClaimsIdentity 并添加 claims
var claims = new List<Claim>();
if (!string.IsNullOrEmpty(userId))
claims.Add(new Claim("UserId", userId));
if (!string.IsNullOrEmpty(username))
claims.Add(new Claim("UserName", username));
if (!string.IsNullOrEmpty(email))
claims.Add(new Claim("Email", email));
var identity = new ClaimsIdentity(claims, "header");
context.User = new ClaimsPrincipal(identity);
await _next(context);
}
private bool ValidateGatewaySignature(HttpContext context)
{
try
{
var signature = context.Request.Headers["X-Gateway-Signature"].FirstOrDefault();
if (string.IsNullOrEmpty(signature))
{
return false;
}
var signatureBytes = Convert.FromBase64String(signature);
var signatureText = System.Text.Encoding.UTF8.GetString(signatureBytes);
var parts = signatureText.Split('.');
if (parts.Length != 2)
{
return false;
}
if (!long.TryParse(parts[0], out var timestamp))
{
return false;
}
var secret = _configuration["GatewaySecret"] ?? "SP_Gateway_Secret_2024";
if (parts[1] != secret)
{
return false;
}
// 验证时间戳(5分钟内的请求有效)
var currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var timeDiff = Math.Abs(currentTimestamp - timestamp);
if (timeDiff > 300) // 5分钟
{
return false;
}
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "验证网关签名时发生错误");
return false;
}
}
}
在修改后的中间件中,我们引入了一个至关重要的安全验证机制,即网关签名验证。这一机制是专门为微服务架构设计的安全保护措施,其核心目标是确保所有请求必须经过网关进行转发,禁止任何尝试绕过网关直接访问微服务的行为。这种设计不仅能有效防止非法请求,还能抵御重放攻击和中间人攻击,从而显著提升整个系统的安全性与稳定性。在实现上,网关签名验证依赖于一个特殊的请求头字段 X-Gateway-Signature
,该字段的值不是简单的字符串,而是经过 Base64 编码的复杂格式数据。签名字符串内部由两部分组成,用英文句点(.
)分隔,第一部分是 Unix 时间戳,第二部分是网关密钥。时间戳采用十位格式,表示自 1970 年 1 月 1 日 UTC 零时起至当前时刻的秒数,这样的设计可以防止重放攻击,即攻击者即使截获了请求,也无法在未来重新发送以欺骗系统,因为系统会对时间戳进行严格验证,通常限制在五分钟内有效。网关密钥是一个预先配置的字符串值,可以通过配置文件中的 GatewaySecret
项进行指定,如果用户没有配置,则系统会使用默认值 SP_Gateway_Secret_2024
。网关密钥的存在确保了只有网关和微服务端知道这个密钥,任何试图伪造请求的行为都会因为密钥不匹配而被拒绝,从而有效保护了微服务的接口安全。
当请求到达微服务时,中间件会首先检查请求是否包含 X-Gateway-Signature
请求头,如果缺少该字段,则说明请求并非经过网关转发,立即被判定为非法请求并返回错误。接着,中间件将签名字符串进行 Base64 解码,以获取原始的时间戳和网关密钥信息,如果解码失败则说明签名格式异常,请求同样会被拒绝。解码后的字符串会按照句点进行拆分,从而分别获取时间戳和网关密钥。时间戳会被转换为数值类型,然后与系统当前时间进行对比,如果时间差超过五分钟,则说明请求已过期。网关密钥则会与配置文件中指定的密钥进行比对,如果不匹配则说明请求不是来自合法网关,也会被拒绝。只有当时间戳在有效范围内且网关密钥正确时,请求才会被允许继续传递给下一个中间件或微服务处理逻辑。通过这种双重验证机制,网关签名验证能够有效防止请求被篡改或重放,同时保证请求来源的合法性。
在具体实现中,这一过程的代码逻辑相对简洁但功能完整。中间件首先通过 RequestDelegate
获取请求上下文,然后尝试读取请求头 X-Gateway-Signature
的值,如果不存在则直接返回 401 状态码。接着对签名进行 Base64 解码,并按照句点拆分出时间戳和密钥部分。如果拆分后的长度不符合预期,说明签名格式错误,中间件会立即返回错误。随后将时间戳与当前 UTC 时间进行比较,如果请求时间超过五分钟,说明请求已过期,会被拒绝。最后将解码得到的网关密钥与配置文件中的密钥进行比对,如果不一致,也会返回错误。整个验证流程设计得既严密又高效,即使在高并发场景下,也不会对系统性能造成明显影响。通过这一机制,微服务可以完全信任来自网关的请求,避免任何未经授权的访问。
网关签名验证机制不仅增强了系统安全性,还为微服务架构提供了可靠的安全保障。它可以有效阻止攻击者绕过网关直接访问服务端点,保证所有请求都必须经过统一的入口。由于签名中包含时间戳信息,系统能够在请求到达时识别过期请求,从而防止重放攻击。这种设计对于保护敏感业务数据和核心接口尤其重要,例如在金融、支付和医疗等高安全需求的场景中,任何非法访问或请求篡改都可能带来严重风险。网关密钥的配置灵活性也使得运维团队可以根据需要随时更换密钥,进一步增强系统安全性,同时默认值保证了系统开箱即用的便捷性。
除了自身的安全优势,网关签名验证机制通常还会与其他安全手段结合使用。在实际生产环境中,它可以与用户身份认证机制如 JWT 一起工作,网关签名确保请求来源合法,而 JWT 则用于验证用户身份,双重验证能够提供更高的安全保障。同时,通过在 HTTPS 传输层加密请求,可以防止请求内容在传输过程中被窃听或篡改,使签名验证与传输安全形成互补。此外,在一些高安全要求的场景中,还可以配合 IP 白名单机制,进一步限制只有网关所在服务器的 IP 能够访问微服务接口。通过这种多层安全设计,系统不仅能够抵御常规的攻击手段,还能有效防范高级持续威胁(APT),确保微服务在复杂网络环境中的稳定性和可靠性。
网关签名验证是微服务安全体系中非常重要的一环。它通过在请求中增加包含时间戳和网关密钥的签名,实现对请求来源和时效的严格校验,从而防止请求被篡改、重放或伪造,同时确保所有请求必须经过网关转发。该机制实现简单、性能开销低,却能够提供显著的安全保护,对于任何依赖网关的微服务架构来说,都是一种高效、可靠且易于推广的安全方案。并且通过这种设计,开发者和运维团队可以在保持系统灵活性和可扩展性的同时,获得稳固的安全保障,从根本上提升系统的整体安全水平。
二、总结
本文主要讲解了通用中间件ApplicationMiddleware
的重构过程。为避免与网关认证功能重复,新版中间件实现了网关签名验证机制,通过X-Gateway-Signature
请求头进行验证,包含时间戳和网关密钥两部分,有效防止请求篡改和重放攻击。中间件还支持匿名访问,并能从请求头提取用户信息创建身份认证。这些改进既保证了系统安全性,又简化了认证流程,为微服务架构提供了可靠的安全保障。