news 2026/10/1 10:58:18

.NET6 WebApi JWT 鉴权实战:从登录签发到 Swagger 调试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
.NET6 WebApi JWT 鉴权实战:从登录签发到 Swagger 调试

简介:针对.NET6平台WebApi开发中的用户鉴权需求,这份资源提供了一套基于JWT的完整示例源码,适合熟悉C#语法、希望在接口层快速加入身份验证能力的开发者参考。示例从初始化WebApi项目开始,依次演示引入令牌处理类库、实现用户登录并签发JWT、在控制器上标记授权特性,以及启用Swagger并配置JWT认证选项的完整链路,可帮助理解鉴权在真实项目中的落地方式。压缩包为RAR格式,共68个文件,大小约1.43MB。包内除C#源码(cs)外,还包含sln/csproj工程文件、json配置与依赖缓存文件,以及编译生成的dll/exe等程序集,便于直接阅读代码或运行验证。项目按AuthenticationOperation、AuthenticationService、AuthenticationModel等模块分层组织,分别承担操作处理、服务逻辑与数据模型定义,同时通过AuthenticationController与WeatherForecastController展示了受保护接口和公开接口的对比,结构清晰,易于二次开发。资源已有4503人学习下载,可作为入门参考或项目模板。开发者通过该示例可快速掌握JWT生成、校验与授权拦截的核心流程,并在此基础上扩展自定义角色和权限控制,为中小型WebApi应用加固安全机制。

1. 用 .NET6 给 WebApi 上 JWT 用户鉴权:先解决登录态从哪来

WebApi 接口天生无状态,每个请求进来都像陌生人,这时就得靠 JWT 充当身份证。.NET6 平台的 WebApi 项目接上 JwtBearer 中间件,登录成功后给用户签发一个加密签名的 Token,后续请求在 Authorization 请求头里带上它,服务端验签通过就认人,不用存 Session,也不怕多实例部署时登录态丢失。对前后端分离、小程序后端、多端共用一套 API 的场景来说,这是落地最快、扩展性最好的用户鉴权方案之一。这篇文章按一套可直接下载测试的源码思路来拆:从新建项目、签发 Token、保护接口,到 Swagger 带 Token 调试,再到 401、Swagger 404 以及发布后常见的翻车点,最后补上自动化验证和生产级习惯。适合已经会创建 ASP.NET Core 项目、想给自己的接口加上登录鉴权的开发者。

2. JWT 的 Header、Payload、Signature 各管什么:先懂结构再装 JwtBearer

2.1 三段式结构的职责边界,以及为什么签名段不能省

JWT 不是加密数据,它是签名数据,这一点第一次接触的人最容易搞混。一个标准 Token 长这样:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTcxMDAwMDAwMH0.signature,两个点分隔成三段,每段都是 Base64Url 编码,不带=填充符。

Header 是第一段,声明签名算法和令牌类型,常见内容是{"alg":"HS256","typ":"JWT"}。对接多个认证源或做密钥轮换时,这里还会出现kid字段,告诉校验方用哪一把 key 来验签。Payload 是第二段,用户 ID、角色、过期时间、自定义业务字段都放在这里,登录后需要识别用户身份就靠它。Signature 是第三段,把前两段拼起来用 alg 指定的算法和密钥算出来的签名,校验方验签通过,才证明 Token 没被篡改、确实是本系统签发的。

校验方实际只做三件事:读 Header 知道算法,读 Payload 拿到 claims,用密钥重算签名并与第三段比对。签名一致就信任 Payload 里的 userId 和 role。这也是 JWT 能当用户鉴权凭证而不用每次查库的底层原因。同时这段结构也决定了它的边界:Token 是"持有即拥有",谁拿到谁就能用,直到过期,所以有效期、密钥强度、传输安全一个都不能省。

2.2 .NET6 里 JWT 鉴权的三件套:JwtBearer、TokenValidationParameters 与授权中间件

