实例详细讲解如何在 ASP.NET Core 中实现 JWT 身份验证

作者:微信公众号:【架构师老卢】
5-2 10:55
18

概述:身份验证是 Web 应用程序的一个关键方面,可确保用户可以安全地访问资源。在这篇文章中,我们将逐步介绍如何在 ASP.NET Core 应用程序中实现 JSON Web 令牌 (JWT) 身份验证。我们将该过程分解为可管理的部分,以便于遵循。首先,让我们创建一个新的 ASP.NET Core Web 应用程序项目,创建后它应该看起来像这样。现在,让我们创建一个 Configuration 类,该类封装用于生成令牌的私钥。虽然我们通过将密钥嵌入到类中来保持简单明了,但它可以存储在其他位置,例如 appsettings。namespace jwtAuth; public static cla

身份验证是 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

相关代码下载地址
重要提示!:取消关注公众号后将无法再启用回复功能,不支持解封!
第一步:微信扫码关键公众号“架构师老卢”
第二步:在公众号聊天框发送code:71013,如:code:71013 获取下载地址
第三步:恭喜你,快去下载你想要的资源吧
阅读排行