JWT 身份验证是保护 API 的标准方法之一。这允许无状态身份验证,因为签名令牌是在客户端和服务器之间传递的。在 .NET 8 中,使用 JWT 令牌的方式得到了改进。将它们与 AppUser 类集成将为您的应用程序提供无缝身份验证。本文介绍了在 .NET 8 Web 应用程序中通过 AppUser 类实现 JWT 令牌身份验证的过程。
包含用户流的图表
JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于将信息作为 JSON 对象在各方之间安全地传输。此信息是经过数字签名的,因此可以验证和信任。可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对对 JWT 进行签名。 尽管 JWT 可以加密以在各方之间提供机密性,但我们将重点介绍签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则对其他方隐藏这些声明。当使用公钥/私钥对令牌进行签名时,签名还会证明只有持有私钥的一方是签署私钥的一方。
在其紧凑形式中,JSON Web 令牌由三个部分组成,由点 () 分隔,它们是:.
因此,JWT 通常如下所示。
xxxxx.yyyyy.zzzzz
更多详情请访问 https://jwt.io/introduction
dotnetnew webapi-n JwtAuthApp
dotnetadd package Microsoft.AspNetCore.Authentication.JwtBearer
dotnetadd package Microsoft.IdentityModel.Tokens
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);
}
}
{
"Jwt":{
"Issuer":"JwtAuthApp",
"Audience":"https://localhost:7031/",
"Secret":"70FC177F-3667-453D-9DA1-AF223DF6C014",
"ExpireDays":
}
}
受众:指定令牌的目标受众(通常是使用 API 的客户端或服务)。
builder.Services.AddTransient<JwtConfiguration>();
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;
},
};
});
}
}
❗️ = 重要
builder.Services.AddJwtAuthentication(builder.Configuration);
app.UseAuthentication();
app.UseAuthorization();
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(角色)和其他数据等信息。
builder.Services.AddTransient<TokenService>();
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(方便测试)
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);
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();
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();
响应令牌 :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5OTk5IiwiZW1haWwiOiJ2YWxlbnRpbi5vc2lkYWNoQGdtYWlsLmNvbSIsImV4cCI6MTczMjIxMTM5OSwiaXNzIjoiSnd0QXV0aEFwcCIsImF1ZCI6IjcwRkMxNzdGLTM2NjctNDUzRC05REExLUFGMjIzREY2QzAxNCJ9.YCBGUFiGFKMZCJJL3sgsk-1lbruSnuY2lWpY71SLx3Y
在本文中,我们演示了如何在 .NET 8 中使用最小 API 结构实现 JWT 令牌身份验证。这种方法提供了一种简单而干净的方法来保护您的 API,而不会产生控制器的开销。关键步骤包括配置 JWT 身份验证、生成令牌以及使用最少的代码保护终端节点。
通过此设置,您可以通过添加更多功能(如用户注册、令牌刷新或基于角色的授权)来进一步扩展身份验证流程。
👥 觉得这个有趣吗?与朋友分享并引发讨论。有时,最好的见解来自一场精彩的辩论。
🚀 不断前进,保持灵感,永不停止学习。
感谢您的阅读!
程序文件:
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;
}