草庐IT

.NET 实现 JWT 登录验证

Fantasy-ke 2023-09-21 原文

.NET 实现JWT登录认证

在ASP.NET Core应用程序中,使用JWT进行身份验证和授权已成为一种流行的方式。JWT是一种安全的方式,用于在客户端和服务器之间传输用户信息。

添加NuGet包

首先,我们需要添加一些NuGet包来支持JWT身份验证。在您的ASP.NET Core项目中,打开Startup.cs文件,并在ConfigureServices方法中添加以下代码:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;

注册

// ...

public void ConfigureServices(IServiceCollection services)
{
    // 添加JWT身份验证服务
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        var config = Configuration.GetSection("JwtConfig").Get<JwtConfig>(); // 从appsettings.json读取JwtConfig配置
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = config.Issuer,
            ValidAudience = config.Audience,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.SecretKey))
        };
        
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = async context =>
            {
                var token = context.Request.Cookies["access_token"]; // 从Cookie中获取token值
                if (!string.IsNullOrEmpty(token))
                {
                    context.Token = token; // 将token值设置到JwtBearer上下文中的Token属性
                }
            }
        };
    });

    // ...
}

这里我们使用AddAuthentication方法添加了JWT身份验证服务,并设置了默认的认证方案为JwtBearerDefaults.AuthenticationScheme,这是JWT身份验证的默认方案。

AddJwtBearer方法中,我们通过GetSection方法从appsettings.json文件中读取了一个名为JwtConfig的配置,其中包含了JWT的一些信息,例如签发者(Issuer)、接收者(Audience)、秘钥(SecretKey)等。这些信息将用于验证和生成JWT令牌。

Configure方法中添加JWT认证中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 其他中间件配置...

    app.UseAuthentication();
    app.UseAuthorization();
   //...
}

配置appsettings.json

接下来,我们需要在appsettings.json文件中配置JWT的相关信息。在您的ASP.NET Core项目中,找到appsettings.json文件,并添加以下配置:

{
  // ...
  "JwtConfig": {
    "Issuer": "YourIssuer",
    "Audience": "YourAudience",
    "SecretKey": "YourSecretKey",
    "AccessTokenExpirationMinutes": 60,
    "RefreshTokenExpirationMinutes": 1440
  }
}

您可以根据自己的需求修改配置项的值。这里的AccessTokenExpirationMinutesRefreshTokenExpirationMinutes分别表示访问令牌和刷新令牌的过期时间,单位为分钟。

创建 JWT 设置类

接下来,我们需要创建一个 C# 类来表示 JWT 的配置项,并使用 IOptions 接口将其注入到需要的地方。以下是一个示例的 JwtSettings 类:

csharpCopy codepublic class JwtSettings
{
    public string Issuer { get; set; }
    public string Audience { get; set; }
    public string SecretKey { get; set; }
    public int AccessTokenExpirationMinutes { get; set; }
    public int RefreshTokenExpirationMinutes { get; set; }
}

这个类定义了与 appsettings.json 文件中的配置项相对应的属性。

用户模型

// Models/User.cs

public class UserModel
{
    public string Id { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public string Email { get; set; }
    public string RefreshToken {get; set; }
}

实现JWT登录认证

现在,我们可以开始实现JWT登录认证的逻辑。我们将创建一个JwtService类,用于生成和验证JWT令牌。在您的ASP.NET Core项目中,创建一个名为JwtService.cs的类文件,然后添加以下代码:

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;

public interface IJwtService
{
    string GenerateAccessToken(UserModel user);
    string GenerateRefreshToken();
    bool ValidateAccessToken(string token);
}

public class JwtService : IJwtService
{
    private readonly JwtConfig _jwtConfig;

    public JwtService(IConfiguration configuration)
    {
        _jwtConfig = configuration.GetSection("JwtConfig").Get<JwtSettings>();
    }

    public string GenerateAccessToken(UserModel user)
    {
        // 设置Token的Claims
        List<Claim> claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id),
            new Claim(ClaimTypes.Name, user.Username)
        };

        // 生成Token的密钥
        SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(appSettings.SecretKey));

        // 生成Token的签名证书
        SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);

        // 设置Token的过期时间
        DateTime expires = DateTime.Now.AddMinutes(appSettings.AccessTokenExpirationMinutes);

        // 创建Token
        JwtSecurityToken token = new JwtSecurityToken(
            appSettings.Issuer,
            appSettings.Audience,
            claims,
            expires: expires,
            signingCredentials: creds
        );

        // 生成Token字符串
        string tokenString = new JwtSecurityTokenHandler().WriteToken(token);

        return tokenString;
    }

    public string GenerateRefreshToken()
    {
        var randomNumber = new byte[32];
        using (var rng = new RNGCryptoServiceProvider())
        {
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
        }
    }

    public bool ValidateAccessToken(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.UTF8.GetBytes(_jwtConfig.SecretKey);
        try
        {
            tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = _jwtConfig.Issuer,
                ValidAudience = _jwtConfig.Audience,
                IssuerSigningKey = new SymmetricSecurityKey(key)
            }, out var validatedToken);
        }
        catch (Exception)
        {
            return false;
        }
        return true;
    }
}

