前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >在 .NET 8/9 中使用 AppUser 进行 JWT 令牌身份验证

在 .NET 8/9 中使用 AppUser 进行 JWT 令牌身份验证

作者头像
郑子铭
发布2024-12-23 13:49:16
发布2024-12-23 13:49:16
19300
代码可运行
举报
运行总次数:0
代码可运行

JWT 身份验证是保护 API 的标准方法之一。这允许无状态身份验证,因为签名令牌是在客户端和服务器之间传递的。在 .NET 8 中,使用 JWT 令牌的方式得到了改进。将它们与 AppUser 类集成将为您的应用程序提供无缝身份验证。本文介绍了在 .NET 8 Web 应用程序中通过 AppUser 类实现 JWT 令牌身份验证的过程。

包含用户流的图表

什么是 JSON Web 令牌?

JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于将信息作为 JSON 对象在各方之间安全地传输。此信息是经过数字签名的,因此可以验证和信任。可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对对 JWT 进行签名。 尽管 JWT 可以加密以在各方之间提供机密性,但我们将重点介绍签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则对其他方隐藏这些声明。当使用公钥/私钥对令牌进行签名时,签名还会证明只有持有私钥的一方是签署私钥的一方。

什么是 JSON Web 令牌结构?

在其紧凑形式中,JSON Web 令牌由三个部分组成,由点 () 分隔,它们是:.

  • 页眉
  • 有效载荷
  • 签名

因此,JWT 通常如下所示。

xxxxx.yyyyy.zzzzz

更多详情请访问 https://jwt.io/introduction

设置 JWT 令牌身份验证

1. 创建新的 .NET 8 Web API 项目

代码语言:javascript
代码运行次数:0
复制
dotnetnew webapi-n JwtAuthApp

2. 安装所需的 NuGet 软件包

代码语言:javascript
代码运行次数:0
复制
dotnetadd package Microsoft.AspNetCore.Authentication.JwtBearer
dotnetadd package Microsoft.IdentityModel.Tokens

3. 创建 JWT 配置模型

代码语言:javascript
代码运行次数:0
复制
usingSystem.Globalization;

namespaceJwtAuthApp.JWT;

publicclassJwtConfiguration
{
publicstring Issuer{get;}=string.Empty;

publicstring Secret{get;}=string.Empty;

publicstring Audience{get;}=string.Empty;

publicint ExpireDays{get;}

publicJwtConfiguration(IConfiguration configuration)
{
var section= configuration.GetSection("JWT");

 Issuer= section[nameof(Issuer)];
 Secret= section[nameof(Secret)];
 Audience= section[nameof(Secret)];
 ExpireDays= Convert.ToInt32(section[nameof(ExpireDays)], CultureInfo.InvariantCulture);
}
}

4. 将 JWT 配置添加到您的 app.settings 中

代码语言:javascript
代码运行次数:0
复制
{
"Jwt":{
"Issuer":"JwtAuthApp",
"Audience":"https://localhost:7031/",
"Secret":"70FC177F-3667-453D-9DA1-AF223DF6C014",
"ExpireDays":
}
}
  • ❗️颁发者:标识颁发令牌的委托人(通常是您的应用程序)。

受众:指定令牌的目标受众(通常是使用 API 的客户端或服务)。

  • ❗️密钥:密钥用于对 JWT 进行签名,以确保其真实性。它应该是一个长而随机的字符串,以防止篡改。
  • ExpireDays:定义令牌在过期前的有效期。

5. 为 Configuration 配置 DIProgram.cs

代码语言:javascript
代码运行次数:0
复制
builder.Services.AddTransient<JwtConfiguration>();

6. 配置 JWT 身份验证扩展

代码语言:javascript
代码运行次数:0
复制
usingMicrosoft.AspNetCore.Authentication;
usingMicrosoft.AspNetCore.Authentication.JwtBearer;
usingMicrosoft.AspNetCore.DataProtection;
usingMicrosoft.IdentityModel.Tokens;
usingSystem.ComponentModel.DataAnnotations;
usingSystem.Diagnostics.Metrics;
usingSystem.Security.Claims;
usingSystem;
usingSystem.Text;

namespaceJwtAuthApp.JWT;

publicstaticclassJwtAuthBuilderExtesnions
{
publicstaticAuthenticationBuilderAddJwtAuthentication(thisIServiceCollection services,IConfiguration configuration)
{
var jwtConfiguration=newJwtConfiguration(configuration);

 services.AddAuthorization();

return services.AddAuthentication(x=>
{
 x.DefaultAuthenticateScheme= JwtBearerDefaults.AuthenticationScheme;
 x.DefaultChallengeScheme= JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x=>
{
 x.SaveToken=true;
 x.TokenValidationParameters=newTokenValidationParameters
{
 ValidateIssuer=true,
 ValidateAudience=true,
 ValidateLifetime=true,
 ValidateIssuerSigningKey=true,
 ValidIssuer= jwtConfiguration.Issuer,
 ValidAudience= jwtConfiguration.Audience,
 IssuerSigningKey=newSymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfiguration.Secret)),

 RequireExpirationTime=true,
};
 x.Events=newJwtBearerEvents
{
 OnMessageReceived= context=>
{
string authorization= context.Request.Headers["Authorization"\];

if(string.IsNullOrEmpty(authorization))
{
 context.NoResult();
}
else
{
 context.Token= authorization.Replace("Bearer ",string.Empty);
}

return Task.CompletedTask;
},
};
});
}
}
  • SaveToken:定义在成功授权后是否应将不记名令牌存储在 AuthenticationProperties 中。
  • ❗️**ValidateIssuer:**确保令牌的 (issuer) 声明与预期的颁发者匹配。iss
  • ValidateAudience:验证令牌中的 (audience) 声明,以确保它与预期的受众匹配。aud
  • ValidateLifetime:检查令牌的(过期)时间是否有效,令牌是否未过期。exp
  • ❗️ValidateIssuerSigningKey:根据签名密钥验证令牌的签名,以确保其完整性。
  • ❗️ValidIssuer:指定从配置(或环境变量)中提取的令牌的预期颁发者。appsettings.json
  • ❗️IssuerSigningKey:使用对称安全密钥对 JWT 进行签名和验证,将配置中的密钥转换为字节数组进行加密。
  • RequireExpirationTime:确保 JWT 令牌包含 (expiration) 声明。exp
  • OnMessageReceived:在上下文中保存令牌

❗️ = 重要

7. 在Program.cs

代码语言:javascript
代码运行次数:0
复制
builder.Services.AddJwtAuthentication(builder.Configuration);

app.UseAuthentication();
app.UseAuthorization();

8. 创建 Token 生成服务

代码语言:javascript
代码运行次数:0
复制
usingMicrosoft.IdentityModel.Tokens;
usingSystem.IdentityModel.Tokens.Jwt;
usingSystem.Security.Claims;
usingSystem.Text;

namespaceJwtAuthApp.JWT;

publicclassTokenService
{
privatereadonlyJwtConfiguration _config;

publicTokenService(JwtConfiguration config)
{
 _config= config;
}

publicstringGenerateToken(string id,string email)
{
var claims=new[]
{
newClaim(JwtRegisteredClaimNames.Sub, id),
newClaim(JwtRegisteredClaimNames.Email, email),
// Add more claims if needed
};

var key=newSymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.Secret));
var creds=newSigningCredentials(key, SecurityAlgorithms.HmacSha256);

var token=newJwtSecurityToken(
issuer: _config.Issuer,
audience: _config.Audience,
claims: claims,
expires: DateTime.Now.AddDays(_config.ExpireDays),
signingCredentials: creds
);

returnnewJwtSecurityTokenHandler().WriteToken(token);
}
}

我不建议在令牌中存储 First Name(名字)、Last Name(姓氏)、Role(角色)和其他数据等信息。

9. 注册 Token Service

代码语言:javascript
代码运行次数:0
复制
builder.Services.AddTransient<TokenService>();

10. 添加登录端点或控制器

代码语言:javascript
代码运行次数:0
复制
app.MapPost("/login",(LoginRequest request,TokenService tokenService)=>
{
// In a real app, you would validate the user's credentials against a database.
// Authenticate user and generate token
// For demo purposes, we are using hardcoded values
var userIsAuthenticated= request.Username=="admin"&& request.Password=="admin";

if(!userIsAuthenticated)
{
return Results.Unauthorized();
}
var userId="9999";// Get user id from database
var email="valentin.osidach@gmail.com";// Get email from database
var token= tokenService.GenerateToken(userId, email);

return Results.Ok(token);
}).AllowAnonymous();

11. 新增 SwaggerConfiguration(方便测试)

代码语言:javascript
代码运行次数:0
复制
usingMicrosoft.OpenApi.Models;
usingSwashbuckle.AspNetCore.SwaggerGen;

namespaceJwtAuthApp.JWT;

publicstaticclassSwaggerConfiguration
{
publicstaticOpenApiSecurityScheme Scheme=>newOpenApiSecurityScheme
{
 In= ParameterLocation.Header,
 Description="Please enter a valid token",
 Name="Authorization",
 Type= SecuritySchemeType.Http,
 BearerFormat="JWT",
 Scheme="Bearer",
 Reference=newOpenApiReference
{
 Id="Bearer",
 Type= ReferenceType.SecurityScheme,
},
};

publicstaticvoidConfigure(SwaggerGenOptions option)
{
 option.ResolveConflictingActions(apiDesc=> apiDesc.First());
 option.SwaggerDoc("v1",newOpenApiInfo{ Title="API", Version="v1"});
 option.AddSecurityDefinition(Scheme.Reference.Id, Scheme);
 option.AddSecurityRequirement(newOpenApiSecurityRequirement
{
{ Scheme, Array.Empty<string>()},
});
}
}builder.Services.AddSwaggerGen(SwaggerConfiguration.Configure);

12. 添加 AppUser

代码语言:javascript
代码运行次数:0
复制
publicclassAppUser:ClaimsPrincipal
{
publicAppUser(IHttpContextAccessor contextAccessor):base(contextAccessor.HttpContext.User){}

publicstring Id=>FindFirst(ClaimTypes.NameIdentifier).Value;
publicstring Email=>FindFirst(ClaimTypes.Email).Value;
}builder.Services.AddTransient<AppUser>();
builder.Services.AddHttpContextAccessor();

13. 为所有 Controller 或端点添加 Authorize 属性

代码语言:javascript
代码运行次数:0
复制
app.MapGet("/weatherforecast",()=>
{
var forecast= Enumerable.Range(,).Select(index=>
newWeatherForecast
(
 DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
 Random.Shared.Next(-,),
 summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.RequireAuthorization()
.WithName("GetWeatherForecast")
.WithOpenApi();

//Inject AppUser and get user email from the token
app.MapGet("/user",(AppUser user)=>
{
return Results.Ok(user.Email);
})
.RequireAuthorization()
.WithName("GetUserEmail")
.WithOpenApi();

测试

  • 所有端点
  • 获取天气预报在登录前收到错误 401 (未授权)
  • 登录返回的 jwt 令牌

响应令牌 :

代码语言:javascript
代码运行次数:0
复制
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5OTk5IiwiZW1haWwiOiJ2YWxlbnRpbi5vc2lkYWNoQGdtYWlsLmNvbSIsImV4cCI6MTczMjIxMTM5OSwiaXNzIjoiSnd0QXV0aEFwcCIsImF1ZCI6IjcwRkMxNzdGLTM2NjctNDUzRC05REExLUFGMjIzREY2QzAxNCJ9.YCBGUFiGFKMZCJJL3sgsk-1lbruSnuY2lWpY71SLx3Y
  • 在 Swagger Auth 中使用 jwt 令牌
  • 获取天气预报返回结果
  • 获取用户电子邮件 返回用户电子邮件

在本文中,我们演示了如何在 .NET 8 中使用最小 API 结构实现 JWT 令牌身份验证。这种方法提供了一种简单而干净的方法来保护您的 API,而不会产生控制器的开销。关键步骤包括配置 JWT 身份验证、生成令牌以及使用最少的代码保护终端节点。

通过此设置,您可以通过添加更多功能(如用户注册、令牌刷新或基于角色的授权)来进一步扩展身份验证流程。

👥 觉得这个有趣吗?与朋友分享并引发讨论。有时,最好的见解来自一场精彩的辩论。

🚀 不断前进,保持灵感,永不停止学习。

感谢您的阅读!

程序文件:

代码语言:javascript
代码运行次数:0
复制
usingJwtAuthApp.JWT;
usingMicrosoft.AspNetCore.Authorization;
usingSystem.Security.Claims;

var builder= WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHttpContextAccessor();

builder.Services.AddTransient<JwtConfiguration>();
builder.Services.AddTransient<TokenService>();
builder.Services.AddTransient<AppUser>();

// Add JWT Authentication configuration
builder.Services.AddJwtAuthentication(builder.Configuration);
builder.Services.AddSwaggerGen(SwaggerConfiguration.Configure);

var app= builder.Build();

app.UseAuthentication();
app.UseAuthorization();

if(app.Environment.IsDevelopment())
{
 app.UseSwagger();
 app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var summaries=new[]
{
"Freezing","NY","Chilly","Cool","Mild","Warm","Balmy","Hot","Sweltering","Lviv"
};


app.MapPost("/login",(LoginRequest request,TokenService tokenService)=>
{
// In a real app, you would validate the user's credentials against a database.
// Authenticate user and generate token
// For demo purposes, we are using hardcoded values
var userIsAuthenticated= request.Username=="admin"&& request.Password=="admin";

if(!userIsAuthenticated)
{
return Results.Unauthorized();
}
var userId="9999";// Get user id from database
var email="valentin.osidach@gmail.com";// Get email from database
var token= tokenService.GenerateToken(userId, email);

return Results.Ok(token);
}).AllowAnonymous();

app.MapGet("/weatherforecast",()=>
{
var forecast= Enumerable.Range(,).Select(index=>
newWeatherForecast
(
 DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
 Random.Shared.Next(-,),
 summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.RequireAuthorization()
.WithName("GetWeatherForecast")
.WithOpenApi();

//get user email from token
app.MapGet("/user",[Authorize](AppUser user)=>
{
return Results.Ok(user.Email);
})
.RequireAuthorization()
.WithName("GetUserEmail")
.WithOpenApi();

app.Run();

publicclassLoginRequest
{
publicstring Username{get;set;}=string.Empty;
publicstring Password{get;set;}=string.Empty;
}

publicrecordWeatherForecast(DateOnly Date,int TemperatureC,string? Summary)
{
publicint TemperatureF=>+(int)(TemperatureC/0.5556);
}

publicclassAppUser:ClaimsPrincipal
{
publicAppUser(IHttpContextAccessor contextAccessor):base(contextAccessor.HttpContext.User){}

publicstring Id=>FindFirst(ClaimTypes.NameIdentifier).Value;
publicstring Email=>FindFirst(ClaimTypes.Email).Value;
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet NB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是 JSON Web 令牌?
  • 什么是 JSON Web 令牌结构?
  • 设置 JWT 令牌身份验证
  • 1. 创建新的 .NET 8 Web API 项目
  • 2. 安装所需的 NuGet 软件包
  • 3. 创建 JWT 配置模型
  • 4. 将 JWT 配置添加到您的 app.settings 中
  • 5. 为 Configuration 配置 DIProgram.cs
  • 6. 配置 JWT 身份验证扩展
  • 7. 在Program.cs
  • 8. 创建 Token 生成服务
  • 9. 注册 Token Service
  • 10. 添加登录端点或控制器
  • 12. 添加 AppUser
  • 13. 为所有 Controller 或端点添加 Authorize 属性
  • 测试
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档