Skip to content

在 .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.AddMinutes(30),
                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
{
    public string Id { get; }
    public string Email { get; }

    public AppUser(ClaimsPrincipal claimsPrincipal)
    {
        Id = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        Email = claimsPrincipal.FindFirst(ClaimTypes.Email)?.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 令牌
  • 获取天气预报返回结果
  • 获取用户电子邮件 返回用户电子邮件

最后更新于: