当前位置: 首页 > news >正文

.NET 10 API 鉴权体系:从原理到实践

一、什么是 API 鉴权1.1 问题的起点HTTP 是无状态的HTTP 协议本身对请求者是谁毫无记忆。每一个请求到达服务器时服务器看到的都是一个陌生的连接。这意味着没有鉴权 任何人 → GET /api/salary/records → 200 OK 有鉴权 陌生请求 → GET /api/salary/records → 401 Unauthorized 持证请求 → GET /api/salary/records Token → 200 OKAPI 鉴权的本质是在无状态协议上建立一套可信凭证的颁发与验证机制回答两个根本问题你是谁Authentication认证你能做什么Authorization授权这两个问题在 .NET 中由两个独立的中间件Middleware处理顺序固定协同工作。1.2 认证与授权的分工┌─────────────────────────────────────────────────┐ │ 请求处理管道Pipeline │ │ │ │ 请求进入 │ │ │ │ │ ▼ │ │ Authentication 中间件 │ │ · 解析 Token / Cookie │ │ · 填充 HttpContext.UserClaimsPrincipal │ │ · 回答你是谁 │ │ │ │ │ ▼ │ │ Authorization 中间件 │ │ · 检查端点是否需要授权 │ │ · 验证 Role / Policy / Claims │ │ · 回答你能做什么 │ │ │ │ │ ▼ │ │ Controller / Minimal API Handler │ └─────────────────────────────────────────────────┘两个中间件的顺序不能颠倒。认证在先授权在后——只有先知道你是谁才能判断你能做什么。1.3 核心数据结构ClaimsPrincipal 与 Claim认证成功后用户身份被表达为一个ClaimsPrincipal对象挂载在HttpContext.User上。它包含一组Claim声明——本质是键值对// Claim 示例newClaim(ClaimTypes.NameIdentifier,user-123)// 用户 IDnewClaim(ClaimTypes.Role,admin)// 角色newClaim(department,engineering)// 自定义属性newClaim(exp,1716000000)// 过期时间整个鉴权体系都围绕 Claims 运转Token 里打包 Claims服务器解析 Claims授权策略检查 Claims。二、常见凭证方案对比方案凭证形态服务器验证方式是否有状态适用场景Session CookieSessionId随机串查内存/数据库有状态传统 Web 应用JWT Bearer自包含 Token验签名无需查库无状态前后端分离、微服务API Key固定密钥字符串查库比对有状态机器对机器调用OAuth Access Token授权服务器颁发的 JWT验签名或查授权服务器通常无状态第三方授权、企业 SSO单点登录在现代 API 开发中JWT Bearer是最主流的选择原因在于其无状态特性天然适合分布式系统——服务器不需要集中存储 Session每个节点独立验证 Token 即可。三、JWT 验签原理理解 JWT 验签是理解整个鉴权体系的基础。3.1 JWT 的物理结构JWT 是由.分隔的三段 Base64URL 编码字符串eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiJ1c2VyLTEyMyIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxNjAwMDAwMH0 . SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ────────────────── ────────────────────────────────────── ──────────────────────── Header Payload Signature解码后// Header描述算法{alg:HS256,typ:JWT}// Payload实际 Claims 数据{sub:user-123,role:admin,exp:1716000000}// Signature二进制数据是 HeaderPayload 的数字指纹Payload 明文可读绝不能存放密码等敏感信息。Base64 编码不是加密。3.2 签名生成颁发时签名公式 Signature HMAC_SHA256( Base64URL(Header) . Base64URL(Payload), SecretKey ) 最终 Token Base64URL(Header) . Base64URL(Payload) . Base64URL(Signature)Signature 是 Header 与 Payload 合并内容的数字指纹且只有持有 SecretKey 的人才能生成。3.3 验签过程每次请求时验签不是解密而是重新计算后比对收到 TokenA . B . C └── 客户端携带的签名 服务器 Step 1C HMAC_SHA256(A . B, SecretKey) // 用同一 Key 重新计算 Step 2C C → 签名一致Token 未被篡改可信 C ! C → 签名不一致Token 已被篡改拒绝3.4 为什么篡改一定会被发现攻击者想把 role 从 user 改成 admin 原始 Token Header . Payload{role:user} . Signature_原始 篡改 Token Header . Payload{role:admin} . Signature_原始 ← Signature 没变 服务器验证 C HMAC_SHA256(Header . Payload_篡改, SecretKey) C ≠ Signature_原始 ← Payload 变了指纹必然不同 要伪造签名必须知道 SecretKey → SecretKey 从未在网络传输 → 无法伪造3.5 两种签名算法HS256对称签名与验证用同一个密钥。颁发服务器 资源服务器API SecretKey ──────────── SecretKey 签名 验证缺点所有服务都必须持有 SecretKey任一节点泄漏即全线失守。RS256非对称私钥签名公钥验证推荐用于生产环境。授权服务器 资源服务器API PrivateKey签名 PublicKey验证 只有我有 可以公开分发 通过 /.well-known/jwks.json 获取数学保证用私钥加密的数据只有对应公钥能解开API 节点即使被攻破攻击者拿到公钥也无法伪造 Token。3.6 验签之外完整校验链验签只证明Token 未被篡改完整验证还需要① 验签名 → Token 是否由可信方颁发、内容是否完整 ② 验 exp → Token 是否已过期防重放过期 Token ③ 验 iss → 是否来自我信任的授权服务器防其他系统的 Token ④ 验 aud → Token 是否颁发给我这个 API防 Token 跨服务复用 ⑤ 提取 Claims → 填充 HttpContext.User供后续授权使用其中aud受众Audience校验尤为重要系统中存在 Service-A 和 Service-B 攻击者合法登录 Service-A获得 access_tokenaud service-a Service-B 不校验 aud → 攻击者用 service-a 的 Token 访问 B → 成功入侵 Service-B 校验 aud → aud ! service-b → 直接拒绝四、鉴权决策机制如何判断是否需要登录4.1 三层决策模型┌──────────────────────────────┐ 请求进入 │ Authorization 中间件 │ ──────────────────▶ │ │ │ Layer 1端点是否受保护 │ │ 无 [Authorize] → 直接放行 │ │ 有 [AllowAnonymous] → 放行 │ │ 有 [Authorize] → 继续 ↓ │ │ │ │ Layer 2身份是否已认证 │ │ IsAuthenticated false │ │ → 401 Unauthorized │ ← 请先登录 │ IsAuthenticated true → ↓ │ │ │ │ Layer 3是否满足权限要求 │ │ Policy/Role 不满足 │ │ → 403 Forbidden │ ← 无权访问 │ 满足 → 放行 │ └──────────────────────────────┘4.2 401 与 403 的精确语义状态码语义触发条件客户端动作401 Unauthorized身份未知请先证明你是谁未携带 Token / Token 无效 / Token 过期跳转登录页或刷新 Token403 Forbidden身份已知但你没有权限Token 有效但 Role/Policy 不满足提示权限不足不跳登录4.3 Authentication 中间件的执行时机一个常见误解Authentication 中间件只在受保护接口上运行。实际上Authentication 中间件对【所有请求】都执行总是尝试解析 Token。 公开接口无 [Authorize] 携带有效 Token → User.IsAuthenticated true但不做检查 未携带 Token → User.IsAuthenticated false但不做检查 → Authorization 中间件判断端点未受保护直接放行 受保护接口有 [Authorize] 携带有效 Token → 放行 未携带 Token → 401五、.NET 10 中的完整实现5.1 Token 颁发登录接口app.MapPost(/api/auth/login,async(LoginRequestreq,UserServiceuserService){// 1. 验证身份凭据varuserawaituserService.VerifyAsync(req.Username,req.Password);if(userisnull)returnResults.Unauthorized();// 2. 构造 Claimsvarclaimsnew[]{newClaim(ClaimTypes.NameIdentifier,user.Id.ToString()),newClaim(ClaimTypes.Name,user.Username),newClaim(ClaimTypes.Role,user.Role),newClaim(department,user.Department)};// 3. 签名并生成 TokenvarkeynewSymmetricSecurityKey(Encoding.UTF8.GetBytes(config[Jwt:Secret]!));vartokennewJwtSecurityToken(issuer:config[Jwt:Issuer],audience:config[Jwt:Audience],claims:claims,expires:DateTime.UtcNow.AddHours(1),signingCredentials:newSigningCredentials(key,SecurityAlgorithms.HmacSha256));returnResults.Ok(new{access_tokennewJwtSecurityTokenHandler().WriteToken(token),expires_in3600});});5.2 注册认证与授权服务// Program.csbuilder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options{options.TokenValidationParametersnewTokenValidationParameters{ValidateIssuerSigningKeytrue,IssuerSigningKeynewSymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration[Jwt:Secret]!)),ValidateIssuertrue,ValidIssuerbuilder.Configuration[Jwt:Issuer],ValidateAudiencetrue,ValidAudiencebuilder.Configuration[Jwt:Audience],ValidateLifetimetrue,ClockSkewTimeSpan.Zero// 不允许时间偏差容忍};});builder.Services.AddAuthorization(options{// 基于策略Policy-based授权options.AddPolicy(SeniorEngineer,policypolicy.RequireAuthenticatedUser().RequireClaim(department,engineering).RequireRole(senior));});// 中间件顺序固定不能交换app.UseAuthentication();app.UseAuthorization();5.3 保护接口// 需要登录app.MapGet(/api/profile,(HttpContextctx){varuserIdctx.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;returnResults.Ok(new{userId});}).RequireAuthorization();// 需要特定角色app.MapDelete(/api/users/{id},(intid)Results.NoContent()).RequireAuthorization(pp.RequireRole(admin));// 需要满足自定义策略app.MapGet(/api/engineering/report,()Results.Ok()).RequireAuthorization(SeniorEngineer);// 公开接口显式声明app.MapGet(/api/health,()Results.Ok(healthy)).AllowAnonymous();六、OAuth 2.0 / OIDC 规范access_token 与 id_token在接入第三方身份提供者IdPIdentity Provider的场景中需要理解 OAuth 2.0 与 OIDC 的规范分工。6.1 两个规范的关系┌─────────────────────────────────────────────────┐ │ OpenID ConnectOIDC │ │ 解决你是谁Identity / Authentication │ │ ┌─────────────────────────────────────────────┐│ │ │ OAuth 2.0 ││ │ │ 解决你能访问什么Authorization ││ │ │ 颁发access_token ││ │ └─────────────────────────────────────────────┘│ │ 新增id_token必须是 JWT 格式 │ └─────────────────────────────────────────────────┘OAuth 2.0RFC 6749授权框架规定如何颁发 access_token但不规定其格式对客户端透明OIDC在 OAuth 2.0 之上的身份层新增 id_token规定格式必须为 JWT6.2 access_token规范定义与使用约束RFC 6749 的核心定义access_token 代表一份授权许可客户端持它访问受保护资源其内容对客户端不透明Opaque。access_token 回答的问题 这个客户端被授权访问哪些资源、执行哪些操作scope access_token 的受众aud 资源服务器Resource Server即你的 API 典型 JWT 载荷 { iss: https://auth-server.com, sub: user-123, aud: https://your-api.com, // 目标是 API exp: 1716000000, scope: read:data write:data, // 被授权的操作范围 client_id: my-client-app }规范约束客户端拿到后不应解析其内容格式是实现细节只应发送给aud指定的资源服务器有效期应短通常 15 分钟 ~ 1 小时6.3 id_token规范定义与使用约束OIDC Core 1.0 的核心定义id_token 包含关于用户认证的断言Assertion由授权服务器颁发给客户端必须是 JWT 格式。id_token 回答的问题 用户是谁什么时候登录的用哪种方式认证的 id_token 的受众aud 客户端 App不是 API 标准 Claims { iss: https://auth-server.com, sub: user-123, aud: my-client-app-id, // 目标是客户端 exp: 1716000000, auth_time: 1715996000, // 用户实际认证的时间 nonce: abc123, // 防重放攻击 name: 张三, email: zhangexample.com }规范明确禁止OIDC Core 1.0 Section 2将 id_token 发送给资源服务器API用 id_token 访问 API6.4 两者的核心区别维度access_tokenid_token所属规范OAuth 2.0OIDC本质授权凭证身份断言受众 aud资源服务器API客户端 App用途携带去访问 API客户端本地读取用户信息格式要求规范不限制必须是 JWT能否访问 API是这是它的职责规范明确禁止6.5 Authorization Code PKCEProof Key for Code Exchange完整流程PKCE 是当前最安全的授权模式防止授权码拦截攻击适用于 SPASingle Page Application和移动端。用户浏览器 客户端 App 授权服务器 资源服务器API │ │ │ │ │ 点击登录 │ │ │ │─────────────────▶│ │ │ │ │ ① 生成 code_verifier code_challenge │ │ │ 重定向至授权端点 │ │◀─────────────────│──────────────────▶│ │ │ │ │ │ │ ② 用户在授权服务器完成登录输入账号密码 │ │─────────────────────────────────────▶│ │ │ │ │ │ │ ③ 授权服务器重定向回 App携带短期 code │ │◀────────────────────────────────────▶│ │ │─────────────────▶│ │ │ │ │ ④ 用 code code_verifier 换 Token │ │ │ POST /token │ │ │──────────────────▶│ │ │ │ │ │ │ │ ⑤ 返回 │ │ │ │ access_token │ │ │ │ id_token ◀─────│ │ │ │ refresh_token │ │ │ │ │ │ │ │ ⑥ 客户端验证并解析 id_token本地使用 │ │ │ 展示用户名、头像 │ │ │ │ │ │ │ ⑦ 携带 access_token 调用 API │ │ │ Authorization: Bearer access_token │ │ │──────────────────────────────────────────▶ │ │ │ ⑧ 验证 access_token │ │ │ │ 检查签名、aud、scope │ │ │◀────────────────────────────────────────── │ │ │ ⑨ 返回业务数据 │6.6 Token 刷新机制access_token短期1小时过期 │ ▼ POST /token grant_typerefresh_token refresh_token长期 Token │ ▼ 授权服务器返回新 access_token 旧 refresh_token 立即作废Refresh Token Rotation │ ▼ 客户端用新 access_token 继续请求七、鉴权体系的安全边界JWT 验签保证了 Token 未被篡改但不能覆盖所有威胁威胁JWT 能防应对措施Token 内容篡改✅ 验签覆盖—Token 被盗用❌ 验签无感知HTTPS 强制传输加密重放合法 Token❌ 签名依然有效短有效期 jtiJWT ID黑名单用户主动注销❌ Token 未过期仍有效Redis 存储已注销jti验证时查询跨服务 Token 复用❌ 签名仍有效严格校验aud字段生产环境最佳实践清单使用 RS256非对称替代 HS256对称API 节点只持有公钥access_token 有效期不超过 1 小时通过 refresh_token 静默续期强制 HTTPS禁止 Token 出现在 URL 中aud和iss校验必须开启ClockSkew设为TimeSpan.Zero主动注销场景使用 Redis 维护jti黑名单敏感端点配合内置 Rate Limiting.NET 10防暴力破解八、总结┌─────────────────────────────────────────────────────────┐ │ .NET 10 API 鉴权全景 │ │ │ │ 颁发层 │ │ 登录接口 → 验证密码 → 打包 Claims → 签名 → JWT │ │ │ │ 验证层每次请求 │ │ 提取 Token → 验签名 → 验 iss/aud/exp → 填充 User │ │ │ │ 授权层 │ │ 端点有 [Authorize]? → IsAuthenticated? → Policy满足? │ │ → 放行 / 401 / 403 │ │ │ │ 企业级扩展OAuth 2.0 OIDC │ │ 授权服务器颁发 access_token访问 API │ │ 颁发 id_token客户端读取用户信息 │ │ refresh_token 静默续期 │ └─────────────────────────────────────────────────────────┘鉴权不是单点技术而是一条完整的信任链从用户提交凭据到 Token 被颁发、传递、验证、使用每个环节的设计都影响整体安全性。理解每个环节背后的原理才能在面对非标准场景时做出正确的架构决策。
http://www.rkmt.cn/news/1389112.html