在 .NET6 里做 JWT 用户鉴权,核心不是自己写解析代码,而是让 JwtBearer 中间件接管。它承担三个职责:从 HTTP 请求头解析Authorization: Bearer <token>;按你配置的 TokenValidationParameters 做签名、颁发者、受众、有效期校验;校验通过后把 Payload 里的 claims 包装成HttpContext.User,供控制器里用[Authorize]和User.Identity读取。

这套链路在 Program.cs 里分成两步注册。第一步是AddAuthentication与AddJwtBearer,负责"认证",回答"你是谁";第二步是AddAuthorization,负责"授权",回答"你能干什么"。运行时UseAuthentication先执行认证管道,UseAuthorization再根据角色和策略放行或拒绝。这个顺序一旦颠倒,比如漏了app.UseAuthentication(),结果就是所有带[Authorize]的接口永远 401,后面第 5 章会专门讲。

TokenValidationParameters 是验签行为的完整开关列表,四个 Validate 开关平时建议全开:ValidateIssuer 校验签发方、ValidateAudience 校验受众、ValidateLifetime 校验有效期、ValidateIssuerSigningKey 校验签名密钥。这四个开关全开,再加上正确的 ValidIssuer、ValidAudience、IssuerSigningKey,才算一个生产可用的校验配置,而不是只验个签名就放行。

2.3 生成 Token 的两条路线:手写 JWT 还是用 IdentityModel 类库

生成 Token 有两条路线。一条是自己拼三段式字符串:先 Base64Url 编码 Header 和 Payload,再用 HMAC-SHA256 算签名。适合教学演示,但容易踩坑,比如忘了移除 Base64 填充符=、算法标记没对齐、时间戳格式写错,这些问题在跨语言校验时很容易翻车。

另一条是System.IdentityModel.Tokens.Jwt包里的JwtSecurityTokenHandler,把 claims、issuer、audience、expires、signingCredentials 传进去,由类库负责序列化和签名,生成结果天然兼容 JwtBearer 的校验逻辑。我一般在可复现的测试源码里选后者,少写约三十行易错代码,后续要切 RSA、要换算法,改动也小得多。

对比项手写三段式JwtSecurityTokenHandler
代码量多,需自己处理 Base64Url 与算法细节少,集中配置一段
规范兼容容易产生非标准 Token标准实现,自动处理
与 JwtBearer 配合需严格对齐 claims 命名开箱即用
适用场景学习原理、轻量自定义生产 API、测试源码

这里顺带说清一个容易翻车的点:Payload 里的 claim 名,JWT 社区惯例用短名,比如sub、role,而 .NET 的 ClaimTypes 是长 URI。JwtSecurityTokenHandler 默认会做 inbound 映射,把sub映射成ClaimTypes.NameIdentifier,但 JwtBearer 在新版本里的映射策略并不完全一致,拿不到用户 ID 的问题常出在这里,具体解法放到第 5 章。

3. 从零跑通 JWT 签发的 WebApi:项目创建、配置、登录接口与 Swagger 调试

3.1 创建 .NET6 WebApi 项目并安装 JwtBearer 包

先建项目,再装包。命令行是:

dotnet new webapi -n MyJwtApi -f net6.0 cd MyJwtApi dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer dotnet build

逻辑说明:-f net6.0明确指定目标框架,避免本机装了更高版本 SDK 后模板默认生成到新版运行时,导致后面引用的包版本和你实际跑的框架不一致。dotnet add package不加版本号时,NuGet 会按当前项目的目标框架自动匹配兼容版本,在 net6.0 项目里实际拿到的是 6.0.x 系列。

参数说明:模板自带了 Controllers 和 Swashbuckle.AspNetCore,不需要额外加。如果你建的是空模板,需要手动注册AddControllers()和 Swagger。装完包后先dotnet build一次,确认 JwtBearer 引用的 IdentityModel 系列包都拉下来了,再继续改代码,避免写完后才被编译错误打断思路。

3.2 配置 appsettings.json:Issuer、Audience、密钥与过期时间

