发布于 2026-01-06 7 阅读
0

.NET Core 3.1 中的 JSON Web Tokens 身份验证(续)

在 .NET Core 3.1 中使用 JSON Web Tokens 进行身份验证

身份验证(续)

本系列教程现在也推出了在线视频课程。您可以在 YouTube 上观看第一小时,或在 Udemy 上购买完整课程。当然,您也可以继续阅读。祝您学习愉快!:)

身份验证(续)

使用 JSON Web Tokens 进行令牌认证

令牌认证背后的原理很简单。

在应用程序的这个阶段,用户可以使用用户名和密码登录。我们会验证用户的凭据,并告知用户密码正确。

但如果用户想要调用一个需要进行身份验证的函数,她就必须再次发送凭据。这是因为该Web服务是无状态的

这意味着,除非我们从请求中获取到凭据,否则我们永远无法知道是谁发送的请求。

与其每次请求都输入凭据,我们可以将用户名和密码存储在浏览器的本地存储或会话存储中,并从中获取信息。但这非常不安全,因为任何可以访问您计算机的人都可以看到您的密码。

这就是代币发挥作用的地方。

令牌本质上是一长串存储用户信息(或声明)的字符串。这些声明并不包含密码,但它可以告诉服务器用户是谁以及用户可能拥有哪些权限。

该令牌使用只有服务器知道的私钥生成,因此很难伪造。而且我们可以为该令牌设置过期日期。所以,即使有人能够窃取您的令牌,该令牌很可能在被窃取者尝试使用时立即失效。

由于此令牌不包含明文的关键信息,我们可以将其存储在浏览器中,并随每个请求自动将其发送到 Web 服务。

这样一来,该服务就能知道用户是谁,甚至可能向你的下一个请求发送一个新的令牌。

在jwt.io网站上,我们可以查看 JSON Web 令牌,甚至可以查看其中的信息是如何存储的。

你可以看到标头中包含所用算法,有效载荷中包含诸如用户名之类的声明。

JWT 示例

好的,现在让我们在 Web API 中使用 JSON Web Tokens。

JSON Web Tokens (JWT) 准备

在编写实际代码之前,我们必须先做一些事情。

我们首先从 `<key>` 标签开始appsettings.json。在这里,我们输入JWT 身份验证的安全密钥ConnectionStrings。在该部分下方,我们可以创建一个新部分AppSettings,并输入一个名为 `<key>` 的新密钥Token,并将任何字符串设置为其值。例如,`<value>`my top secret key就完全足够了。只需确保它至少包含 16 个字符即可。

"AppSettings": {
  "Token": "my top secret key"
},
Enter fullscreen mode Exit fullscreen mode

之后,我们需要在应用程序中添加一些包引用。

在 Visual Studio Code 中,您可以按 Enter 键Ctrl + Shift + P获取Add Package输入框NuGet Package Manager: Add Package,然后输入所需的包名。

添加套餐

我们需要总共三个软件包引用。

第一个是Microsoft.IdentityModel.Tokens。此软件包包含支持安全令牌和加密操作(例如签名和验证签名)的类型。您可以随时选择最新版本。

添加包引用后,Visual Studio Code 会执行还原命令来解析新的依赖项。当然,我们可以这样做。

恢复

顺便一提,除了使用 NuGet 包管理器,你也可以直接dotnet add package Microsoft.IdentityModel.Tokens在终端输入命令来添加最新版本。两种方法都可行,看你个人喜好。

第二个软件包是[软件包名称] System.IdentityModel.Tokens.Jwt。它提供了创建、序列化和验证JSON Web令牌的支持。这正是我们需要的。

最后一个Microsoft.AspNetCore.Authentication.JwtBearer是.NET Core中间件,它使我们的应用程序能够接收持有者令牌。

持有者令牌(bearer token)只是我们之前讨论过的令牌的通用名称。有些服务器使用短字符串作为令牌,而我们将使用结构化的 JSON Web 令牌。两者都可以称为持有者令牌

好的。添加这些包之后,你的.csproj文件现在应该会稍微大一些,并且包含这些新的引用。

<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="5.6.0"/>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.6.0"/>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.2"/>
Enter fullscreen mode Exit fullscreen mode