这里我们创建了一个JwtService类,实现了IJwtService接口。该服务类通过依赖注入方式注入了IConfiguration,从而可以在构造函数中读取JwtConfig配置。

  • JwtService类中包含了生成访问令牌和刷新令牌的方法,以及验证访问令牌和从访问令牌中获取用户主体的方法。其中,

    • GenerateAccessToken方法使用JwtSecurityTokenHandler来生成访问令牌,并设置了过期时间、签名等参数。

    • GenerateRefreshToken方法生成一个随机的32位Base64编码的字符串作为刷新令牌。

    • ValidateAccessToken方法验证访问令牌和从访问令牌中获取用户主体。它们使用JwtSecurityTokenHandler来验证令牌的签名、过期时间等信息,并返回验证结果或用户主体。

用户信息加密

在JWT中,用户信息是以Claims的形式进行传递的,但默认情况下,这些信息是以明文的形式存储在令牌中的。为了保护用户信息的安全性,我们可以选择对用户信息进行加密。下面是一个简单的示例,演示如何在生成访问令牌时对用户信息进行加密。

public string GenerateAccessToken(UserModel user)
{
    // 设置Token的Claims
    List<Claim> claims = new List<Claim>
    {
        new Claim(ClaimTypes.NameIdentifier, user.Id),
        new Claim(ClaimTypes.Name, user.Username)
    };

    // 生成Token的密钥
    SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(appSettings.SecretKey));

    // 生成Token的签名证书
    SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);

    // 设置Token的过期时间
    DateTime expires = DateTime.Now.AddMinutes(appSettings.AccessTokenExpirationMinutes);

    // 创建Token
    JwtSecurityToken token = new JwtSecurityToken(
        appSettings.Issuer,
        appSettings.Audience,
        claims,
        expires: expires,
        signingCredentials: creds
    );

    // 生成Token字符串
    string tokenString = new JwtSecurityTokenHandler().WriteToken(token);

    return tokenString;
}

在上面的示例中,我们使用了SigningCredentials类来设置加密的参数,包括加密密钥、加密算法等。这样生成的访问令牌在传递用户信息时会进行加密,增加了用户信息的安全性。

用户登录验证

在用户登录时,我们需要对用户提供的用户名和密码进行验证,并生成访问令牌和刷新令牌。下面是一个简单的示例,演示如何在ASP.NET Core中实现用户登录验证,并生成JWT令牌。

[HttpPost("login")]
public IActionResult Login(UserModel model)
{
    // 验证用户名和密码
    var isValidUser = ValidateUser(model.Username, model.Password);

    if (!isValidUser)
    {
        return BadRequest(new { message = "Invalid username or password" });
    }

    // 生成访问令牌
    var accessToken = _jwtService.GenerateAccessToken(model);

    // 生成刷新令牌
    var refreshToken = _jwtService.GenerateRefreshToken();

    // 返回访问令牌和刷新令牌给客户端
    return Ok(new
    {
        access_token = accessToken,
        refresh_token = refreshToken
    });
}

在上面的示例中,我们通过调用_jwtService.GenerateAccessToken_jwtService.GenerateRefreshToken方法来生成访问令牌和刷新令牌,并将刷新令牌保存到数据库或其他持久化存储中,以便后续使用。

刷新令牌

在用户登录后,访问令牌会在一定时间后过期,此时用户需要使用刷新令牌来获取新的访问令牌,而无需重新登录。下面是一个简单的示例,演示如何在ASP.NET Core中实现刷新令牌功能。

[HttpPost("refresh")]
public IActionResult RefreshToken(UserModel model)
{
    // 验证刷新令牌是否有效
    var isValidRefreshToken = ValidateAccessToken(model.RefreshToken);

    if (!isValidRefreshToken)
    {
        return BadRequest(new { message = "Invalid refresh token" });
    }

    // 生成新的访问令牌
    var accessToken = _jwtService.GenerateAccessToken(model);

    // 返回新的访问令牌给客户端
    return Ok(new
    {
        access_token = accessToken
    });
}

在上面的示例中,我们通过调用_jwtService.GenerateAccessToken方法来生成新的访问令牌,并将其返回给客户端。在生成新的访问令牌时,我们可以使用之前保存的用户信息,例如用户名等。

用户登录简单验证

在每次请求时,我们需要对访问令牌进行验证,以确保用户的身份和权限。下面是一个简单的示例,演示如何在ASP.NET Core中实现对访问令牌的简单验证。

[HttpGet("profile")]
[Authorize]
public IActionResult GetUserProfile()
{
    // 获取当前用户的用户名
    var username = ....;

    // 根据用户名从数据库或其他持久化存储中获取用户信息
    var userModel = ......;

    if (userModel == null)
    {
        return NotFound(new { message = "userModel not found" });
    }

    // 返回用户信息给客户端
    return Ok(new
    {
        username = userModel.Username,
        email = userModel.Email
    });
}

在上面的示例中,我们通过添加[Authorize]属性来标记需要验证访问令牌的API端点。当客户端发送请求时,ASP.NET Core会自动验证访问令牌的有效性,并将用户信息存储在UserModel对象中,以便我们在方法内部访问。

总结

本篇博文通过一个简单的案例,介绍了如何使用 C# .NET 实现 JWT 登录验证,并处理用户信息的加密、刷新 Token、各种验证规则等功能。

有关.NET 实现 JWT 登录验证的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  3. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  4. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  5. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  6. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  7. ruby-on-rails - 如何将验证与模型分开 - 2

    我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:

  8. ruby-on-rails - 跳过状态机方法的所有验证 - 2

    当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested

  9. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

    我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

  10. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

随机推荐