JWT 的四项配置集中放在一个 Jwt 节点下,便于后面在 Program.cs 里一次性读取:

{ "Jwt": { "Issuer": "MyApiServer", "Audience": "MyApiClient", "SecretKey": "My-Super-Secret-Key-For-Jwt-Demo-2024", "ExpireMinutes": 120 }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" }

逻辑说明:这四个配置项分别对应签发的iss、aud和 Token 存活时长,以及验签时用的对称密钥。Issuer 和 Audience 是字符串标识,不一定非得是 URL;密钥只放在服务端,前端和客户端不需要知道。校验时这四个值要和签发时完全一致,大小写敏感。

参数说明:密钥长度建议 32 字节以上,因为 HS256 签名算法对这个长度有下限要求,短了会直接抛异常。演示项目把密钥写在 appsettings 里方便跑通,生产环境千万别这样干,密钥应该用环境变量Jwt__SecretKey注入,并且不要提交到代码仓库。ExpireMinutes 测试时可以设 120,方便调式;生产建议 30 到 60 分钟,再配刷新令牌兜底。

3.3 在 Program.cs 注册鉴权与授权服务,并理清中间件顺序

Program.cs 是整套鉴权的装配现场:

using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var jwtSection = builder.Configuration.GetSection("Jwt"); var secretKey = jwtSection["SecretKey"] ?? throw new InvalidOperationException("Jwt:SecretKey 未配置"); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtSection["Issuer"], ValidAudience = jwtSection["Audience"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(secretKey)), ClockSkew = TimeSpan.FromMinutes(1) }; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();

逻辑说明:AddAuthentication(JwtBearerDefaults.AuthenticationScheme)把 JWT Bearer 设为默认认证方案,凡是被[Authorize]标记的接口都会自动走 JwtBearer 校验。TokenValidationParameters 里的四个 Validate 开关分别管签名密钥、颁发者、受众和有效期,全开是生产的基本要求。IssuerSigningKey 用同一个密钥字节数组,注册给验签环节使用。

参数说明:ClockSkew默认值是 5 分钟,设计目的是容忍不同服务器之间的时间偏差,但演示项目我一般设成 1 分钟,更贴近真实过期行为,也方便测试过期逻辑。中间件顺序上,UseAuthentication()必须在UseAuthorization()之前,并且两者都必须在MapControllers()之前注册。漏掉UseAuthentication()是 401 的头号原因。

3.4 写登录接口签发 Token,受保护接口验证效果

新建一个 AuthController,登录成功之后签发 Token,再用一个带[Authorize]的接口验证效果:

using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; namespace MyJwtApi.Controllers; public record LoginRequest(string UserName, string Password); [ApiController] [Route("api/[controller]")] public class AuthController : ControllerBase { private readonly IConfiguration _config; public AuthController(IConfiguration config) => _config = config; [HttpPost("login")] [AllowAnonymous] public IActionResult Login(LoginRequest request) { if (request.UserName != "admin" || request.Password != "123456") return Unauthorized(new { message = "用户名或密码错误" }); var token = CreateToken(request.UserName, "admin"); return Ok(new { token, tokenType = "Bearer" }); } [HttpGet("me")] [Authorize] public IActionResult Me() { var name = User.Identity?.Name; var role = User.FindFirst(ClaimTypes.Role)?.Value; return Ok(new { name, role }); } private string CreateToken(string userName, string role) { var jwt = _config.GetSection("Jwt"); var key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(jwt["SecretKey"]!)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(ClaimTypes.Name, userName), new Claim(ClaimTypes.Role, role) }; var token = new JwtSecurityToken( issuer: jwt["Issuer"], audience: jwt["Audience"], claims: claims, expires: DateTime.Now.AddMinutes( Convert.ToInt32(jwt["ExpireMinutes"])), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } }

