在 .NET 8/9 中使用 AppUser 进行 JWT 令牌身份验证
原文版本:https://mp.weixin.qq.com/s/wKoi7u4dTtS31-hmWG8irw
经修正
概述
本文介绍了在 .NET 8 Web 应用程序中通过 AppUser
类实现 JWT 令牌身份验证的过程。JWT 身份验证是保护 API 的标准方法之一,它允许无状态身份验证,因为签名令牌是在客户端和服务器之间传递的。
什么是 JSON Web 令牌?
JSON Web 令牌(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于将信息作为 JSON 对象在各方之间安全地传输。此信息是经过数字签名的,因此可以验证和信任。可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对对 JWT 进行签名。
什么是 JSON Web 令牌结构?
在其紧凑形式中,JSON Web 令牌由三个部分组成,由点(.)分隔,它们是:
- 页眉
- 有效载荷
- 签名
因此,JWT 通常如下所示:xxxxx.yyyyy.zzzzz
更多详情请访问 JWT 介绍
设置 JWT 令牌身份验证
1. 创建新的 .NET 8 Web API 项目
shell
dotnet new webapi -n JwtAuthApp
2. 安装所需的 NuGet 软件包
shell
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.IdentityModel.Tokens
3. 创建 JWT 配置模型
csharp
using System.Globalization;
namespace JwtAuthApp.JWT
{
public class JwtConfiguration
{
public string Issuer { get; } = string.Empty;
public string Secret { get; } = string.Empty;
public string Audience { get; } = string.Empty;
public int ExpireDays { get; }
public JwtConfiguration(IConfiguration configuration)
{
var section = configuration.GetSection("JWT");
Issuer = section[nameof(Issuer)];
Secret = section[nameof(Secret)];
Audience = section[nameof(Audience)];
ExpireDays = Convert.ToInt32(section[nameof(ExpireDays)], CultureInfo.InvariantCulture);
}
}
}
4. 将 JWT 配置添加到您的 appsettings.json 中
json
{
"Jwt": {
"Issuer": "JwtAuthApp",
"Audience": "https://localhost:7031/",
"Secret": "70FC177F-3667-453D-9DA1-AF223DF6C014",
"ExpireDays": 30
}
}
5. 为 Configuration 配置 DIProgram.cs
csharp
builder.Services.AddTransient<JwtConfiguration>();
6. 配置 JWT 身份验证扩展
csharp
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Text;
namespace JwtAuthApp.JWT
{
public static class JwtAuthBuilderExtensions
{
public static AuthenticationBuilder AddJwtAuthentication(this IServiceCollection services, IConfiguration configuration)
{
var jwtConfiguration = new JwtConfiguration(configuration);
services.AddAuthorization();
return services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtConfiguration.Issuer,
ValidAudience = jwtConfiguration.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfiguration.Secret)),
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var token = context.Request.Headers["Authorization"].ToString()?.Replace("Bearer ", "");
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
return Task.CompletedTask;
}
};
});
}
}
}
7. 在 Program.cs
csharp
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddJwtAuthentication(builder.Configuration);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
8. 创建 Token 生成服务
csharp
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace JwtAuthApp.JWT
{
public class TokenService
{
private readonly JwtConfiguration _config;
public TokenService(JwtConfiguration config)
{
_config = config;
}
public string GenerateToken(string userId, string email)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim(JwtRegisteredClaimNames.Email, email)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.Secret));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _config.Issuer,
audience: _config.Audience,
claims: claims,
expires: DateTime.Now.AddDays(_config.ExpireDays),
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
}
9. 注册 Token Service
csharp
builder.Services.AddTransient<TokenService>();
10. 添加登录端点或控制器
csharp
app.MapPost("/login", [FromBody] LoginRequest request, TokenService tokenService)
{
if (request.Username == "admin" && request.Password == "admin")
{
var userId = "123456"; // 从数据库获取用户 ID
var email = "admin@example.com"; // 从数据库获取用户邮箱
var token = tokenService.GenerateToken(userId, email);
return Results.Ok(new { token });
}
return Results.Unauthorized();
})
.WithName("Login")
.RequireAuthorization();
11. 新增 SwaggerConfiguration(方便测试)
csharp
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace JwtAuthApp.JWT
{
public static class SwaggerConfiguration
{
public static void Configure(SwaggerGenOptions options)
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "JWT Auth API",
Version = "v1",
Description = "A sample API for JWT Authentication"
});
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme.",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
}
}
}
12. 添加 AppUser
csharp
public class AppUser : ClaimsPrincipal
{
public AppUser(IHttpContextAccessor contextAccessor) : base(contextAccessor.HttpContext.User) { }
public string UserID => FindFirst(CustomerConst.UserID)?.Value ?? "";
public string OpenId => FindFirst(CustomerConst.OpenId)?.Value ?? "";
}
builder.Services.AddHttpContextAccessor();
builder.Services.AddTransient<AppUser>();
13. 为所有 Controller 或端点添加 Authorize 属性
csharp
app.MapGet("/weatherforecast", [Authorize] () =>
{
// ...
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.MapGet("/user", [Authorize] (AppUser user) =>
{
return Results.Ok(new { user.Email });
})
.WithName("GetUserEmail")
.WithOpenApi();
测试
- 所有端点
- 获取天气预报在登录前收到错误 401 (未授权)
- 登录返回的 jwt 令牌
- 在 Swagger Auth 中使用 jwt 令牌
- 获取天气预报返回结果
- 获取用户电子邮件 返回用户电子邮件