现在我们准备开始编写代码了。

JSON Web Tokens (JWT) 实现

首先,AuthRepository我们添加了一个private名为 `a` 的新方法CreateToken(),该方法返回一个 `a`对象string,并接受一个 ` Usera` 对象作为参数。

private string CreateToken(User user)
{
    return string.Empty; //token;
}
Enter fullscreen mode Exit fullscreen mode

现在我们返回一个空值string,这样我们就可以在方法中调用此方法,并在用户输入正确的密码后相应地Login()设置该值。response.Data

public async Task<ServiceResponse<string>> Login(string username, string password)
{
    ServiceResponse<string> response = new ServiceResponse<string>();
    User user = await _context.Users.FirstOrDefaultAsync(x => x.Username.ToLower().Equals(username.ToLower()));
    if (user == null)
    {
        response.Success = false;
        response.Message = "User not found.";
    }
    else if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
    {
        response.Success = false;
        response.Message = "Wrong password.";
    }
    else
    {
        response.Data = CreateToken(user);
    }
    return response;
}
Enter fullscreen mode Exit fullscreen mode

回到CreateToken()方法本身,我们声明List一个Claims

在实现此方法时,我们需要添加一些 using 指令。

我们添加的第一个索赔类型是ClaimTypes.NameIdentifier。这将是Id给定索赔的user。第二个是ClaimTypes.Name,它将简单地表示Username

List<Claim> claims = new List<Claim>
{
    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
    new Claim(ClaimTypes.Name, user.Username)
};
Enter fullscreen mode Exit fullscreen mode

接下来,我们需要一个密钥SymmetricSecurityKey。这是我们文件中的私钥appsettings.json。我们需要能够访问该文件,因此我们跳转到构造函数AuthRepository并注入密钥IConfiguration。请务必添加Microsoft.Extensions.Configurationusing 指令。

private readonly IConfiguration _configuration;
public AuthRepository(DataContext context, IConfiguration configuration)
{
    _configuration = configuration;
    _context = context;
}
Enter fullscreen mode Exit fullscreen mode

回到CreateToken()方法本身,我们创建一个类的实例SymmetricSecurityKey并为其提供一个byte数组。我们这样做Encoding.UTF8.GetBytes(),然后访问该_configuration数组以获取包含我们密钥的相应部分。

SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8
    .GetBytes(_configuration.GetSection("AppSettings:Token").Value));
Enter fullscreen mode Exit fullscreen mode

有了这个密钥,我们就可以创建新的密钥SigningCredentials,并使用该HmacSha512Signature算法。

SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
Enter fullscreen mode Exit fullscreen mode

接下来是 `.` 对象SecurityTokenDescriptor。该对象获取用于创建最终令牌的信息。例如,我们将提供声明和过期日期。

要设置声明,我们使用Subjecta 进行设置new ClaimsIdentity,并赋予它claims我们之前创建的。

Expires可以设置为任何日期。比如第二天?那么DateTime.Now.AddDays(1)……

最后,SigningCredentials就是我们的creds对象。

SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(claims),
    Expires = DateTime.Now.AddDays(1),
    SigningCredentials = creds
};
Enter fullscreen mode Exit fullscreen mode

我们快完成了。

现在我们需要一个新的JwtSecurityTokenHandler,并利用这个tokenHandlertokenDescriptor来创建SecurityToken

JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
Enter fullscreen mode Exit fullscreen mode

最后,tokenHandler.WriteToken(token);我们将 JSON Web 令牌作为string.

private string CreateToken(User user)
{
    List<Claim> claims = new List<Claim>
    {
        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
        new Claim(ClaimTypes.Name, user.Username)
    };

    SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8
        .GetBytes(_configuration.GetSection("AppSettings:Token").Value));

    SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

    SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(claims),
        Expires = DateTime.Now.AddDays(1),
        SigningCredentials = creds
    };

    JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
    SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);

    return tokenHandler.WriteToken(token);
}
Enter fullscreen mode Exit fullscreen mode

好了,深呼吸,我们用 Postman 测试一下。HTTP 方法为POST,我们使用登录 URL 和正确的凭据,然后点击“发送”。