逻辑说明:login 接口是匿名入口,比对通过后才进入 CreateToken。JwtSecurityToken 构造器接收的 issuer、audience、claims 会写进 Payload,expires 控制 Token 生命周期,signingCredentials 决定用哪个算法和密钥做签名。最后WriteToken负责把对象序列化成标准三段的 JWT 字符串。

参数说明:claims 里的角色用ClaimTypes.Role,这是后面做RequireRole策略授权的关键。这里登录用了固定账号,测试源码里你可以把它换成 EF Core 或 Dapper 查用户表,再把用户唯一 ID 作为ClaimTypes.NameIdentifier写入,Me 接口就能返回真正的用户标识。演示代码用DateTime.Now没毛病,正式项目建议统一 UTC 避免时区歧义。

3.5 给 Swagger 加 Authorization 输入框,调试时不缺请求头

Swagger 默认不知道你的接口需要带 Token,得在 AddSwaggerGen 里补充安全定义:

// Program.cs 中 AddSwaggerGen 部分 using Microsoft.OpenApi.Models; builder.Services.AddSwaggerGen(c => { c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "格式:Bearer {token}", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty<string>() } }); });

逻辑说明:AddSecurityDefinition 声明了一个名为 Authorization 的请求头参数;AddSecurityRequirement 把这个安全方案应用到接口定义上。配置完成后 Swagger 页面右上角出现 Authorize 按钮,填入 Token 后调试请求会自动携带标准请求头。

参数说明:Type必须写SecuritySchemeType.ApiKey,In必须是Header,Name必须精确为Authorization。填 Token 时输入Bearer <token>整段,不要只填 token 本身,否则拼接出来的请求头缺 Bearer 前缀,会直接 401。

4. JWT 续签、自定义 Claims 与策略授权:把鉴权从能用做到好用

4.1 jwt 续签怎么落地:刷新令牌与自动续签方案

单 Token 过期后用户会被迫重新登录,体验很割裂,所以 Token 续签是生产项目绕不开的设计。常见做法有三种:短期 Access Token 配长期 Refresh Token;前端捕获 401 后自动调刷新接口重放原请求;或者干脆把 Token 有效期拉长,简单但对安全不友好,泄露后长时间有效。

前两种可以组合使用。登录时同时签发两个 Token:Access Token 短命,30 到 60 分钟;Refresh Token 长命,7 到 30 天,只用来换新 Access Token。刷新接口校验 Refresh Token 后重新签发。代码骨架这样:

// 登录时生成 refreshToken,存哈希并关联用户 var refreshToken = Guid.NewGuid().ToString("N"); _refreshStore.TryAdd(refreshToken, request.UserName); // 生产用 Redis/库存储哈希 // 刷新接口 [HttpPost("refresh")] [AllowAnonymous] public IActionResult Refresh(RefreshRequest req) { if (!_refreshStore.TryRemove(req.RefreshToken, out var userName)) return Unauthorized(new { message = "刷新令牌无效或已失效" }); var newAccess = CreateToken(userName, "admin"); return Ok(new { token = newAccess }); }

逻辑说明:Refresh Token 选用随机字符串而不是 JWT,是因为它只充当"换票凭证",不需要携带用户身份。服务端要把记录删除来作废它,这是对 JWT 无状态特性的补丁:Access Token 本身签发后无法主动作废,但刷新通道可以做到。

参数说明:Refresh Token 建议生成 128 位以上随机数,库里存哈希而不是明文。每次刷新就轮换一个新的,并且限制只能使用一次,防止重放攻击。存储记录要带过期时间,过期后自动清理,避免表越积越大。前端在收到 401 时先调刷新接口,成功则重放原请求,用户几乎无感。

4.2 把用户 ID 和角色写进 Claims,用策略授权控制接口

Claim 是 JWT 携带用户信息的最小单元。登录接口里已经用了ClaimTypes.Name和ClaimTypes.Role,这是最基础的两类。实际项目还会把用户 ID、租户 ID、权限码都塞进去,然后在 Program.cs 注册策略:

