身份验证是 Web 应用程序的一个关键方面,可确保用户可以安全地访问资源。在这篇文章中,我们将逐步介绍如何在 ASP.NET Core 应用程序中实现 JSON Web 令牌 (JWT) 身份验证。我们将该过程分解为可管理的部分,以便于遵循。
首先,让我们创建一个新的 ASP.NET Core Web 应用程序项目,创建后它应该看起来像这样。
现在,让我们创建一个 Configuration 类,该类封装用于生成令牌的私钥。虽然我们通过将密钥嵌入到类中来保持简单明了,但它可以存储在其他位置,例如 appsettings。
namespace jwtAuth;
public static class Configuration
{
public static string PrivateKey => "bAafd@A7d9#@F4*V!LHZs#ebKQrkE6pad2f3kj34c3dXy@";
}
接下来,使用记录(或类)定义用户模型来表示与用户相关的信息,在我的示例中,我将包括 Id、Username、Name、Email、Password 和 Roles。
namespace jwtAuth.Models;
public record User(
int Id,
string Username,
string Name,
string Email,
string Password,
string[] Roles);
要使用 JWT,我们需要将此包添加到项目中。
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
现在,创建 AuthService(通常也称为 JwtService 或 TokenService)类,用于处理令牌生成。包括一个 Create() 方法,该方法将用户作为参数并返回生成的令牌。
using jwtAuth.Models;
namespace jwtAuth.Services;
public class AuthService
{
public string Create(User user)
{
}
}
在方法中,我将实例化 JwtSecurityTokenHandler,它负责生成令牌。
var handler = new JwtSecurityTokenHandler();
下一步是生成令牌的信息并对其进行签名。为此,我们需要私钥,因此我们将使用 SigningCredentials 对其进行签名,只需使用密钥作为 byte[] 和算法来加密它。
var privateKey = Encoding.UTF8.GetBytes(Configuration.PrivateKey);
var credentials = new SigningCredentials(
new SymmetricSecurityKey(privateKey),
SecurityAlgorithms.HmacSha256);
为了给令牌添加值,我们使用声明(可以解释为键/值结构)。
new Claim(ClaimTypes.Name, user.Username)
为了简化操作,我将创建一个方法来返回我们将保存在令牌中的 ClaimsIdentity(所有令牌声明的列表),该方法会自动添加到令牌的有效负载中。
private static ClaimsIdentity GenerateClaims(User user)
{
var ci = new ClaimsIdentity();
ci.AddClaim(new Claim("id", user.Id.ToString()));
ci.AddClaim(new Claim(ClaimTypes.Name, user.Username));
ci.AddClaim(new Claim(ClaimTypes.GivenName, user.Name));
ci.AddClaim(new Claim(ClaimTypes.Email, user.Email));
foreach (var role in user.Roles)
ci.AddClaim(new Claim(ClaimTypes.Role, role));
return ci;
}
下一个操作是创建 SecurityTokenDescriptor 的实例,以便在令牌中包含基本信息。
var tokenDescriptor = new SecurityTokenDescriptor
{
SigningCredentials = credentials,
Expires = DateTime.UtcNow.AddHours(1),
Subject = GenerateClaims(user)
};
然后我使用处理程序。CreateToken() 生成令牌和处理程序。WriteToken(token) 使用紧凑序列化格式将 JwtSecurityToken 序列化为 JWT。
var token = handler.CreateToken(tokenDescriptor);
return handler.WriteToken(token);
最后,结果应为类似于以下内容的类:
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using jwtAuth.Models;
using Microsoft.IdentityModel.Tokens;
namespace jwtAuth.Services;
public class AuthService
{
public string Create(User user)
{
var handler = new JwtSecurityTokenHandler();
var privateKey = Encoding.UTF8.GetBytes(Configuration.PrivateKey);
var credentials = new SigningCredentials(
new SymmetricSecurityKey(privateKey),
SecurityAlgorithms.HmacSha256);
var tokenDescriptor = new SecurityTokenDescriptor
{
SigningCredentials = credentials,
Expires = DateTime.UtcNow.AddHours(1),
Subject = GenerateClaims(user)
};
var token = handler.CreateToken(tokenDescriptor);
return handler.WriteToken(token);
}
private static ClaimsIdentity GenerateClaims(User user)
{
var ci = new ClaimsIdentity();
ci.AddClaim(new Claim("id", user.Id.ToString()));
ci.AddClaim(new Claim(ClaimTypes.Name, user.Username));
ci.AddClaim(new Claim(ClaimTypes.GivenName, user.Name));
ci.AddClaim(new Claim(ClaimTypes.Email, user.Email));
foreach (var role in user.Roles)
ci.AddClaim(new Claim(ClaimTypes.Role, role));
return ci;
}
}
转到 Program 类,它最初将按以下方式生成:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
为了实现基于令牌的身份验证,我们将合并构建器。Services.AddAuthentication() 中。此外,我们还将包括应用程序。UseAuthentication() 在定义路由之前。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication();
var app = builder.Build();
app.UseAuthentication();
app.MapGet("/", () => "Hello World!");
app.Run();
对于授权,我们包括**构建器。Services.AddAuthorization(),**为了激活它,我们添加了 app。UseAuthorization() 在应用程序配置中。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", () => "Hello World!");
app.Run();
要指定使用 JWT 进行身份验证,需要进行配置调整。这涉及设置 DefaultChallengeScheme 以定义如何检查每个传入请求以确定适当的身份验证方法。这可确保应用程序收到的每个请求都被视为 JWT 身份验证。
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
});
此外,为了设置令牌验证,参数是通过 AddJwtBearer() 定义的。在本例中,将使用私钥,并且为了本图的简单起见,排除了对颁发者和受众的验证。
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration.PrivateKey)),
ValidateIssuer = false,
ValidateAudience = false
};
});
为了使 authService 正常工作,我们添加了构建器。Services.AddTransient<AuthService>() 中。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient<AuthService>();
我将使用此终结点生成访问令牌:I'll use this endpoint to generate the access token:
app.MapGet("/login", (AuthService service) =>
{
var user = new User(
1,
"bruno.bernardes",
"Bruno Bernardes",
"bruno@gmail.com",
"q1w2e3r4t5",
["developer"]);
return service.Create(user);
});
为了检查用户的授权,我将使用以下端点:
app.MapGet("/test", () => "OK!")
.RequireAuthorization();
要专门为具有特定角色的用户授予对终结点的访问权限,我们可以实施一个策略,如下所示:
builder.Services.AddAuthorization(x =>
{
x.AddPolicy("tech", p => p.RequireRole("developer"));
});
之后,设置终结点以使用您刚刚创建的策略。
builder.Services.AddAuthorization(x =>
{
x.AddPolicy("tech", p => p.RequireRole("developer"));
});
app.MapGet("/test/tech", () => "tech OK!")
.RequireAuthorization("tech");
最后,你的类应该看起来像这样:
using System.Text;
using jwtAuth;
using jwtAuth.Models;
using jwtAuth.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient<AuthService>();
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration.PrivateKey)),
ValidateIssuer = false,
ValidateAudience = false
};
});
builder.Services.AddAuthorization(x =>
{
x.AddPolicy("tech", p => p.RequireRole("developer"));
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/login", (AuthService service) =>
{
var user = new User(
1,
"bruno.bernardes",
"Bruno Bernardes",
"bruno@gmail.com",
"q1w2e3r4t5",
["developer"]);
return service.Create(user);
});
app.MapGet("/test", () => "OK!")
.RequireAuthorization();
app.MapGet("/test/tech", () => "tech OK!")
.RequireAuthorization("tech");
app.Run();
1 — 在未提供令牌的情况下使用 RequireAuthorization 访问路由时,将不允许我的访问。
错误 401 未经授权
因此,我将使用 /login 端点生成令牌。
终结点返回令牌
因此,现在我可以访问终结点而不会阻止我的访问。
在标头中提供令牌并返回状态 200 OK
使用相同的令牌,我可以访问仅为我们定义的策略授权的路由
2 — 如果我更改用户的角色并生成新令牌,则测试/技术终结点不再返回 200 OK,并将通过返回 403 Forbidden 开始阻止访问。
具有新角色的用户
终结点返回 403 Forbidden
源代码获取:公众号回复消息【code:71013
】