{
    "data": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJwYXRyaWNrIiwibmJmIjoxNTgyMTA1MDEyLCJleHAiOjE1ODIxOTE0MTIsImlhdCI6MTU4MjEwNTAxMn0.FaxvO8pqLLkBjplEW815-DzekgBwW94gBx--_3n4X5UAs6kRuM2zflpwQ0H2PnCgIuupJKq7EED5c_mC_DI8FQ",
    "success": true,
    "message": null
}
Enter fullscreen mode Exit fullscreen mode

我们的代币在这里!

现在我们可以获取此令牌,并在 jwt.io 上深入了解 JWT 调试器。

调试 JWT

粘贴令牌后,您就可以看到我们在代码中输入的声明。

如果输入正确的密钥,就可以验证签名。

太好了!这就是我们获取JSON Web Token的方法。

授权属性

为了保护 Web 服务调用甚至整个控制器,我们可以[Authorize]在控制器类或任何需要保护的方法上使用特性。但在使用此特性之前,我们必须先为 Web 服务添加身份验证方案。这需要在类中完成Startup

在该ConfigureServices()方法中,我们使用AddAuthentication()JwtBearerDefaults.AuthenticationScheme添加一些配置选项AddJwtBearer()

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
});
Enter fullscreen mode Exit fullscreen mode

关于这一点,options我们初始化一个新的实例TokenValidationParameters并设置这些参数。

我们需要验证签名密钥,所以我们将其设置ValidateIssuerSigningKeytrue

IssuerSigningKey又是我们文件中的那个值appsettings.json。所以,一个新的SymmetricSecurityKey函数会获取编码后的AppSettings:Token值。

我们不需要验证发行者或受众,因此我们将ValidateIssuer其设置ValidateAudiencefalse

目前为止,认证方案就是这样。

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII
            .GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
        ValidateIssuer = false,
        ValidateAudience = false
    };
});
Enter fullscreen mode Exit fullscreen mode

此外,我们还需要添加 .NET CoreAuthenticationMiddlewareIApplicationBuilder启用身份验证功能。

为此,我们需要添加app.UseAuthentication(); 上面 app.UseAuthorization();那一行代码。添加这行代码非常重要。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    //app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}
Enter fullscreen mode Exit fullscreen mode

好了。有了这些,我们就可以使用身份验证了。为此,我们在类[Authorize]的顶部添加属性CharacterController

[Authorize]
[ApiController]
[Route("[controller]")]
public class CharacterController : ControllerBase
Enter fullscreen mode Exit fullscreen mode

如果我们现在想使用 Postman 通过GETHTTP 方法和 URL获取所有 RPG 角色http://localhost:5000/character/getall,我们会收到401 未授权的返回。

获取所有未授权

这正是我们想要的!现在我们需要在请求头中添加一个令牌。我们先登录以获取JSON Web令牌。

复制 JWT

只需复制令牌,然后返回调用即可GetAll

这里我们需要添加一个头部信息。我们添加键Authorization,并将令牌作为值。令牌本身还不够,我们还需要Bearer在令牌前面加一个空格,然后添加整个词项。

现在我们终于又能获得所有RPG角色了。

使用 Bearer Token 获取所有结果

我这里还有一点要补充。

要调用任何方法CharacterController,我们都必须先进行身份验证。但我们可以对这条规则做出例外处理。例如,如果我们[AllowAnonymous]在方法顶部添加一个属性GetAll(),就可以在无需身份验证的情况下再次调用该方法。

[AllowAnonymous]
[HttpGet("GetAll")]
public async Task<IActionResult> Get()
Enter fullscreen mode Exit fullscreen mode

在 Postman 中可以看到,我们可以删除Authorization标头中的键,仍然可以从数据库中获取 RPG 角色。

允许匿名

让我们再次移除该属性,因为我们只想让经过身份验证的用户获取字符。

实际上,我们希望用户能够看到他们创建的角色。目前,他们只能看到所有角色扮演游戏角色。

因此,我们必须利用用户和角色之间的关系,并且我们还必须从已验证的用户那里获取声明。

阅读声明并获取用户的角色扮演游戏角色

该类的优点之一ControllerBase是它提供了一个User类型为 的对象ClaimsPrincipal