builder.Services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("admin")); options.AddPolicy("OwnerOnly", policy => policy.RequireClaim("ownerId")); });

控制器用法:

[HttpGet("admin")] [Authorize(Policy = "AdminOnly")] public IActionResult AdminData() => Ok("仅管理员可见"); [HttpGet("owner/{ownerId}")] [Authorize(Policy = "OwnerOnly")] public IActionResult OwnerData(string ownerId) { var myId = User.FindFirst("ownerId")?.Value; if (myId != ownerId) return Forbid(); return Ok("归属校验通过"); }

逻辑说明:策略授权把"哪些角色能访问"从控制器里抽出来,集中配置。RequireRole 读取 JWT Payload 里 role claim 的值,RequireClaim 要求请求者必须携带指定 claim。OwnerData 接口进一步把 claim 值和路由参数比对,实现了简单的数据归属校验。

参数说明:自定义 claim 的字符串名要和签发时完全一致,大小写敏感。读取时User.FindFirst("ownerId")用的是原始 claim 名;如果你签发时写的是ClaimTypes.NameIdentifier长 URI,读取时也要写对应名称,否则拿不到值。策略可以在一个接口上叠加,多策略时必须全部满足才放行。

4.3 JWT 发包格式:Authorization 请求头的规范写法

JWT 在 HTTP 请求里走 Authorization 请求头,标准格式是Bearer <token>。Bearer 和 Token 之间只有一个空格,Token 整段放一个请求头里,不拆行、不加引号。Postman 里可以直接在 Authorization 面板选 Bearer Token 类型填充;Swagger 里则在 Authorize 弹窗粘贴Bearer <token>整串。不少 401 排查到最后,原因只是这里多了一个空格或者少写了 Bearer。

手动解析时,规范校验是:按空格拆分请求头,第一部分等于 Bearer,第二部分是非空的三段式字符串,再交给 TokenValidationParameters 验证签名和有效期。curl 调用是这个格式:

curl -X GET "https://localhost:5001/api/auth/me" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9.abc123signature"

逻辑说明:JwtBearer 中间件自动完成上述拆分和校验,业务代码里通常看不到这段逻辑。但排查客户端问题时知道规范能快速定位:是前端忘了带请求头,还是带了但格式不对。

参数说明:Bearer 按规范不区分大小写,但客户端普遍按惯例首字母大写。JWT 本身是 URL 安全的 Base64,包含字母、数字、-和_,不会出现=填充符;如果你看到 Token 中间带=,那基本不是标准 JWT,可能是某种变体实现,需要回到签发端确认。

5. JWT 鉴权踩坑排查:401、Swagger 404、密钥与时钟偏移

5.1 接口永远 401:漏了 UseAuthentication 或注册顺序不对

现象:登录接口正常返回 Token,但所有带[Authorize]的接口不管 Token 怎么传都返回 401,Swagger 里也看不到具体原因。

原因:Program.cs 里调用了AddAuthentication和AddAuthorization,却只写了app.UseAuthorization(),漏掉了app.UseAuthentication()。认证中间件负责解析并填充用户身份,授权中间件只负责判断是否放行;前者没执行,后者看到的永远是匿名用户。

解决:确认app.UseAuthentication()和app.UseAuthorization()都在,且前者在后者之前,两者都在app.Run()前。如果依然 401,在 JwtBearer 的OnTokenValidated事件里断点看一下有没有走进来;没走进来,说明请求头里的 Token 根本没被中间件接收到,回去查客户端请求头格式。

5.2 发布后 Swagger 提示 not found /swagger/v1/swagger.json

现象:本地调试 Swagger 一切正常,用 vs 发布 webapi 项目到 IIS 或文件夹后,访问/swagger页面报 404,或者提示 not found /swagger/v1/swagger.json。

原因:dotnet 模板生成的 Program.cs 默认只在app.Environment.IsDevelopment()时启用 UseSwagger 和 UseSwaggerUI。发布后环境变量 ASPNETCORE_ENVIRONMENT 是 Production,Swagger 中间件没有被注册,所以静态页面和 json 文档都找不到。