相关文章:

  • 2026最新五家句容市黄金回收白银回收铂金回收彩金回收店铺靠谱回收门店推荐TOP5排行榜及联系方式推荐 - 前途无量YY
  • 2026最新五家建德市黄金回收白银回收铂金回收彩金回收店铺靠谱回收门店推荐TOP5排行榜及联系方式推荐 - 前途无量YY
  • 告别卡顿!5分钟优化你的树莓派二维码识别程序:OpenCV多线程与pyzbar参数调优实战
  • 2026年AI论文网站实测报告:5款AI神器从文献综述到降重全流程效率翻倍
  • 【LeetCode刷题日记】二叉树最近公共祖先:从236到235,一篇文章彻底搞定
  • 深入浅出 Pydantic:BaseModel 核心原理与实战指南
  • 2026最新五家常宁市黄金回收白银回收铂金回收彩金回收店铺靠谱回收门店推荐TOP5排行榜及联系方式推荐 - 前途无量YY
  • 干货指南:专利注册服务的选购要点 - mypinpai
  • 别再花钱买图床了!用Gitee+SpringBoot+Hutool,5分钟搞定个人博客图片托管
  • 2026最新五家建瓯市黄金回收白银回收铂金回收彩金回收店铺靠谱回收门店推荐TOP5排行榜及联系方式推荐 - 前途无量YY
  • 20.刷机协议逆向实战:高通 MSM 与苹果 iBoot USB 通信协议详解
  • 嵌入式开发入门全景指南:路径选择与所需基础分析
  • Seraphine:5分钟快速上手的英雄联盟智能助手完整指南
  • P1318 积水面积【洛谷算法习题】
  • uniapp+cocos跨平台游戏架构实战:广告调度与Bridge通信
  • 有实力的首饰黄金回收公司口碑如何?价格贵不贵? - mypinpai
  • 【初阶数据结构与算法】八大排序之非比较排序(计数排序),一次性讲清!
  • CenToken 官网使用指南:新手从零玩转全域大模型聚合平台
  • 实战掌握RISC-V处理器模拟:Ripes图形化调试工具完全指南
  • 3秒识别模糊根源:Midjourney日志诊断法+实时--no parameter校验表(仅限本期开放下载)
  • Python实现GPU显存温度监控与动态温控,解决AI应用热节流问题
  • 5分钟学会Zotero Style插件:让你的文献管理体验焕然一新
  • UE5+Aximmetry实时虚拟制片:从绿幕抠像到信号级同步
  • 小红书链接解析终极指南:5分钟快速上手XHS-Downloader工具
  • 边缘智能:核心概念与技术深度解析
  • 声明式AI智能体架构生成:从YAML配置到可运行代码的自动化实践
  • 并发编程(三)
  • 手机位置自由:如何为每个应用单独设置虚拟定位?
  • 大语言模型微调实战:让AI精准生成企业级SQL查询
  • Snowflake Time Travel 实战指南:数据回溯、克隆与故障修复