.net 8 实现 JWT 无状态设计 token [附源码]
本文主要分为两个部分:
1、概念
2、.net 8 demo
第一部分主要描述所有与 JWT 相关的概念以及词汇,及其原理;第二部分是代码示例,文末附 源码下载。
* 阅读提示 :鼠标悬停在 章节标题 上可见 文章目录
1、概念
JWT |
json web token 是一种开放标准(RFC 7519),是指定一种数据格式(json) 和数据结构 (header, payload, signature) , 用于网络应用间信息传输,一般是用于客户端与服务器之间的安全校验 |
authentication |
鉴权;可以为用户创建 token,可以校验 token 有效性 |
authorization |
批准 / 授权;是一种配置,约束访问条件,例如: [Authorize(Roles = "Admin")] // 管理员才可访问 [Authorize(Policy = "EmployeeWithDepartment")] // 符合特定策略才可访问 |
bearer |
持票人,使用场景在于请求时,header 的格式 例如请求头部的参数 "Authorization" : "Bearer tokenString" |
一个 token 由 3 个部分组成,并且以实心句号 . 分割,以下为一个 token 示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidXNlcjEyMyIsIm5iZiI6MTczMTM5MzYzNiwiZXhwIjoxNzMxMzk3MjM2LCJpYXQiOjE3MzEzOTM2MzYsImlzcyI6InlvdXJkb21haW4uY29tIiwiYXVkIjoieW91ci1hcGktYXVkaWVuY2UifQ.q2zUeZ3RAVIxg5CVNI2Pjq9Nfvue3_tCQagDRJmStYI
|
token part |
decode (by base64) |
remark |
header |
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 |
{ "alg" : "HS256", "typ" : "JWT" } |
token metadata 说明用于加密的算法类型 |
payload (有效载荷) |
eyJuYW1lIjoidXNlcjEyMyIsIm5iZiI6MTczMTM5MzYzNiwiZXhwIjoxNzMxMzk3MjM2LCJpYXQiOjE3MzEzOTM2MzYsImlzcyI6InlvdXJkb21haW4uY29tIiwiYXVkIjoieW91ci1hcGktYXVkaWVuY2UifQ
|
{ "name": "user123", "nbf": 1731393636, "exp": 1731397236, "iat": 1731393636, "iss": "yourdomain.com", "aud": "your-api-audience" } |
自定义的用户信息(claims), 以及一些必要信息: nbf : not before, token 生效时间 exp : expiration time, token 过期时间 iat : issued at 签发时间 iss : issuer of the token 签发者(比如服务端名字) aud : audience,该 token 的受众,可以是服务名称 或者 api 名称等等 |
signature |
q2zUeZ3RAVIxg5CVNI2Pjq9Nfvue3_tCQagDRJmStYI |
N/A |
signature:是根据 header 中指定的算法以及在服务端密钥,对 payload 加密得到的哈希值; 用于服务端校验 token 是否签发自服务端。
(请求时 token 的 payload 与解密 signature 后得到的payload 需要一致,所以擅改 payload 会导致token 无效) |
* base64 transfer online : Base64编码_base64在线解码_base64加密在线转换
什么是无状态化?
无状态 (stateless) :指服务端不需要存储任何关于用户会话的信息。 一般地,在旧时的实现方式中,服务端可能需要存储用户登录的记录,例如需要存储对应用户会话的有效时间。 而 JWT 标准中 signature 的设计允许了服务端可以不对登录信息做持久化,服务端可以通过算法验证 signature 的有效性,以此确保了 token 的安全性,所以并不需要另外存储用户会话状态,这样就减少服务端压力和复杂性,做到“无状态”化。 但其实现今的应用需求中,很多时候都希望追踪到用户行为,所以还是会 log 用户操作,为日后的用户行为分析做数据奠基;当然,这是另一个话题。 |
2、.net 8 demo
2.1 Authentication
包引用
using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims;
创建 token 的方法
1 public string GenerateJwtToken(string userName) 2 { 3 4 var tokenHander = new JwtSecurityTokenHandler(); 5 var key = Encoding.ASCII.GetBytes("your secret key"); 6 var tokenDescriptor = new SecurityTokenDescriptor 7 { 8 // configure the custome information in the Claim instance 9 Subject = new System.Security.Claims.ClaimsIdentity(new[] { new Claim("name", userName) }), 10 11 // configure the expiration time 12 Expires = DateTime.UtcNow.AddHours(1), 13 14 // issuer could be your service name or other, and it will be the [iss] of the token 15 Issuer = "xx Servicing", 16 17 // audience could be the audience of your service, and it will be the [aud] of the token 18 Audience = "your api audience", 19 20 // configure secret algorithm and secret key for signature part 21 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) 22 23 }; 24 25 var token = tokenHander.CreateToken(tokenDescriptor); 26 return tokenHander.WriteToken(token); 27 }
验证 token 的配置
1 builder.Services.AddAuthentication(options => 2 { 3 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 4 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 5 }) 6 .AddJwtBearer(options => 7 { 8 options.TokenValidationParameters = new TokenValidationParameters 9 { 10 // validate the token expiration 11 ValidateLifetime = true, 12 13 // validate signature 14 ValidateIssuerSigningKey = true, 15 16 // need to sure the issuer/audience value here is same with the issuer/audience while token generating 17 ValidateIssuer = true, 18 ValidateAudience = true, 19 ValidIssuer = "yourdomain.com", 20 ValidAudience = "your-api-audience", 21 22 //ValidateIssuer = false, // Skip issuer validation 23 //ValidateAudience = false, // Skip audience validation 24 25 IssuerSigningKey = new SymmetricSecurityKey("your secret key"), 26 ClockSkew = TimeSpan.FromMinutes(5) // Allow for clock skew 27 28 }; 29 30 });
开启 authentication 和 authorization
1 app.UseAuthentication(); 2 app.UseAuthorization();
!注意:
1、此处的 Authentication,Authorization 都是 Miscroft.AspNetCore 自带的中间件。
2、Authentication 是鉴权,Authorization 是授权 / 批准,这两个中间件的位置不能错,必须是:鉴权在前。
Authentication 中间件主要负责验证 token ,比如检查签名是否正确,检查 token 有效期。 当 Authentication 中间件成功验证令牌后,它会将 token 中的用户信息(像是用户ID、角色等)写入到 httpContext,再传递给 Authorization 中间件。此时,Authorization 会拿到有效的 httpContext.User 属性,这是一个 ClaimsPrincipal 对象,包含了用户信息声明(claims); 若绕开了 Authentication 中间件,httpContext 直接到 Authorization ,此时会找不到合法的 httpContext.User,所以返回 401 Unauthorized 错误。 |
源码下载
1、.net 8 AuthDemo Web Api Project
2、AuthDemo.postman_collection.json
2.2 Authorization
2.2.1 Authorize
1 // 标注 Authorize 后访问该api时会验证token 2 [Authorize] 3 [HttpGet("GetList")] 4 public IEnumerable<WeatherForecast> GetList() 5 { 6 .. 7 }
2.2.2 Policy 策略
1 // 在 Startup.cs 中配置策略 2 services.AddAuthorization(options => 3 { 4 options.AddPolicy("Over18", policy => 5 policy.RequireClaim("Age", "18")); // 验证 token 中的 age 参数是否等于 18 6 }); 7 8 // 在 Controller 中使用策略 9 [Authorize(Policy = "Over18")] 10 public IActionResult RestrictedContent() 11 { 12 .. 13 }
1 // 验证 token 中的 role 参数是否 ("Admin", "Manager")中的成员,注:大小写敏感 2 services.AddAuthorization(options => 3 { 4 options.AddPolicy("AdminOrManager", policy => 5 policy.RequireRole("Admin", "Manager")); 6 }); 7 8 // 多个“并且”条件的声明 9 services.AddAuthorization(options => 10 { 11 12 options.AddPolicy("EmployeeWithDepartment", policy => 13 policy.RequireClaim("EmployeeNumber") 14 .RequireClaim("Department", "HR", "IT")); 15 });
2.2.3 AllowAnonymous
[Authorize] 可以修饰在 controller 上,这意味着该 controller 下的所有 api 都需要验证token,
此时可以通过修饰 [AllowAnonymous] 特别指定某一个 api 不需要 token 验证。
1 [Authorize] 2 public class AccountController : Controller 3 { 4 [AllowAnonymous] 5 public IActionResult Login() 6 { 7 .. 8 } 9 10 public IActionResult Logout() 11 { 12 .. 13 } 14 }
博客园的编辑器也太难用了吧!!
2024-11-13