解决:需要测试环境暴露接口文档时,把 UseSwagger 移出条件判断,并在 UseSwaggerUI 里显式指定 SwaggerEndpoint。生产环境不建议直接暴露,可以给 Swagger 加独立路由前缀或放内网访问。

5.3 密钥太短、带换行或两端不一致,导致签名校验失败

现象:登录接口能生成 Token,但把 Token 放到受保护接口校验时 401,日志里报 Signature validation failed,IDX10501 之类错误。

原因:HS256 要求密钥至少 32 字节,SymmetricSecurityKey在密钥过短时会报错。更隐蔽的是密钥里有看不见的换行或 BOM,比如从 appsettings 复制到环境变量时带回车符,签发端和校验端使用的字节序列不一致,签名自然对不上。

解决:密钥用固定长度的随机字符串,不少于 32 字节,从环境变量读取后做Trim()。签发端和校验端共用同一来源,发布环境通过Jwt__SecretKey环境变量统一注入,不要多人手动复制配置,避免肉眼看不出来的差异。

5.4 明明没过期却被拒:时钟偏移 ClockSkew 与服务器时间差

现象:Token 刚签发一秒钟就发起请求,返回 401,日志提示 token not yet valid;或者过期边界上同一个 Token 一会儿能通过,一会儿被拒绝。

原因:JWT 的nbf和exp是绝对时间戳,签发机和校验机时间不同步时,会出现"未来 Token"或"已过期 Token"的判定偏差。TokenValidationParameters 默认 ClockSkew 为 5 分钟,能容忍一定偏差,但如果你显式把 ClockSkew 设成 0,再小的毫秒级时间差都会被放大成 401。

解决:生产环境同步各服务器 NTP 时间;校验参数里设置合理的 ClockSkew,一般 30 秒到 1 分钟够用,不要为了严格而设 0。测试源码里想快速验证过期逻辑,可以把过期时间临时调成 1 分钟,再观察 401 出现时机。

5.5 从 jwt 漏洞总结看落地安全:弱密钥、泄露与失效机制

现象:网上流传的 jwt 漏洞总结里,最常见的是弱密钥爆破。攻击者拿到一个 Token 样本后,用字典里的常见密钥离线爆破签名段,几十秒就能还原出secret、123456级别的弱密钥,然后任意伪造合法 Token。

原因:HS256 对称签名下,安全边界全押在密钥强度上。密钥一旦泄露,攻击者就能伪造任意身份的 Token,而服务端还检测不出来。同时 JWT 是无状态凭证,服务端不记录,签发后无法单独撤回,过期前始终有效,这是 JWT 鉴权固有的短板。

解决:密钥强度至少 256 位随机串,定期轮换;Access Token 有效期缩短到 30 分钟级别;需要主动失效的用户通过黑名单机制处理,比如在 Redis 里存 jti,校验时检查一次;日志和异常输出里不要打印 Token 明文,审计链路只记录 jti 和用户 ID。前端存放上,敏感项目建议把 Token 放内存变量而不是 localStorage,降低 XSS 读取面。

6. 验证 JWT 实现是否可靠:三招自测加一个生产习惯

6.1 用 jwt.io 对线上 Token 做解包体检

拿到任意接口返回的 Token,先粘贴到 jwt.io 看 Payload 展开后的字段。重点核对三件事:iss和aud是否与 appsettings 里的配置一致;exp减去nbf是否等于你配置的 ExpireMinutes;alg是否为 HS256。这三项对不上,问题基本出在签发参数或配置读取,不用急着查校验端。

6.2 用 WebApplicationFactory 写鉴权集成测试

单元测试覆盖不了中间件链路,集成测试可以。引 Microsoft.AspNetCore.Mvc.Testing 包,把整个管道跑起来,先断言未带 Token 返回 401:

