ASP.NET Core是否支持JSON Web Signature(RFC7515)用于RESTful API防篡改?
在ASP.NET Core中用JWS保护REST API请求防篡改的实现方案
完全可以用JSON Web Signature (JWS, RFC7515)来保护你的请求内容防篡改,正好适配你这种明文HTTP的场景——毕竟JWS的核心就是对内容进行签名验证,确保传输过程中内容没被篡改,同时内容本身是明文可见的,完全符合你的需求。
下面我会一步步带你在ASP.NET Core里实现这个方案,用自定义输入格式化器来处理JWS格式的请求体,自动完成签名验证并反序列化成你的CreateRequest对象:
1. 准备工作:安装依赖包
首先需要安装微软的JWT处理包,它原生支持JWS的解析和验证:
dotnet add package System.IdentityModel.Tokens.Jwt
2. 实现自定义JWS输入格式化器
我们需要创建一个自定义的InputFormatter,让ASP.NET Core能够识别并处理JWS格式的请求体,自动完成签名验证和反序列化:
using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.Options; using System.IdentityModel.Tokens.Jwt; using System.Text; using System.Text.Json; public class JwsInputFormatter : InputFormatter { private readonly JwtSecurityTokenHandler _tokenHandler; private readonly TokenValidationParameters _validationParams; public JwsInputFormatter(IOptions<JwsValidationSettings> settings) { _tokenHandler = new JwtSecurityTokenHandler(); // 这里用对称密钥示例,生产环境可以换成RSA非对称密钥(更安全) _validationParams = new TokenValidationParameters { ValidateIssuer = false, // 根据业务需求决定是否验证签发者 ValidateAudience = false, // 根据业务需求决定是否验证受众 ValidateLifetime = false, // 如果不需要过期时间验证可以关闭 IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey( Encoding.UTF8.GetBytes(settings.Value.SecretKey)) }; // 注册支持的JWS媒体类型 SupportedMediaTypes.Add("application/jws"); SupportedMediaTypes.Add("application/jose+json"); } public override bool CanRead(InputFormatterContext context) { var contentType = context.HttpContext.Request.ContentType; return contentType != null && (contentType.Contains("application/jws") || contentType.Contains("application/jose+json")); } public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context) { var request = context.HttpContext.Request; using var reader = new StreamReader(request.Body); var jwsString = await reader.ReadToEndAsync(); try { // 验证JWS签名并解析Token _tokenHandler.ValidateToken(jwsString, _validationParams, out var validatedToken); var jwtToken = validatedToken as JwtSecurityToken; if (jwtToken == null) { return await InputFormatterResult.FailureAsync(); } // 将JWS的Payload反序列化成目标Model类型(这里是CreateRequest) var payload = JsonSerializer.Deserialize(jwtToken.Payload.SerializeToJson(), context.ModelType); return await InputFormatterResult.SuccessAsync(payload); } catch (SecurityTokenValidationException ex) { // 签名验证失败,将错误信息添加到ModelState context.ModelState.TryAddModelError(string.Empty, $"JWS签名验证失败: {ex.Message}"); return await InputFormatterResult.FailureAsync(); } catch (Exception ex) { context.ModelState.TryAddModelError(string.Empty, $"处理JWS请求失败: {ex.Message}"); return await InputFormatterResult.FailureAsync(); } } } // 用于配置JWS验证参数的实体类 public class JwsValidationSettings { public string SecretKey { get; set; } = string.Empty; }
3. 注册自定义格式化器到ASP.NET Core管道
在Program.cs中注册我们的格式化器和配置项:
var builder = WebApplication.CreateBuilder(args); // 绑定JWS验证配置(从appsettings.json读取) builder.Services.Configure<JwsValidationSettings>(builder.Configuration.GetSection("JwsValidation")); builder.Services.AddControllers(options => { // 将自定义JWS格式化器添加到输入格式化器列表的最前面 var jwsSettings = builder.Services.BuildServiceProvider().GetRequiredService<IOptions<JwsValidationSettings>>(); options.InputFormatters.Insert(0, new JwsInputFormatter(jwsSettings)); }); var app = builder.Build(); app.UseAuthorization(); app.MapControllers(); app.Run();
然后在appsettings.json中添加密钥配置(注意密钥要足够强,对称密钥建议至少16位以上):
"JwsValidation": { "SecretKey": "your-strong-256-bit-secret-key-here" }
4. 修改控制器Action支持JWS请求
更新你的控制器Action,指定接受JWS格式的请求,并通过ModelState判断签名是否验证通过:
[Produces("application/json")] [Route("api/WebService")] public class WebServiceController : ControllerBase { [HttpPost("CreateRequest")] [Consumes("application/jws", "application/jose+json")] // 指定接受的媒体类型 public IActionResult Create([FromBody] CreateRequest request) { // 先检查JWS签名验证是否通过 if (!ModelState.IsValid) { return BadRequest(ModelState); } if (request == null) { return BadRequest(); } // 处理你的业务逻辑... return Ok(); } } // 你的CreateRequest实体类示例 public class CreateRequest { public string RequestId { get; set; } = string.Empty; public string Content { get; set; } = string.Empty; }
5. 客户端如何发送JWS请求
客户端需要将CreateRequest序列化为JSON,然后生成JWS签名后发送,下面是C#客户端的示例代码:
using System.IdentityModel.Tokens.Jwt; using System.Text; using System.Text.Json; // 与服务端共享的密钥 var secretKey = "your-strong-256-bit-secret-key-here"; var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)); var signingCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256); // 创建要发送的请求对象 var requestObj = new CreateRequest { RequestId = "REQ-001", Content = "Test content" }; var payloadJson = JsonSerializer.Serialize(requestObj); // 生成JWS var tokenHandler = new JwtSecurityTokenHandler(); var jwtToken = new JwtSecurityToken( signingCredentials: signingCredentials, payload: payloadJson ); var jwsString = tokenHandler.WriteToken(jwtToken); // 发送POST请求,指定Content-Type为application/jws using var client = new HttpClient(); var content = new StringContent(jwsString, Encoding.UTF8, "application/jws"); var response = await client.PostAsync("http://your-api-url/api/WebService/CreateRequest", content); response.EnsureSuccessStatusCode();
关键注意事项
- 密钥管理:如果用对称密钥,必须确保客户端和服务端安全共享;生产环境推荐用RSA非对称密钥(客户端用私钥签名,服务端用公钥验证),避免密钥泄露风险。
- 防重放攻击:如果需要防止请求被重复提交,可以开启
ValidateLifetime,并在JWS中添加exp(过期时间)和nbf(生效时间)声明,同时可以结合唯一请求ID做额外校验。 - 性能优化:高并发场景下,可以缓存验证密钥,避免重复创建密钥对象;也可以考虑异步验证逻辑。
内容的提问来源于stack exchange,提问作者Earl Sven
相关产品推荐
相关产品推荐