// Summary:
//     Gets the System.Security.Claims.ClaimsPrincipal for user associated with the
//     executing action.
public ClaimsPrincipal User { get; }
Enter fullscreen mode Exit fullscreen mode

User对象提供了我们添加到 JSON Web 令牌中的所有声明。

让我们从数据库中获取已认证用户的NameIdentifier用户名。Id

在该Get()方法中CharacterController,我们定义了一个新int变量。

通过User.Claims访问声明,然后遍历它们,或者FirstOrDefault()通过后跟 lambda 表达式找到我们想要的声明,其中Type声明的等于ClaimTypes.NameIdentifier

从该结果中,我们获取Value并将其解析为一个int

int id = int.Parse(User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value);
Enter fullscreen mode Exit fullscreen mode

现在我们需要将它传递id给它CharacterService。因此,我们对该方法进行一些更改GetAllCharacters(),并将它id作为参数添加到相应的接口和服务中,并且可能调用此参数userId以避免任何混淆。

Task<ServiceResponse<List<GetCharacterDto>>> GetAllCharacters(int userId);
Enter fullscreen mode Exit fullscreen mode

userId然后我们可以使用从声明中获得的适当值调用该方法,并对服务方法进行一些修改。

public async Task<IActionResult> Get()
{
    int userId = int.Parse(User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value);
    return Ok(await _characterService.GetAllCharacters(userId));
}
Enter fullscreen mode Exit fullscreen mode

我们唯一需要修改的方法是如何从中GetAllCharacters()访问。我们不再返回所有 RPG 角色,而是利用该函数,借助该函数仅返回与给定用户相关的角色Characters_contextWhere()userId

List<Character> dbCharacters = await _context.Characters.Where(c => c.User.Id == userId).ToListAsync();
Enter fullscreen mode Exit fullscreen mode

借助 Entity Framework,我们可以访问相关User对象及其属性,并实际获取数据库表中Id具有正确属性集的 RPG 角色。UserId

当我们用 Postman 测试时,没有返回任何字符。这完全正确,因为我们还没有设置任何关系。

但我们可以很快解决这个问题。

Characters表格中,我们只需将UserIdFrodo 的值设置为1。再次运行测试,现在我们就能得到 Frodo 了!UserId同样设置 Sam 的值,我们就能找回两位英雄了。

太棒了!这行得通。当然,我们希望通过代码添加关联关系,而不是每次都手动在数据库中添加。在此之前,让我们总结一下本节所学内容。

概括

恭喜!您已成功在 Web API 中实现 JSON Web Token 身份验证。

我们首先创建了用户模型,并将这个新用户添加为我们角色扮演游戏(RPG)角色的关联关系。这是一个一对多关系,这意味着一个用户可以拥有多个角色。

然后我们就开始深入学习身份验证的理论。你了解了身份验证的一般原理,包括密码哈希值和密码盐值,以及为什么要使用哈希值和盐值。这一切都是为了安全。

我们构建了用户注册和用户登录系统,并使用特定的加密算法对输入的密码进行哈希处理和验证。

之后,我们介绍了使用 JSON Web Token 进行令牌认证。

现在您知道什么是持有者代币,该代币中包含什么(例如使用的算法和有效载荷),以及如何向该代币添加您想要添加的任何声明。

您已经学习了如何创建 JSON Web Token 以及如何使用该[Authorize]属性保护您的 Web API。同时,您还学习了如何读取 JSON Web Token 中的声明,并使用它们向用户返回正确的数据。

太棒了!现在是时候给我们的应用添加更多关系了。例如,添加一些战斗中可以使用的技能怎么样?当然,角色生成后,我们也要立即添加用户和角色之间的正确关系。

我们将在下一节中完成所有这些工作。


本教程系列的第八部分到此结束。希望对您有所帮助。想要第一时间收到下一部分的通知,只需在dev.to上关注我或订阅我的邮件列表即可。您将第一时间获知最新内容。

下次见!

小心。


接下来:使用 Entity Framework Core 实现高级关系

图片由 cornecoba 在freepik.com上创作。


等等,还有更多!

文章来源:https://dev.to/_patrickgod/authentication-with-json-web-tokens-in-net-core-3-1-29bd