var app = new WebApplicationFactory<Program>(); var client = app.CreateClient(); var anon = await client.GetAsync("/api/auth/me"); Assert.Equal(HttpStatusCode.Unauthorized, anon.StatusCode);

逻辑说明:CreateClient 会真实走认证和授权中间件,401 断言能覆盖"漏配 UseAuthentication"这类回归问题。在这个基础上再补一个先登录换 Token、再带 Token 访问的用例,整条鉴权链路就闭环了。

6.3 生产习惯:密钥轮换与刷新令牌黑名单

我个人的上线习惯是排一张密钥版本表:签发用当前密钥,校验时保留最近两个版本的密钥做兼容;每次轮换写 changelog 记录生效时间,避免突然全量失效。刷新令牌黑名单用 Redis,key 带 jti 后缀,过期时间和刷新令牌有效期对齐,这样被动回收、主动踢人都有退路。测试源码可以简化,但生产环境这条底线建议保留。交过的学费是曾经把 Access Token 过期时间设成 24 小时还写进了访问日志,翻车后被安全扫描盯上,从那以后 Token 明文再也不出现在任何可见输出里。希望上面的方案能帮你少走弯路,从 .NET6 的 JWT 鉴权第一步一直走到能上线验证。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 10:57:55

一文带你初识C++和命名空间

1. 初识CC语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度的抽象和建模时&#xff0c;C语言则不合适。为了解决软件危机&#xff0c; 20世纪80年代&#xff0c; 计算机界提出了OOP(objectorient…

作者头像 李华
网站建设 2026/10/1 10:57:03

PHP 8.4怎么实现API接口数据加密传输

前言先澄清一个常见误解&#xff1a;把请求体加密并不等于「传输安全」。HTTPS&#xff08;TLS&#xff09;解决的恰恰就是传输安全——防窃听、防篡改、防中间人。如果只做应用层加密却不上 HTTPS&#xff0c;中间人完全可以降级、替换公钥、重放到你的接口。那应用层再加密一…

作者头像 李华
网站建设 2026/10/1 10:57:02

AI毁灭概率与全民高收入背后:对齐、Agent与工程化生存指南

最近马斯克那段访谈在圈子里炸开了锅&#xff0c;核心就两句话&#xff1a;AI 有 20% 的概率把人类搞没&#xff0c;但也有可能把人类带进一个“没有钱”的全民高收入时代。很多人只盯着前半句恐惧&#xff0c;或者只拿后半句当段子&#xff0c;但这两句话其实说的是同一个东西…

作者头像 李华
网站建设 2026/10/1 10:56:26

基于Django的交通标志识别系统:从PyTorch模型训练到Web部署全流程解析

简介&#xff1a;这套基于深度学习的交通标志识别Django项目源码&#xff0c;面向python开发者和深度学习初学者&#xff0c;解决图片及摄像头实时交通标志识别与分类问题。资源共570个文件&#xff0c;包含Python源码、Django模板、前端样式与JavaScript脚本、YOLOv5模型权重与…

作者头像 李华
网站建设 2026/10/1 10:55:43

VSCode 调试配置实战:launch.json 与多文件断点排错指南

1. 调试的第一步&#xff1a;搞懂 VSCode 的调试器到底在执行什么很多人装上 VSCode、配好编译器&#xff0c;兴冲冲写了第一行print("Hello World")&#xff0c;然后信心满满地按下 F5&#xff0c;结果屏幕上弹出一个从未见过的launch.json文件&#xff0c;里面一堆…

作者头像 李华
网站建设 2026/10/1 10:55:30

Linux上部署Redis全攻略:从源码编译到Docker主从与调优避坑

在Linux上把Redis跑起来&#xff0c;看着就是个apt install或者解压make的事&#xff0c;但真到了2026年&#xff0c;这事情里的门道其实越来越多。Redis早已不是当年那个只做缓存的KV数据库&#xff0c;数据类型、分布式锁、缓存治理、监控排障一套下来&#xff0c;部署方式的…

作者头像 李华